• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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