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