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
5package org.chromium.android_webview;
6
7import android.graphics.Picture;
8import android.os.Handler;
9import android.os.Looper;
10import android.os.Message;
11import android.os.SystemClock;
12
13import org.chromium.base.VisibleForTesting;
14
15import 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
24public 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
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
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
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 {
93        private MyHandler(Looper looper) {
94            super(looper);
95        }
96
97        @Override
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
152    public AwContentsClientCallbackHelper(Looper looper, AwContentsClient contentsClient) {
153        mHandler = new MyHandler(looper);
154        mContentsClient = contentsClient;
155    }
156
157    public void postOnLoadResource(String url) {
158        mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_LOAD_RESOURCE, url));
159    }
160
161    public void postOnPageStarted(String url) {
162        mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_PAGE_STARTED, url));
163    }
164
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
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
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
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
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