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.content.ContentResolver;
      8 import android.content.Context;
      9 import android.net.Uri;
     10 import android.os.AsyncTask;
     11 import android.os.Handler;
     12 import android.os.Message;
     13 import android.provider.MediaStore;
     14 import android.util.Log;
     15 import android.view.KeyEvent;
     16 import android.view.View;
     17 import android.webkit.ConsoleMessage;
     18 import android.webkit.ValueCallback;
     19 
     20 import org.chromium.base.ContentUriUtils;
     21 import org.chromium.base.ThreadUtils;
     22 import org.chromium.content.browser.ContentVideoView;
     23 import org.chromium.content.browser.ContentViewCore;
     24 
     25 /**
     26  * Adapts the AwWebContentsDelegate interface to the AwContentsClient interface.
     27  * This class also serves a secondary function of routing certain callbacks from the content layer
     28  * to specific listener interfaces.
     29  */
     30 class AwWebContentsDelegateAdapter extends AwWebContentsDelegate {
     31     private static final String TAG = "AwWebContentsDelegateAdapter";
     32 
     33     final AwContentsClient mContentsClient;
     34     View mContainerView;
     35     final Context mContext;
     36 
     37     public AwWebContentsDelegateAdapter(AwContentsClient contentsClient,
     38             View containerView, Context context) {
     39         mContentsClient = contentsClient;
     40         setContainerView(containerView);
     41         mContext = context;
     42     }
     43 
     44     public void setContainerView(View containerView) {
     45         mContainerView = containerView;
     46     }
     47 
     48     @Override
     49     public void onLoadProgressChanged(int progress) {
     50         mContentsClient.onProgressChanged(progress);
     51     }
     52 
     53     @Override
     54     public void handleKeyboardEvent(KeyEvent event) {
     55         if (event.getAction() == KeyEvent.ACTION_DOWN) {
     56             int direction;
     57             switch (event.getKeyCode()) {
     58                 case KeyEvent.KEYCODE_DPAD_DOWN:
     59                     direction = View.FOCUS_DOWN;
     60                     break;
     61                 case KeyEvent.KEYCODE_DPAD_UP:
     62                     direction = View.FOCUS_UP;
     63                     break;
     64                 case KeyEvent.KEYCODE_DPAD_LEFT:
     65                     direction = View.FOCUS_LEFT;
     66                     break;
     67                 case KeyEvent.KEYCODE_DPAD_RIGHT:
     68                     direction = View.FOCUS_RIGHT;
     69                     break;
     70                 default:
     71                     direction = 0;
     72                     break;
     73             }
     74             if (direction != 0 && tryToMoveFocus(direction)) return;
     75         }
     76         mContentsClient.onUnhandledKeyEvent(event);
     77     }
     78 
     79     @Override
     80     public boolean takeFocus(boolean reverse) {
     81         int direction =
     82             (reverse == (mContainerView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL)) ?
     83             View.FOCUS_RIGHT : View.FOCUS_LEFT;
     84         if (tryToMoveFocus(direction)) return true;
     85         direction = reverse ? View.FOCUS_UP : View.FOCUS_DOWN;
     86         return tryToMoveFocus(direction);
     87     }
     88 
     89     private boolean tryToMoveFocus(int direction) {
     90         View focus = mContainerView.focusSearch(direction);
     91         return focus != null && focus != mContainerView && focus.requestFocus();
     92     }
     93 
     94     @Override
     95     public boolean addMessageToConsole(int level, String message, int lineNumber,
     96             String sourceId) {
     97         ConsoleMessage.MessageLevel messageLevel = ConsoleMessage.MessageLevel.DEBUG;
     98         switch(level) {
     99             case LOG_LEVEL_TIP:
    100                 messageLevel = ConsoleMessage.MessageLevel.TIP;
    101                 break;
    102             case LOG_LEVEL_LOG:
    103                 messageLevel = ConsoleMessage.MessageLevel.LOG;
    104                 break;
    105             case LOG_LEVEL_WARNING:
    106                 messageLevel = ConsoleMessage.MessageLevel.WARNING;
    107                 break;
    108             case LOG_LEVEL_ERROR:
    109                 messageLevel = ConsoleMessage.MessageLevel.ERROR;
    110                 break;
    111             default:
    112                 Log.w(TAG, "Unknown message level, defaulting to DEBUG");
    113                 break;
    114         }
    115 
    116         return mContentsClient.onConsoleMessage(
    117                 new ConsoleMessage(message, sourceId, lineNumber, messageLevel));
    118     }
    119 
    120     @Override
    121     public void onUpdateUrl(String url) {
    122         // TODO: implement
    123     }
    124 
    125     @Override
    126     public void openNewTab(String url, String extraHeaders, byte[] postData, int disposition,
    127             boolean isRendererInitiated) {
    128         // This is only called in chrome layers.
    129         assert false;
    130     }
    131 
    132     @Override
    133     public void closeContents() {
    134         mContentsClient.onCloseWindow();
    135     }
    136 
    137     @Override
    138     public void showRepostFormWarningDialog(final ContentViewCore contentViewCore) {
    139         // TODO(mkosiba) We should be using something akin to the JsResultReceiver as the
    140         // callback parameter (instead of ContentViewCore) and implement a way of converting
    141         // that to a pair of messages.
    142         final int MSG_CONTINUE_PENDING_RELOAD = 1;
    143         final int MSG_CANCEL_PENDING_RELOAD = 2;
    144 
    145         // TODO(sgurun) Remember the URL to cancel the reload behavior
    146         // if it is different than the most recent NavigationController entry.
    147         final Handler handler = new Handler(ThreadUtils.getUiThreadLooper()) {
    148             @Override
    149             public void handleMessage(Message msg) {
    150                 switch(msg.what) {
    151                     case MSG_CONTINUE_PENDING_RELOAD: {
    152                         contentViewCore.continuePendingReload();
    153                         break;
    154                     }
    155                     case MSG_CANCEL_PENDING_RELOAD: {
    156                         contentViewCore.cancelPendingReload();
    157                         break;
    158                     }
    159                     default:
    160                         throw new IllegalStateException(
    161                                 "WebContentsDelegateAdapter: unhandled message " + msg.what);
    162                 }
    163             }
    164         };
    165 
    166         Message resend = handler.obtainMessage(MSG_CONTINUE_PENDING_RELOAD);
    167         Message dontResend = handler.obtainMessage(MSG_CANCEL_PENDING_RELOAD);
    168         mContentsClient.onFormResubmission(dontResend, resend);
    169     }
    170 
    171     @Override
    172     public void runFileChooser(final int processId, final int renderId, final int modeFlags,
    173             String acceptTypes, String title, String defaultFilename, boolean capture) {
    174         AwContentsClient.FileChooserParams params = new AwContentsClient.FileChooserParams();
    175         params.mode = modeFlags;
    176         params.acceptTypes = acceptTypes;
    177         params.title = title;
    178         params.defaultFilename = defaultFilename;
    179         params.capture = capture;
    180 
    181         mContentsClient.showFileChooser(new ValueCallback<String[]>() {
    182             boolean completed = false;
    183             @Override
    184             public void onReceiveValue(String[] results) {
    185                 if (completed) {
    186                     throw new IllegalStateException("Duplicate showFileChooser result");
    187                 }
    188                 completed = true;
    189                 if (results == null) {
    190                     nativeFilesSelectedInChooser(
    191                             processId, renderId, modeFlags, null, null);
    192                     return;
    193                 }
    194                 GetDisplayNameTask task = new GetDisplayNameTask(
    195                         mContext.getContentResolver(), processId, renderId, modeFlags, results);
    196                 task.execute();
    197             }
    198         }, params);
    199     }
    200 
    201     @Override
    202     public boolean addNewContents(boolean isDialog, boolean isUserGesture) {
    203         return mContentsClient.onCreateWindow(isDialog, isUserGesture);
    204     }
    205 
    206     @Override
    207     public void activateContents() {
    208         mContentsClient.onRequestFocus();
    209     }
    210 
    211     @Override
    212     public void toggleFullscreenModeForTab(boolean enterFullscreen) {
    213         if (!enterFullscreen) {
    214             ContentVideoView videoView = ContentVideoView.getContentVideoView();
    215             if (videoView != null) videoView.exitFullscreen(false);
    216         }
    217     }
    218 
    219     private static class GetDisplayNameTask extends AsyncTask<Void, Void, String[]> {
    220         final int mProcessId;
    221         final int mRenderId;
    222         final int mModeFlags;
    223         final String[] mFilePaths;
    224         final ContentResolver mContentResolver;
    225 
    226         public GetDisplayNameTask(ContentResolver contentResolver, int processId, int renderId,
    227                                   int modeFlags, String[] filePaths) {
    228             mProcessId = processId;
    229             mRenderId = renderId;
    230             mModeFlags = modeFlags;
    231             mFilePaths = filePaths;
    232             mContentResolver = contentResolver;
    233         }
    234 
    235         @Override
    236         protected String[] doInBackground(Void...voids) {
    237             String[] displayNames = new String[mFilePaths.length];
    238             for (int i = 0; i < mFilePaths.length; i++) {
    239                 displayNames[i] = resolveFileName(mFilePaths[i]);
    240             }
    241             return displayNames;
    242         }
    243 
    244         @Override
    245         protected void onPostExecute(String[] result) {
    246             nativeFilesSelectedInChooser(mProcessId, mRenderId, mModeFlags, mFilePaths, result);
    247         }
    248 
    249         /**
    250          * @return the display name of a path if it is a content URI and is present in the database
    251          * or an empty string otherwise.
    252          */
    253         private String resolveFileName(String filePath) {
    254             if (mContentResolver == null || filePath == null) return "";
    255             Uri uri = Uri.parse(filePath);
    256             return ContentUriUtils.getDisplayName(
    257                     uri, mContentResolver, MediaStore.MediaColumns.DISPLAY_NAME);
    258         }
    259     }
    260 }
    261