| 1 | // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | package org.chromium.android_webview; |
| 6 | |
| 7 | import android.graphics.Picture; |
| 8 | import android.os.Handler; |
| 9 | import android.os.Looper; |
| 10 | import android.os.Message; |
| 11 | import android.os.SystemClock; |
| 12 | |
| 13 | import org.chromium.content.browser.ContentViewCore; |
| 14 | |
| 15 | import java.util.concurrent.Callable; |
| 16 | |
| 17 | /** |
| 18 | * This class is responsible for calling certain client callbacks on the UI thread. |
| 19 | * |
| 20 | * Most callbacks do no go through here, but get forwarded to AwContentsClient directly. The |
| 21 | * messages processed here may originate from the IO or UI thread. |
| 22 | */ |
| 23 | class AwContentsClientCallbackHelper { |
| 24 | |
| 25 | // TODO(boliu): Consider removing DownloadInfo and LoginRequestInfo by using native |
| 26 | // MessageLoop to post directly to AwContents. |
| 27 | |
| 28 | private static class DownloadInfo { |
| 29 | final String mUrl; |
| 30 | final String mUserAgent; |
| 31 | final String mContentDisposition; |
| 32 | final String mMimeType; |
| 33 | final long mContentLength; |
| 34 | |
| 35 | DownloadInfo(String url, |
| 36 | String userAgent, |
| 37 | String contentDisposition, |
| 38 | String mimeType, |
| 39 | long contentLength) { |
| 40 | mUrl = url; |
| 41 | mUserAgent = userAgent; |
| 42 | mContentDisposition = contentDisposition; |
| 43 | mMimeType = mimeType; |
| 44 | mContentLength = contentLength; |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | private static class LoginRequestInfo { |
| 49 | final String mRealm; |
| 50 | final String mAccount; |
| 51 | final String mArgs; |
| 52 | |
| 53 | LoginRequestInfo(String realm, String account, String args) { |
| 54 | mRealm = realm; |
| 55 | mAccount = account; |
| 56 | mArgs = args; |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | private static class OnReceivedErrorInfo { |
| 61 | final int mErrorCode; |
| 62 | final String mDescription; |
| 63 | final String mFailingUrl; |
| 64 | |
| 65 | OnReceivedErrorInfo(int errorCode, String description, String failingUrl) { |
| 66 | mErrorCode = errorCode; |
| 67 | mDescription = description; |
| 68 | mFailingUrl = failingUrl; |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | private final static int MSG_ON_LOAD_RESOURCE = 1; |
| 73 | private final static int MSG_ON_PAGE_STARTED = 2; |
| 74 | private final static int MSG_ON_DOWNLOAD_START = 3; |
| 75 | private final static int MSG_ON_RECEIVED_LOGIN_REQUEST = 4; |
| 76 | private final static int MSG_ON_RECEIVED_ERROR = 5; |
| 77 | private final static int MSG_ON_NEW_PICTURE = 6; |
| 78 | |
| 79 | // Minimum period allowed between consecutive onNewPicture calls, to rate-limit the callbacks. |
| 80 | private static final long ON_NEW_PICTURE_MIN_PERIOD_MILLIS = 500; |
| 81 | // Timestamp of the most recent onNewPicture callback. |
| 82 | private long mLastPictureTime = 0; |
| 83 | // True when a onNewPicture callback is currenly in flight. |
| 84 | private boolean mHasPendingOnNewPicture = false; |
| 85 | |
| 86 | private final AwContentsClient mContentsClient; |
| 87 | |
| 88 | private final Handler mHandler = new Handler(Looper.getMainLooper()) { |
| 89 | @Override |
| 90 | public void handleMessage(Message msg) { |
| 91 | switch(msg.what) { |
| 92 | case MSG_ON_LOAD_RESOURCE: { |
| 93 | final String url = (String) msg.obj; |
| 94 | mContentsClient.onLoadResource(url); |
| 95 | break; |
| 96 | } |
| 97 | case MSG_ON_PAGE_STARTED: { |
| 98 | final String url = (String) msg.obj; |
| 99 | mContentsClient.onPageStarted(url); |
| 100 | break; |
| 101 | } |
| 102 | case MSG_ON_DOWNLOAD_START: { |
| 103 | DownloadInfo info = (DownloadInfo) msg.obj; |
| 104 | mContentsClient.onDownloadStart(info.mUrl, info.mUserAgent, |
| 105 | info.mContentDisposition, info.mMimeType, info.mContentLength); |
| 106 | break; |
| 107 | } |
| 108 | case MSG_ON_RECEIVED_LOGIN_REQUEST: { |
| 109 | LoginRequestInfo info = (LoginRequestInfo) msg.obj; |
| 110 | mContentsClient.onReceivedLoginRequest(info.mRealm, info.mAccount, info.mArgs); |
| 111 | break; |
| 112 | } |
| 113 | case MSG_ON_RECEIVED_ERROR: { |
| 114 | OnReceivedErrorInfo info = (OnReceivedErrorInfo) msg.obj; |
| 115 | mContentsClient.onReceivedError(info.mErrorCode, info.mDescription, |
| 116 | info.mFailingUrl); |
| 117 | break; |
| 118 | } |
| 119 | case MSG_ON_NEW_PICTURE: { |
| 120 | Picture picture = null; |
| 121 | try { |
| 122 | if (msg.obj != null) picture = (Picture) ((Callable<?>) msg.obj).call(); |
| 123 | } catch (Exception e) { |
| 124 | throw new RuntimeException("Error getting picture", e); |
| 125 | } |
| 126 | mContentsClient.onNewPicture(picture); |
| 127 | mLastPictureTime = SystemClock.uptimeMillis(); |
| 128 | mHasPendingOnNewPicture = false; |
| 129 | break; |
| 130 | } |
| 131 | default: |
| 132 | throw new IllegalStateException( |
| 133 | "AwContentsClientCallbackHelper: unhandled message " + msg.what); |
| 134 | } |
| 135 | } |
| 136 | }; |
| 137 | |
| 138 | public AwContentsClientCallbackHelper(AwContentsClient contentsClient) { |
| 139 | mContentsClient = contentsClient; |
| 140 | } |
| 141 | |
| 142 | public void postOnLoadResource(String url) { |
| 143 | mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_LOAD_RESOURCE, url)); |
| 144 | } |
| 145 | |
| 146 | public void postOnPageStarted(String url) { |
| 147 | mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_PAGE_STARTED, url)); |
| 148 | } |
| 149 | |
| 150 | public void postOnDownloadStart(String url, String userAgent, String contentDisposition, |
| 151 | String mimeType, long contentLength) { |
| 152 | DownloadInfo info = new DownloadInfo(url, userAgent, contentDisposition, mimeType, |
| 153 | contentLength); |
| 154 | mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_DOWNLOAD_START, info)); |
| 155 | } |
| 156 | |
| 157 | public void postOnReceivedLoginRequest(String realm, String account, String args) { |
| 158 | LoginRequestInfo info = new LoginRequestInfo(realm, account, args); |
| 159 | mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_RECEIVED_LOGIN_REQUEST, info)); |
| 160 | } |
| 161 | |
| 162 | public void postOnReceivedError(int errorCode, String description, String failingUrl) { |
| 163 | OnReceivedErrorInfo info = new OnReceivedErrorInfo(errorCode, description, failingUrl); |
| 164 | mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_RECEIVED_ERROR, info)); |
| 165 | } |
| 166 | |
| 167 | public void postOnNewPicture(Callable<Picture> pictureProvider) { |
| 168 | if (mHasPendingOnNewPicture) return; |
| 169 | mHasPendingOnNewPicture = true; |
| 170 | long pictureTime = java.lang.Math.max(mLastPictureTime + ON_NEW_PICTURE_MIN_PERIOD_MILLIS, |
| 171 | SystemClock.uptimeMillis()); |
| 172 | mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ON_NEW_PICTURE, pictureProvider), |
| 173 | pictureTime); |
| 174 | } |
| 175 | } |