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