Home | History | Annotate | Download | only in android_webview
      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 org.chromium.base.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 
     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