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 5package org.chromium.android_webview; 6 7import android.content.ContentResolver; 8import android.content.Context; 9import android.net.Uri; 10import android.os.AsyncTask; 11import android.os.Handler; 12import android.os.Message; 13import android.provider.MediaStore; 14import android.util.Log; 15import android.view.KeyEvent; 16import android.view.View; 17import android.webkit.ConsoleMessage; 18import android.webkit.ValueCallback; 19 20import org.chromium.base.ContentUriUtils; 21import org.chromium.base.ThreadUtils; 22import org.chromium.content.browser.ContentVideoView; 23import 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 */ 30class 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 msgContinuePendingReload = 1; 143 final int msgCancelPendingReload = 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 msgContinuePendingReload: { 152 contentViewCore.getWebContents().getNavigationController() 153 .continuePendingReload(); 154 break; 155 } 156 case msgCancelPendingReload: { 157 contentViewCore.getWebContents().getNavigationController() 158 .cancelPendingReload(); 159 break; 160 } 161 default: 162 throw new IllegalStateException( 163 "WebContentsDelegateAdapter: unhandled message " + msg.what); 164 } 165 } 166 }; 167 168 Message resend = handler.obtainMessage(msgContinuePendingReload); 169 Message dontResend = handler.obtainMessage(msgCancelPendingReload); 170 mContentsClient.onFormResubmission(dontResend, resend); 171 } 172 173 @Override 174 public void runFileChooser(final int processId, final int renderId, final int modeFlags, 175 String acceptTypes, String title, String defaultFilename, boolean capture) { 176 AwContentsClient.FileChooserParams params = new AwContentsClient.FileChooserParams(); 177 params.mode = modeFlags; 178 params.acceptTypes = acceptTypes; 179 params.title = title; 180 params.defaultFilename = defaultFilename; 181 params.capture = capture; 182 183 mContentsClient.showFileChooser(new ValueCallback<String[]>() { 184 boolean mCompleted = false; 185 @Override 186 public void onReceiveValue(String[] results) { 187 if (mCompleted) { 188 throw new IllegalStateException("Duplicate showFileChooser result"); 189 } 190 mCompleted = true; 191 if (results == null) { 192 nativeFilesSelectedInChooser( 193 processId, renderId, modeFlags, null, null); 194 return; 195 } 196 GetDisplayNameTask task = new GetDisplayNameTask( 197 mContext.getContentResolver(), processId, renderId, modeFlags, results); 198 task.execute(); 199 } 200 }, params); 201 } 202 203 @Override 204 public boolean addNewContents(boolean isDialog, boolean isUserGesture) { 205 return mContentsClient.onCreateWindow(isDialog, isUserGesture); 206 } 207 208 @Override 209 public void activateContents() { 210 mContentsClient.onRequestFocus(); 211 } 212 213 @Override 214 public void toggleFullscreenModeForTab(boolean enterFullscreen) { 215 if (!enterFullscreen) { 216 ContentVideoView videoView = ContentVideoView.getContentVideoView(); 217 if (videoView != null) videoView.exitFullscreen(false); 218 } 219 } 220 221 private static class GetDisplayNameTask extends AsyncTask<Void, Void, String[]> { 222 final int mProcessId; 223 final int mRenderId; 224 final int mModeFlags; 225 final String[] mFilePaths; 226 final ContentResolver mContentResolver; 227 228 public GetDisplayNameTask(ContentResolver contentResolver, int processId, int renderId, 229 int modeFlags, String[] filePaths) { 230 mProcessId = processId; 231 mRenderId = renderId; 232 mModeFlags = modeFlags; 233 mFilePaths = filePaths; 234 mContentResolver = contentResolver; 235 } 236 237 @Override 238 protected String[] doInBackground(Void...voids) { 239 String[] displayNames = new String[mFilePaths.length]; 240 for (int i = 0; i < mFilePaths.length; i++) { 241 displayNames[i] = resolveFileName(mFilePaths[i]); 242 } 243 return displayNames; 244 } 245 246 @Override 247 protected void onPostExecute(String[] result) { 248 nativeFilesSelectedInChooser(mProcessId, mRenderId, mModeFlags, mFilePaths, result); 249 } 250 251 /** 252 * @return the display name of a path if it is a content URI and is present in the database 253 * or an empty string otherwise. 254 */ 255 private String resolveFileName(String filePath) { 256 if (mContentResolver == null || filePath == null) return ""; 257 Uri uri = Uri.parse(filePath); 258 return ContentUriUtils.getDisplayName( 259 uri, mContentResolver, MediaStore.MediaColumns.DISPLAY_NAME); 260 } 261 } 262} 263