1 // Copyright 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 com.google.common.annotations.VisibleForTesting; 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 @VisibleForTesting 24 public class AwContentsClientCallbackHelper { 25 26 // TODO(boliu): Consider removing DownloadInfo and LoginRequestInfo by using native 27 // MessageLoop to post directly to AwContents. 28 29 private static class DownloadInfo { 30 final String mUrl; 31 final String mUserAgent; 32 final String mContentDisposition; 33 final String mMimeType; 34 final long mContentLength; 35 DownloadInfo(String url, String userAgent, String contentDisposition, String mimeType, long contentLength)36 DownloadInfo(String url, 37 String userAgent, 38 String contentDisposition, 39 String mimeType, 40 long contentLength) { 41 mUrl = url; 42 mUserAgent = userAgent; 43 mContentDisposition = contentDisposition; 44 mMimeType = mimeType; 45 mContentLength = contentLength; 46 } 47 } 48 49 private static class LoginRequestInfo { 50 final String mRealm; 51 final String mAccount; 52 final String mArgs; 53 LoginRequestInfo(String realm, String account, String args)54 LoginRequestInfo(String realm, String account, String args) { 55 mRealm = realm; 56 mAccount = account; 57 mArgs = args; 58 } 59 } 60 61 private static class OnReceivedErrorInfo { 62 final int mErrorCode; 63 final String mDescription; 64 final String mFailingUrl; 65 OnReceivedErrorInfo(int errorCode, String description, String failingUrl)66 OnReceivedErrorInfo(int errorCode, String description, String failingUrl) { 67 mErrorCode = errorCode; 68 mDescription = description; 69 mFailingUrl = failingUrl; 70 } 71 } 72 73 private static final int MSG_ON_LOAD_RESOURCE = 1; 74 private static final int MSG_ON_PAGE_STARTED = 2; 75 private static final int MSG_ON_DOWNLOAD_START = 3; 76 private static final int MSG_ON_RECEIVED_LOGIN_REQUEST = 4; 77 private static final int MSG_ON_RECEIVED_ERROR = 5; 78 private static final int MSG_ON_NEW_PICTURE = 6; 79 private static final int MSG_ON_SCALE_CHANGED_SCALED = 7; 80 81 // Minimum period allowed between consecutive onNewPicture calls, to rate-limit the callbacks. 82 private static final long ON_NEW_PICTURE_MIN_PERIOD_MILLIS = 500; 83 // Timestamp of the most recent onNewPicture callback. 84 private long mLastPictureTime = 0; 85 // True when a onNewPicture callback is currenly in flight. 86 private boolean mHasPendingOnNewPicture = false; 87 88 private final AwContentsClient mContentsClient; 89 90 private final Handler mHandler; 91 92 private class MyHandler extends Handler { MyHandler(Looper looper)93 private MyHandler(Looper looper) { 94 super(looper); 95 } 96 97 @Override handleMessage(Message msg)98 public void handleMessage(Message msg) { 99 switch(msg.what) { 100 case MSG_ON_LOAD_RESOURCE: { 101 final String url = (String) msg.obj; 102 mContentsClient.onLoadResource(url); 103 break; 104 } 105 case MSG_ON_PAGE_STARTED: { 106 final String url = (String) msg.obj; 107 mContentsClient.onPageStarted(url); 108 break; 109 } 110 case MSG_ON_DOWNLOAD_START: { 111 DownloadInfo info = (DownloadInfo) msg.obj; 112 mContentsClient.onDownloadStart(info.mUrl, info.mUserAgent, 113 info.mContentDisposition, info.mMimeType, info.mContentLength); 114 break; 115 } 116 case MSG_ON_RECEIVED_LOGIN_REQUEST: { 117 LoginRequestInfo info = (LoginRequestInfo) msg.obj; 118 mContentsClient.onReceivedLoginRequest(info.mRealm, info.mAccount, info.mArgs); 119 break; 120 } 121 case MSG_ON_RECEIVED_ERROR: { 122 OnReceivedErrorInfo info = (OnReceivedErrorInfo) msg.obj; 123 mContentsClient.onReceivedError(info.mErrorCode, info.mDescription, 124 info.mFailingUrl); 125 break; 126 } 127 case MSG_ON_NEW_PICTURE: { 128 Picture picture = null; 129 try { 130 if (msg.obj != null) picture = (Picture) ((Callable<?>) msg.obj).call(); 131 } catch (Exception e) { 132 throw new RuntimeException("Error getting picture", e); 133 } 134 mContentsClient.onNewPicture(picture); 135 mLastPictureTime = SystemClock.uptimeMillis(); 136 mHasPendingOnNewPicture = false; 137 break; 138 } 139 case MSG_ON_SCALE_CHANGED_SCALED: { 140 float oldScale = Float.intBitsToFloat(msg.arg1); 141 float newScale = Float.intBitsToFloat(msg.arg2); 142 mContentsClient.onScaleChangedScaled(oldScale, newScale); 143 break; 144 } 145 default: 146 throw new IllegalStateException( 147 "AwContentsClientCallbackHelper: unhandled message " + msg.what); 148 } 149 } 150 } 151 AwContentsClientCallbackHelper(Looper looper, AwContentsClient contentsClient)152 public AwContentsClientCallbackHelper(Looper looper, AwContentsClient contentsClient) { 153 mHandler = new MyHandler(looper); 154 mContentsClient = contentsClient; 155 } 156 postOnLoadResource(String url)157 public void postOnLoadResource(String url) { 158 mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_LOAD_RESOURCE, url)); 159 } 160 postOnPageStarted(String url)161 public void postOnPageStarted(String url) { 162 mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_PAGE_STARTED, url)); 163 } 164 postOnDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength)165 public void postOnDownloadStart(String url, String userAgent, String contentDisposition, 166 String mimeType, long contentLength) { 167 DownloadInfo info = new DownloadInfo(url, userAgent, contentDisposition, mimeType, 168 contentLength); 169 mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_DOWNLOAD_START, info)); 170 } 171 postOnReceivedLoginRequest(String realm, String account, String args)172 public void postOnReceivedLoginRequest(String realm, String account, String args) { 173 LoginRequestInfo info = new LoginRequestInfo(realm, account, args); 174 mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_RECEIVED_LOGIN_REQUEST, info)); 175 } 176 postOnReceivedError(int errorCode, String description, String failingUrl)177 public void postOnReceivedError(int errorCode, String description, String failingUrl) { 178 OnReceivedErrorInfo info = new OnReceivedErrorInfo(errorCode, description, failingUrl); 179 mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_RECEIVED_ERROR, info)); 180 } 181 postOnNewPicture(Callable<Picture> pictureProvider)182 public void postOnNewPicture(Callable<Picture> pictureProvider) { 183 if (mHasPendingOnNewPicture) return; 184 mHasPendingOnNewPicture = true; 185 long pictureTime = java.lang.Math.max(mLastPictureTime + ON_NEW_PICTURE_MIN_PERIOD_MILLIS, 186 SystemClock.uptimeMillis()); 187 mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ON_NEW_PICTURE, pictureProvider), 188 pictureTime); 189 } 190 postOnScaleChangedScaled(float oldScale, float newScale)191 public void postOnScaleChangedScaled(float oldScale, float newScale) { 192 // The float->int->float conversion here is to avoid unnecessary allocations. The 193 // documentation states that intBitsToFloat(floatToIntBits(a)) == a for all values of a 194 // (except for NaNs which are collapsed to a single canonical NaN, but we don't care for 195 // that case). 196 mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_SCALE_CHANGED_SCALED, 197 Float.floatToIntBits(oldScale), Float.floatToIntBits(newScale))); 198 } 199 } 200