• 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.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 
AwWebContentsDelegateAdapter(AwContentsClient contentsClient, View containerView, Context context)37     public AwWebContentsDelegateAdapter(AwContentsClient contentsClient,
38             View containerView, Context context) {
39         mContentsClient = contentsClient;
40         setContainerView(containerView);
41         mContext = context;
42     }
43 
setContainerView(View containerView)44     public void setContainerView(View containerView) {
45         mContainerView = containerView;
46     }
47 
48     @Override
onLoadProgressChanged(int progress)49     public void onLoadProgressChanged(int progress) {
50         mContentsClient.onProgressChanged(progress);
51     }
52 
53     @Override
handleKeyboardEvent(KeyEvent event)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
takeFocus(boolean reverse)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 
tryToMoveFocus(int direction)89     private boolean tryToMoveFocus(int direction) {
90         View focus = mContainerView.focusSearch(direction);
91         return focus != null && focus != mContainerView && focus.requestFocus();
92     }
93 
94     @Override
addMessageToConsole(int level, String message, int lineNumber, String sourceId)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
onUpdateUrl(String url)121     public void onUpdateUrl(String url) {
122         // TODO: implement
123     }
124 
125     @Override
openNewTab(String url, String extraHeaders, byte[] postData, int disposition, boolean isRendererInitiated)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
closeContents()133     public void closeContents() {
134         mContentsClient.onCloseWindow();
135     }
136 
137     @Override
showRepostFormWarningDialog(final ContentViewCore contentViewCore)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
runFileChooser(final int processId, final int renderId, final int modeFlags, String acceptTypes, String title, String defaultFilename, boolean capture)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
addNewContents(boolean isDialog, boolean isUserGesture)202     public boolean addNewContents(boolean isDialog, boolean isUserGesture) {
203         return mContentsClient.onCreateWindow(isDialog, isUserGesture);
204     }
205 
206     @Override
activateContents()207     public void activateContents() {
208         mContentsClient.onRequestFocus();
209     }
210 
211     @Override
toggleFullscreenModeForTab(boolean enterFullscreen)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 
GetDisplayNameTask(ContentResolver contentResolver, int processId, int renderId, int modeFlags, String[] filePaths)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
doInBackground(Void...voids)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
onPostExecute(String[] result)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          */
resolveFileName(String filePath)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