blob: 9cf0d54d3b689557e4c19e1d7feacb121d9b23c2 [file] [log] [blame]
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001/*
Jonathan Dixon3c909522012-02-28 18:45:06 +00002 * Copyright (C) 2012 The Android Open Source Project
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.webkit;
18
19import android.animation.ObjectAnimator;
20import android.annotation.Widget;
21import android.app.ActivityManager;
22import android.app.AlertDialog;
23import android.content.BroadcastReceiver;
24import android.content.ClipData;
25import android.content.ClipboardManager;
26import android.content.ComponentCallbacks2;
27import android.content.Context;
28import android.content.DialogInterface;
29import android.content.DialogInterface.OnCancelListener;
30import android.content.Intent;
31import android.content.IntentFilter;
32import android.content.pm.PackageManager;
33import android.content.res.Configuration;
34import android.database.DataSetObserver;
35import android.graphics.Bitmap;
36import android.graphics.BitmapFactory;
37import android.graphics.BitmapShader;
38import android.graphics.Canvas;
39import android.graphics.Color;
40import android.graphics.ColorFilter;
41import android.graphics.DrawFilter;
42import android.graphics.Paint;
43import android.graphics.PaintFlagsDrawFilter;
44import android.graphics.Picture;
45import android.graphics.Point;
George Mountf9c1f992012-03-21 16:06:10 -070046import android.graphics.PointF;
Jonathan Dixonded37ed92012-02-13 17:26:46 -080047import android.graphics.Rect;
48import android.graphics.RectF;
49import android.graphics.Region;
50import android.graphics.RegionIterator;
51import android.graphics.Shader;
52import android.graphics.drawable.Drawable;
53import android.net.Proxy;
54import android.net.ProxyProperties;
55import android.net.Uri;
56import android.net.http.SslCertificate;
57import android.os.AsyncTask;
58import android.os.Bundle;
59import android.os.Handler;
Jeff Brown9d3bdbd2012-03-21 11:50:06 -070060import android.os.Looper;
Jonathan Dixonded37ed92012-02-13 17:26:46 -080061import android.os.Message;
Jonathan Dixonded37ed92012-02-13 17:26:46 -080062import android.os.SystemClock;
63import android.provider.Settings;
64import android.security.KeyChain;
65import android.speech.tts.TextToSpeech;
66import android.text.Editable;
67import android.text.InputType;
68import android.text.Selection;
69import android.text.TextUtils;
Jonathan Dixonded37ed92012-02-13 17:26:46 -080070import android.util.DisplayMetrics;
71import android.util.EventLog;
72import android.util.Log;
73import android.view.Display;
74import android.view.Gravity;
75import android.view.HapticFeedbackConstants;
76import android.view.HardwareCanvas;
77import android.view.InputDevice;
78import android.view.KeyCharacterMap;
79import android.view.KeyEvent;
80import android.view.LayoutInflater;
81import android.view.MotionEvent;
82import android.view.ScaleGestureDetector;
83import android.view.SoundEffectConstants;
84import android.view.VelocityTracker;
85import android.view.View;
Jonathan Dixon3c909522012-02-28 18:45:06 +000086import android.view.View.MeasureSpec;
Jonathan Dixonded37ed92012-02-13 17:26:46 -080087import android.view.ViewConfiguration;
88import android.view.ViewGroup;
89import android.view.ViewParent;
90import android.view.ViewTreeObserver;
91import android.view.WindowManager;
92import android.view.accessibility.AccessibilityEvent;
93import android.view.accessibility.AccessibilityManager;
94import android.view.accessibility.AccessibilityNodeInfo;
95import android.view.inputmethod.BaseInputConnection;
96import android.view.inputmethod.EditorInfo;
97import android.view.inputmethod.InputConnection;
98import android.view.inputmethod.InputMethodManager;
Jonathan Dixon3c909522012-02-28 18:45:06 +000099import android.webkit.WebView.HitTestResult;
100import android.webkit.WebView.PictureListener;
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800101import android.webkit.WebViewCore.DrawData;
102import android.webkit.WebViewCore.EventHub;
103import android.webkit.WebViewCore.TextFieldInitData;
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800104import android.webkit.WebViewCore.TouchHighlightData;
105import android.webkit.WebViewCore.WebKitHitTest;
106import android.widget.AbsoluteLayout;
107import android.widget.Adapter;
108import android.widget.AdapterView;
109import android.widget.AdapterView.OnItemClickListener;
110import android.widget.ArrayAdapter;
111import android.widget.CheckedTextView;
112import android.widget.LinearLayout;
113import android.widget.ListView;
114import android.widget.OverScroller;
115import android.widget.PopupWindow;
George Mountf70276a2012-03-12 14:22:10 -0700116import android.widget.Scroller;
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800117import android.widget.TextView;
118import android.widget.Toast;
119
120import junit.framework.Assert;
121
122import java.io.File;
123import java.io.FileInputStream;
124import java.io.FileNotFoundException;
125import java.io.FileOutputStream;
126import java.io.IOException;
127import java.io.InputStream;
128import java.io.OutputStream;
129import java.net.URLDecoder;
130import java.util.ArrayList;
131import java.util.HashMap;
132import java.util.HashSet;
133import java.util.List;
134import java.util.Map;
135import java.util.Set;
136import java.util.Vector;
137import java.util.regex.Matcher;
138import java.util.regex.Pattern;
139
140/**
Steve Block406aeb22012-04-23 18:17:19 +0100141 * Implements a backend provider for the {@link WebView} public API.
Jonathan Dixon3c909522012-02-28 18:45:06 +0000142 * @hide
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800143 */
Jonathan Dixon3c909522012-02-28 18:45:06 +0000144// TODO: Check if any WebView published API methods are called from within here, and if so
145// we should bounce the call out via the proxy to enable any sub-class to override it.
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800146@Widget
John Reckb2676f72012-03-02 14:13:06 -0800147@SuppressWarnings("deprecation")
Jonathan Dixon3c909522012-02-28 18:45:06 +0000148public final class WebViewClassic implements WebViewProvider, WebViewProvider.ScrollDelegate,
149 WebViewProvider.ViewDelegate {
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800150 private class InnerGlobalLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener {
151 @Override
152 public void onGlobalLayout() {
Jonathan Dixon3c909522012-02-28 18:45:06 +0000153 if (mWebView.isShown()) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800154 setGLRectViewport();
155 }
156 }
157 }
158
159 private class InnerScrollChangedListener implements ViewTreeObserver.OnScrollChangedListener {
160 @Override
161 public void onScrollChanged() {
Jonathan Dixon3c909522012-02-28 18:45:06 +0000162 if (mWebView.isShown()) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800163 setGLRectViewport();
164 }
165 }
166 }
167
168 /**
169 * InputConnection used for ContentEditable. This captures changes
170 * to the text and sends them either as key strokes or text changes.
171 */
George Mountbcd5dd72012-03-01 08:39:03 -0800172 class WebViewInputConnection extends BaseInputConnection {
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800173 // Used for mapping characters to keys typed.
174 private KeyCharacterMap mKeyCharacterMap;
175 private boolean mIsKeySentByMe;
176 private int mInputType;
177 private int mImeOptions;
178 private String mHint;
179 private int mMaxLength;
George Mountbcd5dd72012-03-01 08:39:03 -0800180 private boolean mIsAutoFillable;
181 private boolean mIsAutoCompleteEnabled;
182 private String mName;
George Mountdbef1c52012-03-28 14:17:13 -0700183 private int mBatchLevel;
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800184
185 public WebViewInputConnection() {
Jonathan Dixon3c909522012-02-28 18:45:06 +0000186 super(mWebView, true);
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800187 }
188
George Mountbcd5dd72012-03-01 08:39:03 -0800189 public void setAutoFillable(int queryId) {
190 mIsAutoFillable = getSettings().getAutoFillEnabled()
191 && (queryId != WebTextView.FORM_NOT_AUTOFILLABLE);
192 int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
193 if (variation != EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD
194 && (mIsAutoFillable || mIsAutoCompleteEnabled)) {
195 if (mName != null && mName.length() > 0) {
196 requestFormData(mName, mFieldPointer, mIsAutoFillable,
197 mIsAutoCompleteEnabled);
198 }
199 }
200 }
201
George Mountdbef1c52012-03-28 14:17:13 -0700202 @Override
203 public boolean beginBatchEdit() {
204 if (mBatchLevel == 0) {
205 beginTextBatch();
206 }
207 mBatchLevel++;
208 return false;
209 }
210
211 @Override
212 public boolean endBatchEdit() {
213 mBatchLevel--;
214 if (mBatchLevel == 0) {
215 commitTextBatch();
216 }
217 return false;
218 }
219
George Mountbcd5dd72012-03-01 08:39:03 -0800220 public boolean getIsAutoFillable() {
221 return mIsAutoFillable;
222 }
223
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800224 @Override
225 public boolean sendKeyEvent(KeyEvent event) {
226 // Some IMEs send key events directly using sendKeyEvents.
227 // WebViewInputConnection should treat these as text changes.
228 if (!mIsKeySentByMe) {
229 if (event.getAction() == KeyEvent.ACTION_UP) {
230 if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
231 return deleteSurroundingText(1, 0);
232 } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) {
233 return deleteSurroundingText(0, 1);
234 } else if (event.getUnicodeChar() != 0){
235 String newComposingText =
236 Character.toString((char)event.getUnicodeChar());
237 return commitText(newComposingText, 1);
238 }
239 } else if (event.getAction() == KeyEvent.ACTION_DOWN &&
240 (event.getKeyCode() == KeyEvent.KEYCODE_DEL
241 || event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL
242 || event.getUnicodeChar() != 0)) {
243 return true; // only act on action_down
244 }
245 }
246 return super.sendKeyEvent(event);
247 }
248
249 public void setTextAndKeepSelection(CharSequence text) {
250 Editable editable = getEditable();
251 int selectionStart = Selection.getSelectionStart(editable);
252 int selectionEnd = Selection.getSelectionEnd(editable);
253 text = limitReplaceTextByMaxLength(text, editable.length());
254 editable.replace(0, editable.length(), text);
255 restartInput();
256 // Keep the previous selection.
257 selectionStart = Math.min(selectionStart, editable.length());
258 selectionEnd = Math.min(selectionEnd, editable.length());
259 setSelection(selectionStart, selectionEnd);
George Mountd4080482012-04-03 11:19:08 -0700260 finishComposingText();
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800261 }
262
263 public void replaceSelection(CharSequence text) {
264 Editable editable = getEditable();
265 int selectionStart = Selection.getSelectionStart(editable);
266 int selectionEnd = Selection.getSelectionEnd(editable);
267 text = limitReplaceTextByMaxLength(text, selectionEnd - selectionStart);
268 setNewText(selectionStart, selectionEnd, text);
269 editable.replace(selectionStart, selectionEnd, text);
270 restartInput();
271 // Move caret to the end of the new text
272 int newCaret = selectionStart + text.length();
273 setSelection(newCaret, newCaret);
274 }
275
276 @Override
277 public boolean setComposingText(CharSequence text, int newCursorPosition) {
278 Editable editable = getEditable();
279 int start = getComposingSpanStart(editable);
280 int end = getComposingSpanEnd(editable);
281 if (start < 0 || end < 0) {
282 start = Selection.getSelectionStart(editable);
283 end = Selection.getSelectionEnd(editable);
284 }
285 if (end < start) {
286 int temp = end;
287 end = start;
288 start = temp;
289 }
290 CharSequence limitedText = limitReplaceTextByMaxLength(text, end - start);
291 setNewText(start, end, limitedText);
292 if (limitedText != text) {
293 newCursorPosition -= text.length() - limitedText.length();
294 }
295 super.setComposingText(limitedText, newCursorPosition);
296 if (limitedText != text) {
297 restartInput();
298 int lastCaret = start + limitedText.length();
299 finishComposingText();
300 setSelection(lastCaret, lastCaret);
301 }
302 return true;
303 }
304
305 @Override
306 public boolean commitText(CharSequence text, int newCursorPosition) {
307 setComposingText(text, newCursorPosition);
308 int cursorPosition = Selection.getSelectionEnd(getEditable());
309 setComposingRegion(cursorPosition, cursorPosition);
310 return true;
311 }
312
313 @Override
314 public boolean deleteSurroundingText(int leftLength, int rightLength) {
315 Editable editable = getEditable();
316 int cursorPosition = Selection.getSelectionEnd(editable);
317 int startDelete = Math.max(0, cursorPosition - leftLength);
318 int endDelete = Math.min(editable.length(),
319 cursorPosition + rightLength);
320 setNewText(startDelete, endDelete, "");
321 return super.deleteSurroundingText(leftLength, rightLength);
322 }
323
324 @Override
325 public boolean performEditorAction(int editorAction) {
326
327 boolean handled = true;
328 switch (editorAction) {
329 case EditorInfo.IME_ACTION_NEXT:
Jonathan Dixon3c909522012-02-28 18:45:06 +0000330 mWebView.requestFocus(View.FOCUS_FORWARD);
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800331 break;
332 case EditorInfo.IME_ACTION_PREVIOUS:
Jonathan Dixon3c909522012-02-28 18:45:06 +0000333 mWebView.requestFocus(View.FOCUS_BACKWARD);
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800334 break;
335 case EditorInfo.IME_ACTION_DONE:
Jonathan Dixon3c909522012-02-28 18:45:06 +0000336 WebViewClassic.this.hideSoftKeyboard();
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800337 break;
338 case EditorInfo.IME_ACTION_GO:
339 case EditorInfo.IME_ACTION_SEARCH:
Jonathan Dixon3c909522012-02-28 18:45:06 +0000340 WebViewClassic.this.hideSoftKeyboard();
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800341 String text = getEditable().toString();
342 passToJavaScript(text, new KeyEvent(KeyEvent.ACTION_DOWN,
343 KeyEvent.KEYCODE_ENTER));
344 passToJavaScript(text, new KeyEvent(KeyEvent.ACTION_UP,
345 KeyEvent.KEYCODE_ENTER));
346 break;
347
348 default:
349 handled = super.performEditorAction(editorAction);
350 break;
351 }
352
353 return handled;
354 }
355
356 public void initEditorInfo(WebViewCore.TextFieldInitData initData) {
357 int type = initData.mType;
358 int inputType = InputType.TYPE_CLASS_TEXT
359 | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
360 int imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
361 | EditorInfo.IME_FLAG_NO_FULLSCREEN;
362 if (!initData.mIsSpellCheckEnabled) {
363 inputType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
364 }
George Mounta34a5ed2012-03-09 16:58:40 -0800365 if (WebTextView.TEXT_AREA != type) {
366 if (initData.mIsTextFieldNext) {
367 imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
368 }
369 if (initData.mIsTextFieldPrev) {
370 imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
371 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800372 }
373 switch (type) {
374 case WebTextView.NORMAL_TEXT_FIELD:
375 imeOptions |= EditorInfo.IME_ACTION_GO;
376 break;
377 case WebTextView.TEXT_AREA:
378 inputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE
379 | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
380 | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
381 imeOptions |= EditorInfo.IME_ACTION_NONE;
382 break;
383 case WebTextView.PASSWORD:
384 inputType |= EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD;
385 imeOptions |= EditorInfo.IME_ACTION_GO;
386 break;
387 case WebTextView.SEARCH:
388 imeOptions |= EditorInfo.IME_ACTION_SEARCH;
389 break;
390 case WebTextView.EMAIL:
391 // inputType needs to be overwritten because of the different text variation.
392 inputType = InputType.TYPE_CLASS_TEXT
393 | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
394 imeOptions |= EditorInfo.IME_ACTION_GO;
395 break;
396 case WebTextView.NUMBER:
397 // inputType needs to be overwritten because of the different class.
398 inputType = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL
399 | InputType.TYPE_NUMBER_FLAG_SIGNED | InputType.TYPE_NUMBER_FLAG_DECIMAL;
400 // Number and telephone do not have both a Tab key and an
401 // action, so set the action to NEXT
402 imeOptions |= EditorInfo.IME_ACTION_NEXT;
403 break;
404 case WebTextView.TELEPHONE:
405 // inputType needs to be overwritten because of the different class.
406 inputType = InputType.TYPE_CLASS_PHONE;
407 imeOptions |= EditorInfo.IME_ACTION_NEXT;
408 break;
409 case WebTextView.URL:
410 // TYPE_TEXT_VARIATION_URI prevents Tab key from showing, so
411 // exclude it for now.
412 imeOptions |= EditorInfo.IME_ACTION_GO;
413 inputType |= InputType.TYPE_TEXT_VARIATION_URI;
414 break;
415 default:
416 imeOptions |= EditorInfo.IME_ACTION_GO;
417 break;
418 }
419 mHint = initData.mLabel;
420 mInputType = inputType;
421 mImeOptions = imeOptions;
422 mMaxLength = initData.mMaxLength;
George Mountbcd5dd72012-03-01 08:39:03 -0800423 mIsAutoCompleteEnabled = initData.mIsAutoCompleteEnabled;
424 mName = initData.mName;
425 mAutoCompletePopup.clearAdapter();
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800426 }
427
428 public void setupEditorInfo(EditorInfo outAttrs) {
429 outAttrs.inputType = mInputType;
430 outAttrs.imeOptions = mImeOptions;
431 outAttrs.hintText = mHint;
432 outAttrs.initialCapsMode = getCursorCapsMode(InputType.TYPE_CLASS_TEXT);
433 }
434
435 /**
436 * Sends a text change to webkit indirectly. If it is a single-
437 * character add or delete, it sends it as a key stroke. If it cannot
438 * be represented as a key stroke, it sends it as a field change.
439 * @param start The start offset (inclusive) of the text being changed.
440 * @param end The end offset (exclusive) of the text being changed.
441 * @param text The new text to replace the changed text.
442 */
443 private void setNewText(int start, int end, CharSequence text) {
444 mIsKeySentByMe = true;
445 Editable editable = getEditable();
446 CharSequence original = editable.subSequence(start, end);
447 boolean isCharacterAdd = false;
448 boolean isCharacterDelete = false;
449 int textLength = text.length();
450 int originalLength = original.length();
451 if (textLength > originalLength) {
452 isCharacterAdd = (textLength == originalLength + 1)
453 && TextUtils.regionMatches(text, 0, original, 0,
454 originalLength);
455 } else if (originalLength > textLength) {
456 isCharacterDelete = (textLength == originalLength - 1)
457 && TextUtils.regionMatches(text, 0, original, 0,
458 textLength);
459 }
460 if (isCharacterAdd) {
461 sendCharacter(text.charAt(textLength - 1));
462 } else if (isCharacterDelete) {
463 sendKey(KeyEvent.KEYCODE_DEL);
464 } else if ((textLength != originalLength) ||
465 !TextUtils.regionMatches(text, 0, original, 0,
466 textLength)) {
467 // Send a message so that key strokes and text replacement
468 // do not come out of order.
469 Message replaceMessage = mPrivateHandler.obtainMessage(
470 REPLACE_TEXT, start, end, text.toString());
471 mPrivateHandler.sendMessage(replaceMessage);
472 }
George Mountbcd5dd72012-03-01 08:39:03 -0800473 if (mAutoCompletePopup != null) {
474 StringBuilder newText = new StringBuilder();
475 newText.append(editable.subSequence(0, start));
476 newText.append(text);
477 newText.append(editable.subSequence(end, editable.length()));
478 mAutoCompletePopup.setText(newText.toString());
479 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800480 mIsKeySentByMe = false;
481 }
482
483 /**
484 * Send a single character to the WebView as a key down and up event.
485 * @param c The character to be sent.
486 */
487 private void sendCharacter(char c) {
488 if (mKeyCharacterMap == null) {
489 mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
490 }
491 char[] chars = new char[1];
492 chars[0] = c;
493 KeyEvent[] events = mKeyCharacterMap.getEvents(chars);
494 if (events != null) {
495 for (KeyEvent event : events) {
496 sendKeyEvent(event);
497 }
498 } else {
499 Message msg = mPrivateHandler.obtainMessage(KEY_PRESS, (int) c, 0);
500 mPrivateHandler.sendMessage(msg);
501 }
502 }
503
504 /**
505 * Send a key event for a specific key code, not a standard
506 * unicode character.
507 * @param keyCode The key code to send.
508 */
509 private void sendKey(int keyCode) {
510 long eventTime = SystemClock.uptimeMillis();
511 sendKeyEvent(new KeyEvent(eventTime, eventTime,
512 KeyEvent.ACTION_DOWN, keyCode, 0, 0,
513 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
514 KeyEvent.FLAG_SOFT_KEYBOARD));
515 sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
516 KeyEvent.ACTION_UP, keyCode, 0, 0,
517 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
518 KeyEvent.FLAG_SOFT_KEYBOARD));
519 }
520
521 private CharSequence limitReplaceTextByMaxLength(CharSequence text,
522 int numReplaced) {
523 if (mMaxLength > 0) {
524 Editable editable = getEditable();
525 int maxReplace = mMaxLength - editable.length() + numReplaced;
526 if (maxReplace < text.length()) {
527 maxReplace = Math.max(maxReplace, 0);
528 // New length is greater than the maximum. trim it down.
529 text = text.subSequence(0, maxReplace);
530 }
531 }
532 return text;
533 }
534
535 private void restartInput() {
536 InputMethodManager imm = InputMethodManager.peekInstance();
537 if (imm != null) {
538 // Since the text has changed, do not allow the IME to replace the
539 // existing text as though it were a completion.
Jonathan Dixon3c909522012-02-28 18:45:06 +0000540 imm.restartInput(mWebView);
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800541 }
542 }
543 }
544
Jonathan Dixon3c909522012-02-28 18:45:06 +0000545 private class PastePopupWindow extends PopupWindow implements View.OnClickListener {
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800546 private ViewGroup mContentView;
547 private TextView mPasteTextView;
548
549 public PastePopupWindow() {
Jonathan Dixon3c909522012-02-28 18:45:06 +0000550 super(mContext, null,
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800551 com.android.internal.R.attr.textSelectHandleWindowStyle);
552 setClippingEnabled(true);
Jonathan Dixon3c909522012-02-28 18:45:06 +0000553 LinearLayout linearLayout = new LinearLayout(mContext);
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800554 linearLayout.setOrientation(LinearLayout.HORIZONTAL);
555 mContentView = linearLayout;
556 mContentView.setBackgroundResource(
557 com.android.internal.R.drawable.text_edit_paste_window);
558
Jonathan Dixon3c909522012-02-28 18:45:06 +0000559 LayoutInflater inflater = (LayoutInflater)mContext.
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800560 getSystemService(Context.LAYOUT_INFLATER_SERVICE);
561
562 ViewGroup.LayoutParams wrapContent = new ViewGroup.LayoutParams(
563 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
564
565 mPasteTextView = (TextView) inflater.inflate(
566 com.android.internal.R.layout.text_edit_action_popup_text, null);
567 mPasteTextView.setLayoutParams(wrapContent);
568 mContentView.addView(mPasteTextView);
569 mPasteTextView.setText(com.android.internal.R.string.paste);
570 mPasteTextView.setOnClickListener(this);
571 this.setContentView(mContentView);
572 }
573
George Mountf9c1f992012-03-21 16:06:10 -0700574 public void show(Point cursorBottom, Point cursorTop,
575 int windowLeft, int windowTop) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800576 measureContent();
577
578 int width = mContentView.getMeasuredWidth();
579 int height = mContentView.getMeasuredHeight();
George Mountf9c1f992012-03-21 16:06:10 -0700580 int y = cursorTop.y - height;
581 int x = cursorTop.x - (width / 2);
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800582 if (y < windowTop) {
583 // There's not enough room vertically, move it below the
584 // handle.
George Mount9a676bf2012-03-01 16:28:12 -0800585 ensureSelectionHandles();
George Mountf9c1f992012-03-21 16:06:10 -0700586 y = cursorBottom.y + mSelectHandleCenter.getIntrinsicHeight();
587 x = cursorBottom.x - (width / 2);
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800588 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800589 if (x < windowLeft) {
590 x = windowLeft;
591 }
592 if (!isShowing()) {
Jonathan Dixon3c909522012-02-28 18:45:06 +0000593 showAtLocation(mWebView, Gravity.NO_GRAVITY, x, y);
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800594 }
595 update(x, y, width, height);
596 }
597
598 public void hide() {
599 dismiss();
600 }
601
602 @Override
603 public void onClick(View view) {
604 pasteFromClipboard();
605 selectionDone();
606 }
607
608 protected void measureContent() {
609 final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
610 mContentView.measure(
611 View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels,
612 View.MeasureSpec.AT_MOST),
613 View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels,
614 View.MeasureSpec.AT_MOST));
615 }
616 }
617
618 // The listener to capture global layout change event.
619 private InnerGlobalLayoutListener mGlobalLayoutListener = null;
620
621 // The listener to capture scroll event.
622 private InnerScrollChangedListener mScrollChangedListener = null;
623
624 // if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing
625 // the screen all-the-time. Good for profiling our drawing code
626 static private final boolean AUTO_REDRAW_HACK = false;
George Mount557748d2012-04-04 13:56:49 -0700627
628 // The rate at which edit text is scrolled in content pixels per millisecond
629 static private final float TEXT_SCROLL_RATE = 0.01f;
630
631 // The presumed scroll rate for the first scroll of edit text
632 static private final long TEXT_SCROLL_FIRST_SCROLL_MS = 16;
633
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800634 // true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK
635 private boolean mAutoRedraw;
636
637 // Reference to the AlertDialog displayed by InvokeListBox.
638 // It's used to dismiss the dialog in destroy if not done before.
639 private AlertDialog mListBoxDialog = null;
640
641 static final String LOGTAG = "webview";
642
643 private ZoomManager mZoomManager;
644
645 private final Rect mGLRectViewport = new Rect();
646 private final Rect mViewRectViewport = new Rect();
647 private final RectF mVisibleContentRect = new RectF();
648 private boolean mGLViewportEmpty = false;
649 WebViewInputConnection mInputConnection = null;
650 private int mFieldPointer;
651 private PastePopupWindow mPasteWindow;
George Mountf70276a2012-03-12 14:22:10 -0700652 private AutoCompletePopup mAutoCompletePopup;
George Mount7102eb22012-04-10 13:41:51 -0700653 Rect mEditTextContentBounds = new Rect();
George Mountf70276a2012-03-12 14:22:10 -0700654 Rect mEditTextContent = new Rect();
655 int mEditTextLayerId;
656 boolean mIsEditingText = false;
George Mountdbef1c52012-03-28 14:17:13 -0700657 ArrayList<Message> mBatchedTextChanges = new ArrayList<Message>();
658 boolean mIsBatchingTextChanges = false;
George Mount557748d2012-04-04 13:56:49 -0700659 private long mLastEditScroll = 0;
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800660
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800661 private static class OnTrimMemoryListener implements ComponentCallbacks2 {
662 private static OnTrimMemoryListener sInstance = null;
663
664 static void init(Context c) {
665 if (sInstance == null) {
666 sInstance = new OnTrimMemoryListener(c.getApplicationContext());
667 }
668 }
669
670 private OnTrimMemoryListener(Context c) {
671 c.registerComponentCallbacks(this);
672 }
673
674 @Override
675 public void onConfigurationChanged(Configuration newConfig) {
676 // Ignore
677 }
678
679 @Override
680 public void onLowMemory() {
681 // Ignore
682 }
683
684 @Override
685 public void onTrimMemory(int level) {
686 if (DebugFlags.WEB_VIEW) {
687 Log.d("WebView", "onTrimMemory: " + level);
688 }
689 // When framework reset EGL context during high memory pressure, all
690 // the existing GL resources for the html5 video will be destroyed
691 // at native side.
692 // Here we just need to clean up the Surface Texture which is static.
Dianne Hackborn162bc0e2012-04-09 14:06:16 -0700693 if (level >= TRIM_MEMORY_UI_HIDDEN) {
694 HTML5VideoInline.cleanupSurfaceTexture();
695 }
Jonathan Dixon3c909522012-02-28 18:45:06 +0000696 WebViewClassic.nativeOnTrimMemory(level);
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800697 }
698
699 }
700
701 // A final CallbackProxy shared by WebViewCore and BrowserFrame.
Jonathan Dixon3c909522012-02-28 18:45:06 +0000702 private CallbackProxy mCallbackProxy;
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800703
Jonathan Dixon3c909522012-02-28 18:45:06 +0000704 private WebViewDatabase mDatabase;
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800705
706 // SSL certificate for the main top-level page (if secure)
707 private SslCertificate mCertificate;
708
709 // Native WebView pointer that is 0 until the native object has been
710 // created.
711 private int mNativeClass;
712 // This would be final but it needs to be set to null when the WebView is
713 // destroyed.
714 private WebViewCore mWebViewCore;
715 // Handler for dispatching UI messages.
716 /* package */ final Handler mPrivateHandler = new PrivateHandler();
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800717 // Used to ignore changes to webkit text that arrives to the UI side after
718 // more key events.
719 private int mTextGeneration;
720
721 /* package */ void incrementTextGeneration() { mTextGeneration++; }
722
723 // Used by WebViewCore to create child views.
Jonathan Dixon3c909522012-02-28 18:45:06 +0000724 /* package */ ViewManager mViewManager;
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800725
726 // Used to display in full screen mode
727 PluginFullScreenHolder mFullScreenHolder;
728
729 /**
730 * Position of the last touch event in pixels.
731 * Use integer to prevent loss of dragging delta calculation accuracy;
732 * which was done in float and converted to integer, and resulted in gradual
733 * and compounding touch position and view dragging mismatch.
734 */
735 private int mLastTouchX;
736 private int mLastTouchY;
737 private int mStartTouchX;
738 private int mStartTouchY;
739 private float mAverageAngle;
740
741 /**
742 * Time of the last touch event.
743 */
744 private long mLastTouchTime;
745
746 /**
747 * Time of the last time sending touch event to WebViewCore
748 */
749 private long mLastSentTouchTime;
750
751 /**
752 * The minimum elapsed time before sending another ACTION_MOVE event to
753 * WebViewCore. This really should be tuned for each type of the devices.
754 * For example in Google Map api test case, it takes Dream device at least
755 * 150ms to do a full cycle in the WebViewCore by processing a touch event,
756 * triggering the layout and drawing the picture. While the same process
757 * takes 60+ms on the current high speed device. If we make
758 * TOUCH_SENT_INTERVAL too small, there will be multiple touch events sent
759 * to WebViewCore queue and the real layout and draw events will be pushed
760 * to further, which slows down the refresh rate. Choose 50 to favor the
761 * current high speed devices. For Dream like devices, 100 is a better
762 * choice. Maybe make this in the buildspec later.
763 * (Update 12/14/2010: changed to 0 since current device should be able to
764 * handle the raw events and Map team voted to have the raw events too.
765 */
766 private static final int TOUCH_SENT_INTERVAL = 0;
767 private int mCurrentTouchInterval = TOUCH_SENT_INTERVAL;
768
769 /**
770 * Helper class to get velocity for fling
771 */
772 VelocityTracker mVelocityTracker;
773 private int mMaximumFling;
774 private float mLastVelocity;
775 private float mLastVelX;
776 private float mLastVelY;
777
778 // The id of the native layer being scrolled.
779 private int mCurrentScrollingLayerId;
780 private Rect mScrollingLayerRect = new Rect();
781
782 // only trigger accelerated fling if the new velocity is at least
783 // MINIMUM_VELOCITY_RATIO_FOR_ACCELERATION times of the previous velocity
784 private static final float MINIMUM_VELOCITY_RATIO_FOR_ACCELERATION = 0.2f;
785
786 /**
787 * Touch mode
Jeff Brown9d3bdbd2012-03-21 11:50:06 -0700788 * TODO: Some of this is now unnecessary as it is handled by
789 * WebInputTouchDispatcher (such as click, long press, and double tap).
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800790 */
791 private int mTouchMode = TOUCH_DONE_MODE;
792 private static final int TOUCH_INIT_MODE = 1;
793 private static final int TOUCH_DRAG_START_MODE = 2;
794 private static final int TOUCH_DRAG_MODE = 3;
795 private static final int TOUCH_SHORTPRESS_START_MODE = 4;
796 private static final int TOUCH_SHORTPRESS_MODE = 5;
797 private static final int TOUCH_DOUBLE_TAP_MODE = 6;
798 private static final int TOUCH_DONE_MODE = 7;
799 private static final int TOUCH_PINCH_DRAG = 8;
800 private static final int TOUCH_DRAG_LAYER_MODE = 9;
George Mountfcff68f2012-03-20 10:21:57 -0700801 private static final int TOUCH_DRAG_TEXT_MODE = 10;
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800802
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800803 // true when the touch movement exceeds the slop
804 private boolean mConfirmMove;
George Mountf70276a2012-03-12 14:22:10 -0700805 private boolean mTouchInEditText;
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800806
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800807 // Whether or not to draw the cursor ring.
808 private boolean mDrawCursorRing = true;
809
810 // true if onPause has been called (and not onResume)
811 private boolean mIsPaused;
812
813 private HitTestResult mInitialHitTestResult;
814 private WebKitHitTest mFocusedNode;
815
816 /**
817 * Customizable constant
818 */
819 // pre-computed square of ViewConfiguration.getScaledTouchSlop()
820 private int mTouchSlopSquare;
821 // pre-computed square of ViewConfiguration.getScaledDoubleTapSlop()
822 private int mDoubleTapSlopSquare;
823 // pre-computed density adjusted navigation slop
824 private int mNavSlop;
825 // This should be ViewConfiguration.getTapTimeout()
826 // But system time out is 100ms, which is too short for the browser.
827 // In the browser, if it switches out of tap too soon, jump tap won't work.
828 // In addition, a double tap on a trackpad will always have a duration of
829 // 300ms, so this value must be at least that (otherwise we will timeout the
830 // first tap and convert it to a long press).
831 private static final int TAP_TIMEOUT = 300;
832 // This should be ViewConfiguration.getLongPressTimeout()
833 // But system time out is 500ms, which is too short for the browser.
834 // With a short timeout, it's difficult to treat trigger a short press.
835 private static final int LONG_PRESS_TIMEOUT = 1000;
836 // needed to avoid flinging after a pause of no movement
837 private static final int MIN_FLING_TIME = 250;
838 // draw unfiltered after drag is held without movement
839 private static final int MOTIONLESS_TIME = 100;
840 // The amount of content to overlap between two screens when going through
841 // pages with the space bar, in pixels.
842 private static final int PAGE_SCROLL_OVERLAP = 24;
843
844 /**
845 * These prevent calling requestLayout if either dimension is fixed. This
846 * depends on the layout parameters and the measure specs.
847 */
848 boolean mWidthCanMeasure;
849 boolean mHeightCanMeasure;
850
851 // Remember the last dimensions we sent to the native side so we can avoid
852 // sending the same dimensions more than once.
853 int mLastWidthSent;
854 int mLastHeightSent;
855 // Since view height sent to webkit could be fixed to avoid relayout, this
856 // value records the last sent actual view height.
857 int mLastActualHeightSent;
858
859 private int mContentWidth; // cache of value from WebViewCore
860 private int mContentHeight; // cache of value from WebViewCore
861
862 // Need to have the separate control for horizontal and vertical scrollbar
863 // style than the View's single scrollbar style
864 private boolean mOverlayHorizontalScrollbar = true;
865 private boolean mOverlayVerticalScrollbar = false;
866
867 // our standard speed. this way small distances will be traversed in less
868 // time than large distances, but we cap the duration, so that very large
869 // distances won't take too long to get there.
870 private static final int STD_SPEED = 480; // pixels per second
871 // time for the longest scroll animation
872 private static final int MAX_DURATION = 750; // milliseconds
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800873
874 // Used by OverScrollGlow
875 OverScroller mScroller;
George Mountf70276a2012-03-12 14:22:10 -0700876 Scroller mEditTextScroller;
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800877
878 private boolean mInOverScrollMode = false;
879 private static Paint mOverScrollBackground;
880 private static Paint mOverScrollBorder;
881
882 private boolean mWrapContent;
883 private static final int MOTIONLESS_FALSE = 0;
884 private static final int MOTIONLESS_PENDING = 1;
885 private static final int MOTIONLESS_TRUE = 2;
886 private static final int MOTIONLESS_IGNORE = 3;
887 private int mHeldMotionless;
888
889 // An instance for injecting accessibility in WebViews with disabled
890 // JavaScript or ones for which no accessibility script exists
891 private AccessibilityInjector mAccessibilityInjector;
892
893 // flag indicating if accessibility script is injected so we
894 // know to handle Shift and arrows natively first
895 private boolean mAccessibilityScriptInjected;
896
897
898 /**
899 * How long the caret handle will last without being touched.
900 */
901 private static final long CARET_HANDLE_STAMINA_MS = 3000;
902
903 private Drawable mSelectHandleLeft;
904 private Drawable mSelectHandleRight;
905 private Drawable mSelectHandleCenter;
George Mountf9c1f992012-03-21 16:06:10 -0700906 private Point mSelectHandleLeftOffset;
907 private Point mSelectHandleRightOffset;
908 private Point mSelectHandleCenterOffset;
909 private Point mSelectCursorBase = new Point();
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800910 private int mSelectCursorBaseLayerId;
George Mountf9c1f992012-03-21 16:06:10 -0700911 private QuadF mSelectCursorBaseTextQuad = new QuadF();
912 private Point mSelectCursorExtent = new Point();
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800913 private int mSelectCursorExtentLayerId;
George Mountf9c1f992012-03-21 16:06:10 -0700914 private QuadF mSelectCursorExtentTextQuad = new QuadF();
915 private Point mSelectDraggingCursor;
916 private Point mSelectDraggingOffset;
917 private QuadF mSelectDraggingTextQuad;
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800918 private boolean mIsCaretSelection;
919 static final int HANDLE_ID_START = 0;
920 static final int HANDLE_ID_END = 1;
921 static final int HANDLE_ID_BASE = 2;
922 static final int HANDLE_ID_EXTENT = 3;
923
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800924 // the color used to highlight the touch rectangles
925 static final int HIGHLIGHT_COLOR = 0x6633b5e5;
926 // the region indicating where the user touched on the screen
927 private Region mTouchHighlightRegion = new Region();
928 // the paint for the touch highlight
929 private Paint mTouchHightlightPaint = new Paint();
930 // debug only
931 private static final boolean DEBUG_TOUCH_HIGHLIGHT = true;
932 private static final int TOUCH_HIGHLIGHT_ELAPSE_TIME = 2000;
933 private Paint mTouchCrossHairColor;
934 private int mTouchHighlightX;
935 private int mTouchHighlightY;
936 private long mTouchHighlightRequested;
937
938 // Basically this proxy is used to tell the Video to update layer tree at
939 // SetBaseLayer time and to pause when WebView paused.
940 private HTML5VideoViewProxy mHTML5VideoViewProxy;
941
942 // If we are using a set picture, don't send view updates to webkit
943 private boolean mBlockWebkitViewMessages = false;
944
945 // cached value used to determine if we need to switch drawing models
946 private boolean mHardwareAccelSkia = false;
947
948 /*
949 * Private message ids
950 */
951 private static final int REMEMBER_PASSWORD = 1;
952 private static final int NEVER_REMEMBER_PASSWORD = 2;
953 private static final int SWITCH_TO_SHORTPRESS = 3;
954 private static final int SWITCH_TO_LONGPRESS = 4;
955 private static final int RELEASE_SINGLE_TAP = 5;
George Mountbcd5dd72012-03-01 08:39:03 -0800956 private static final int REQUEST_FORM_DATA = 6;
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800957 private static final int DRAG_HELD_MOTIONLESS = 8;
958 private static final int AWAKEN_SCROLL_BARS = 9;
959 private static final int PREVENT_DEFAULT_TIMEOUT = 10;
960 private static final int SCROLL_SELECT_TEXT = 11;
961
962
963 private static final int FIRST_PRIVATE_MSG_ID = REMEMBER_PASSWORD;
964 private static final int LAST_PRIVATE_MSG_ID = SCROLL_SELECT_TEXT;
965
966 /*
967 * Package message ids
968 */
969 static final int SCROLL_TO_MSG_ID = 101;
970 static final int NEW_PICTURE_MSG_ID = 105;
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800971 static final int WEBCORE_INITIALIZED_MSG_ID = 107;
972 static final int UPDATE_TEXTFIELD_TEXT_MSG_ID = 108;
973 static final int UPDATE_ZOOM_RANGE = 109;
John Reck4fa40372012-03-06 14:31:45 -0800974 static final int TAKE_FOCUS = 110;
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800975 static final int CLEAR_TEXT_ENTRY = 111;
976 static final int UPDATE_TEXT_SELECTION_MSG_ID = 112;
977 static final int SHOW_RECT_MSG_ID = 113;
978 static final int LONG_PRESS_CENTER = 114;
979 static final int PREVENT_TOUCH_ID = 115;
980 static final int WEBCORE_NEED_TOUCH_EVENTS = 116;
981 // obj=Rect in doc coordinates
982 static final int INVAL_RECT_MSG_ID = 117;
983 static final int REQUEST_KEYBOARD = 118;
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800984 static final int SHOW_FULLSCREEN = 120;
985 static final int HIDE_FULLSCREEN = 121;
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800986 static final int REPLACE_BASE_CONTENT = 123;
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800987 static final int UPDATE_MATCH_COUNT = 126;
988 static final int CENTER_FIT_RECT = 127;
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800989 static final int SET_SCROLLBAR_MODES = 129;
990 static final int SELECTION_STRING_CHANGED = 130;
991 static final int HIT_TEST_RESULT = 131;
992 static final int SAVE_WEBARCHIVE_FINISHED = 132;
993
994 static final int SET_AUTOFILLABLE = 133;
995 static final int AUTOFILL_COMPLETE = 134;
996
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800997 static final int SCREEN_ON = 136;
998 static final int ENTER_FULLSCREEN_VIDEO = 137;
Jonathan Dixonded37ed92012-02-13 17:26:46 -0800999 static final int UPDATE_ZOOM_DENSITY = 139;
1000 static final int EXIT_FULLSCREEN_VIDEO = 140;
1001
1002 static final int COPY_TO_CLIPBOARD = 141;
1003 static final int INIT_EDIT_FIELD = 142;
1004 static final int REPLACE_TEXT = 143;
1005 static final int CLEAR_CARET_HANDLE = 144;
1006 static final int KEY_PRESS = 145;
George Mountbcd5dd72012-03-01 08:39:03 -08001007 static final int RELOCATE_AUTO_COMPLETE_POPUP = 146;
1008 static final int FOCUS_NODE_CHANGED = 147;
1009 static final int AUTOFILL_FORM = 148;
George Mount557748d2012-04-04 13:56:49 -07001010 static final int SCROLL_EDIT_TEXT = 149;
George Mountc6c549d2012-03-15 16:39:16 -07001011 static final int EDIT_TEXT_SIZE_CHANGED = 150;
George Mountf796fdf2012-03-19 14:51:55 -07001012 static final int SHOW_CARET_HANDLE = 151;
George Mount7102eb22012-04-10 13:41:51 -07001013 static final int UPDATE_CONTENT_BOUNDS = 152;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001014
1015 private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID;
1016 private static final int LAST_PACKAGE_MSG_ID = HIT_TEST_RESULT;
1017
1018 static final String[] HandlerPrivateDebugString = {
1019 "REMEMBER_PASSWORD", // = 1;
1020 "NEVER_REMEMBER_PASSWORD", // = 2;
1021 "SWITCH_TO_SHORTPRESS", // = 3;
1022 "SWITCH_TO_LONGPRESS", // = 4;
1023 "RELEASE_SINGLE_TAP", // = 5;
1024 "REQUEST_FORM_DATA", // = 6;
1025 "RESUME_WEBCORE_PRIORITY", // = 7;
1026 "DRAG_HELD_MOTIONLESS", // = 8;
1027 "AWAKEN_SCROLL_BARS", // = 9;
1028 "PREVENT_DEFAULT_TIMEOUT", // = 10;
1029 "SCROLL_SELECT_TEXT" // = 11;
1030 };
1031
1032 static final String[] HandlerPackageDebugString = {
1033 "SCROLL_TO_MSG_ID", // = 101;
1034 "102", // = 102;
1035 "103", // = 103;
1036 "104", // = 104;
1037 "NEW_PICTURE_MSG_ID", // = 105;
1038 "UPDATE_TEXT_ENTRY_MSG_ID", // = 106;
1039 "WEBCORE_INITIALIZED_MSG_ID", // = 107;
1040 "UPDATE_TEXTFIELD_TEXT_MSG_ID", // = 108;
1041 "UPDATE_ZOOM_RANGE", // = 109;
1042 "UNHANDLED_NAV_KEY", // = 110;
1043 "CLEAR_TEXT_ENTRY", // = 111;
1044 "UPDATE_TEXT_SELECTION_MSG_ID", // = 112;
1045 "SHOW_RECT_MSG_ID", // = 113;
1046 "LONG_PRESS_CENTER", // = 114;
1047 "PREVENT_TOUCH_ID", // = 115;
1048 "WEBCORE_NEED_TOUCH_EVENTS", // = 116;
1049 "INVAL_RECT_MSG_ID", // = 117;
1050 "REQUEST_KEYBOARD", // = 118;
1051 "DO_MOTION_UP", // = 119;
1052 "SHOW_FULLSCREEN", // = 120;
1053 "HIDE_FULLSCREEN", // = 121;
1054 "DOM_FOCUS_CHANGED", // = 122;
1055 "REPLACE_BASE_CONTENT", // = 123;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001056 "RETURN_LABEL", // = 125;
1057 "UPDATE_MATCH_COUNT", // = 126;
1058 "CENTER_FIT_RECT", // = 127;
1059 "REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID", // = 128;
1060 "SET_SCROLLBAR_MODES", // = 129;
1061 "SELECTION_STRING_CHANGED", // = 130;
1062 "SET_TOUCH_HIGHLIGHT_RECTS", // = 131;
1063 "SAVE_WEBARCHIVE_FINISHED", // = 132;
1064 "SET_AUTOFILLABLE", // = 133;
1065 "AUTOFILL_COMPLETE", // = 134;
1066 "SELECT_AT", // = 135;
1067 "SCREEN_ON", // = 136;
1068 "ENTER_FULLSCREEN_VIDEO", // = 137;
1069 "UPDATE_SELECTION", // = 138;
1070 "UPDATE_ZOOM_DENSITY" // = 139;
1071 };
1072
1073 // If the site doesn't use the viewport meta tag to specify the viewport,
1074 // use DEFAULT_VIEWPORT_WIDTH as the default viewport width
1075 static final int DEFAULT_VIEWPORT_WIDTH = 980;
1076
1077 // normally we try to fit the content to the minimum preferred width
1078 // calculated by the Webkit. To avoid the bad behavior when some site's
1079 // minimum preferred width keeps growing when changing the viewport width or
1080 // the minimum preferred width is huge, an upper limit is needed.
1081 static int sMaxViewportWidth = DEFAULT_VIEWPORT_WIDTH;
1082
1083 // initial scale in percent. 0 means using default.
1084 private int mInitialScaleInPercent = 0;
1085
1086 // Whether or not a scroll event should be sent to webkit. This is only set
1087 // to false when restoring the scroll position.
1088 private boolean mSendScrollEvent = true;
1089
1090 private int mSnapScrollMode = SNAP_NONE;
1091 private static final int SNAP_NONE = 0;
1092 private static final int SNAP_LOCK = 1; // not a separate state
1093 private static final int SNAP_X = 2; // may be combined with SNAP_LOCK
1094 private static final int SNAP_Y = 4; // may be combined with SNAP_LOCK
1095 private boolean mSnapPositive;
1096
1097 // keep these in sync with their counterparts in WebView.cpp
1098 private static final int DRAW_EXTRAS_NONE = 0;
1099 private static final int DRAW_EXTRAS_SELECTION = 1;
1100 private static final int DRAW_EXTRAS_CURSOR_RING = 2;
1101
1102 // keep this in sync with WebCore:ScrollbarMode in WebKit
1103 private static final int SCROLLBAR_AUTO = 0;
1104 private static final int SCROLLBAR_ALWAYSOFF = 1;
1105 // as we auto fade scrollbar, this is ignored.
1106 private static final int SCROLLBAR_ALWAYSON = 2;
1107 private int mHorizontalScrollBarMode = SCROLLBAR_AUTO;
1108 private int mVerticalScrollBarMode = SCROLLBAR_AUTO;
1109
1110 // constants for determining script injection strategy
1111 private static final int ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED = -1;
1112 private static final int ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT = 0;
1113 private static final int ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED = 1;
1114
1115 // the alias via which accessibility JavaScript interface is exposed
1116 private static final String ALIAS_ACCESSIBILITY_JS_INTERFACE = "accessibility";
1117
1118 // Template for JavaScript that injects a screen-reader.
1119 private static final String ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE =
1120 "javascript:(function() {" +
1121 " var chooser = document.createElement('script');" +
1122 " chooser.type = 'text/javascript';" +
1123 " chooser.src = '%1s';" +
1124 " document.getElementsByTagName('head')[0].appendChild(chooser);" +
1125 " })();";
1126
1127 // Regular expression that matches the "axs" URL parameter.
1128 // The value of 0 means the accessibility script is opted out
1129 // The value of 1 means the accessibility script is already injected
1130 private static final String PATTERN_MATCH_AXS_URL_PARAMETER = "(\\?axs=(0|1))|(&axs=(0|1))";
1131
1132 // TextToSpeech instance exposed to JavaScript to the injected screenreader.
1133 private TextToSpeech mTextToSpeech;
1134
1135 // variable to cache the above pattern in case accessibility is enabled.
1136 private Pattern mMatchAxsUrlParameterPattern;
1137
1138 /**
1139 * Max distance to overscroll by in pixels.
1140 * This how far content can be pulled beyond its normal bounds by the user.
1141 */
1142 private int mOverscrollDistance;
1143
1144 /**
1145 * Max distance to overfling by in pixels.
1146 * This is how far flinged content can move beyond the end of its normal bounds.
1147 */
1148 private int mOverflingDistance;
1149
1150 private OverScrollGlow mOverScrollGlow;
1151
1152 // Used to match key downs and key ups
1153 private Vector<Integer> mKeysPressed;
1154
1155 /* package */ static boolean mLogEvent = true;
1156
1157 // for event log
1158 private long mLastTouchUpTime = 0;
1159
1160 private WebViewCore.AutoFillData mAutoFillData;
1161
1162 private static boolean sNotificationsEnabled = true;
1163
1164 /**
1165 * URI scheme for telephone number
1166 */
1167 public static final String SCHEME_TEL = "tel:";
1168 /**
1169 * URI scheme for email address
1170 */
1171 public static final String SCHEME_MAILTO = "mailto:";
1172 /**
1173 * URI scheme for map address
1174 */
1175 public static final String SCHEME_GEO = "geo:0,0?q=";
1176
1177 private int mBackgroundColor = Color.WHITE;
1178
1179 private static final long SELECT_SCROLL_INTERVAL = 1000 / 60; // 60 / second
1180 private int mAutoScrollX = 0;
1181 private int mAutoScrollY = 0;
1182 private int mMinAutoScrollX = 0;
1183 private int mMaxAutoScrollX = 0;
1184 private int mMinAutoScrollY = 0;
1185 private int mMaxAutoScrollY = 0;
1186 private Rect mScrollingLayerBounds = new Rect();
1187 private boolean mSentAutoScrollMessage = false;
1188
1189 // used for serializing asynchronously handled touch events.
Jeff Brown9d3bdbd2012-03-21 11:50:06 -07001190 private WebViewInputDispatcher mInputDispatcher;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001191
1192 // Used to track whether picture updating was paused due to a window focus change.
1193 private boolean mPictureUpdatePausedForFocusChange = false;
1194
1195 // Used to notify listeners of a new picture.
1196 private PictureListener mPictureListener;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001197
Victoria Leaseabeb6a72012-03-05 16:29:12 -08001198 // Used to notify listeners about find-on-page results.
Victoria Leased405a432012-03-22 15:53:48 -07001199 private WebView.FindListener mFindListener;
Victoria Leaseabeb6a72012-03-05 16:29:12 -08001200
Michael Kolbd2bfdfd2012-03-30 13:06:00 -07001201 // Used to prevent resending save password message
1202 private Message mResumeMsg;
1203
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001204 /**
1205 * Refer to {@link WebView#requestFocusNodeHref(Message)} for more information
1206 */
1207 static class FocusNodeHref {
1208 static final String TITLE = "title";
1209 static final String URL = "url";
1210 static final String SRC = "src";
1211 }
1212
Jonathan Dixon3c909522012-02-28 18:45:06 +00001213 public WebViewClassic(WebView webView, WebView.PrivateAccess privateAccess) {
1214 mWebView = webView;
1215 mWebViewPrivate = privateAccess;
1216 mContext = webView.getContext();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001217 }
1218
1219 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00001220 * See {@link WebViewProvider#init(Map, boolean)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001221 */
Jonathan Dixon3c909522012-02-28 18:45:06 +00001222 @Override
1223 public void init(Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00001224 Context context = mContext;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001225
1226 // Used by the chrome stack to find application paths
1227 JniUtil.setContext(context);
1228
1229 mCallbackProxy = new CallbackProxy(context, this);
1230 mViewManager = new ViewManager(this);
1231 L10nUtils.setApplicationContext(context.getApplicationContext());
1232 mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javaScriptInterfaces);
1233 mDatabase = WebViewDatabase.getInstance(context);
1234 mScroller = new OverScroller(context, null, 0, 0, false); //TODO Use OverScroller's flywheel
1235 mZoomManager = new ZoomManager(this, mCallbackProxy);
1236
1237 /* The init method must follow the creation of certain member variables,
1238 * such as the mZoomManager.
1239 */
1240 init();
1241 setupPackageListener(context);
1242 setupProxyListener(context);
1243 setupTrustStorageListener(context);
1244 updateMultiTouchSupport(context);
1245
1246 if (privateBrowsing) {
1247 startPrivateBrowsing();
1248 }
1249
1250 mAutoFillData = new WebViewCore.AutoFillData();
George Mountf70276a2012-03-12 14:22:10 -07001251 mEditTextScroller = new Scroller(context);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001252 }
1253
Jonathan Dixoncd93e152012-03-02 19:19:44 +00001254 // WebViewProvider bindings
Jonathan Dixon3c909522012-02-28 18:45:06 +00001255
1256 static class Factory implements WebViewFactoryProvider, WebViewFactoryProvider.Statics {
1257 @Override
Jonathan Dixon3c909522012-02-28 18:45:06 +00001258 public String findAddress(String addr) {
1259 return WebViewClassic.findAddress(addr);
1260 }
1261 @Override
1262 public void setPlatformNotificationsEnabled(boolean enable) {
1263 if (enable) {
1264 WebViewClassic.enablePlatformNotifications();
1265 } else {
1266 WebViewClassic.disablePlatformNotifications();
1267 }
1268 }
1269
Jonathan Dixond3101b12012-04-12 20:51:51 +01001270 @Override
1271 public Statics getStatics() { return this; }
1272
1273 @Override
1274 public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {
1275 return new WebViewClassic(webView, privateAccess);
1276 }
1277
1278 @Override
1279 public GeolocationPermissions getGeolocationPermissions() {
1280 return GeolocationPermissionsClassic.getInstance();
1281 }
1282
1283 @Override
1284 public CookieManager getCookieManager() {
1285 return CookieManagerClassic.getInstance();
1286 }
1287
1288 @Override
1289 public WebIconDatabase getWebIconDatabase() {
1290 return WebIconDatabaseClassic.getInstance();
1291 }
1292
1293 @Override
1294 public WebStorage getWebStorage() {
1295 return WebStorageClassic.getInstance();
1296 }
Jonathan Dixon3c909522012-02-28 18:45:06 +00001297 }
1298
Jeff Brown9d3bdbd2012-03-21 11:50:06 -07001299 private void onHandleUiEvent(MotionEvent event, int eventType, int flags) {
1300 switch (eventType) {
1301 case WebViewInputDispatcher.EVENT_TYPE_LONG_PRESS:
1302 HitTestResult hitTest = getHitTestResult();
George Mounte3e26c42012-04-19 09:57:23 -07001303 if (hitTest != null) {
Jeff Brown9d3bdbd2012-03-21 11:50:06 -07001304 performLongClick();
1305 }
1306 break;
1307 case WebViewInputDispatcher.EVENT_TYPE_DOUBLE_TAP:
1308 mZoomManager.handleDoubleTap(event.getX(), event.getY());
1309 break;
1310 case WebViewInputDispatcher.EVENT_TYPE_TOUCH:
1311 onHandleUiTouchEvent(event);
1312 break;
1313 }
1314 }
1315
1316 private void onHandleUiTouchEvent(MotionEvent ev) {
1317 final ScaleGestureDetector detector =
1318 mZoomManager.getMultiTouchGestureDetector();
1319
1320 float x = ev.getX();
1321 float y = ev.getY();
1322
1323 if (detector != null) {
1324 detector.onTouchEvent(ev);
1325 if (detector.isInProgress()) {
1326 mLastTouchTime = ev.getEventTime();
1327 x = detector.getFocusX();
1328 y = detector.getFocusY();
1329
1330 mWebView.cancelLongPress();
1331 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
1332 if (!mZoomManager.supportsPanDuringZoom()) {
1333 return;
1334 }
1335 mTouchMode = TOUCH_DRAG_MODE;
1336 if (mVelocityTracker == null) {
1337 mVelocityTracker = VelocityTracker.obtain();
1338 }
1339 }
1340 }
1341
1342 int action = ev.getActionMasked();
1343 if (action == MotionEvent.ACTION_POINTER_DOWN) {
1344 cancelTouch();
1345 action = MotionEvent.ACTION_DOWN;
1346 } else if (action == MotionEvent.ACTION_POINTER_UP && ev.getPointerCount() >= 2) {
1347 // set mLastTouchX/Y to the remaining points for multi-touch.
1348 mLastTouchX = Math.round(x);
1349 mLastTouchY = Math.round(y);
1350 } else if (action == MotionEvent.ACTION_MOVE) {
1351 // negative x or y indicate it is on the edge, skip it.
1352 if (x < 0 || y < 0) {
1353 return;
1354 }
1355 }
1356
1357 handleTouchEventCommon(ev, action, Math.round(x), Math.round(y));
1358 }
1359
Jonathan Dixon3c909522012-02-28 18:45:06 +00001360 // The webview that is bound to this WebViewClassic instance. Primarily needed for supplying
1361 // as the first param in the WebViewClient and WebChromeClient callbacks.
1362 final private WebView mWebView;
1363 // Callback interface, provides priviledged access into the WebView instance.
1364 final private WebView.PrivateAccess mWebViewPrivate;
1365 // Cached reference to mWebView.getContext(), for convenience.
1366 final private Context mContext;
1367
1368 /**
1369 * @return The webview proxy that this classic webview is bound to.
1370 */
1371 public WebView getWebView() {
1372 return mWebView;
1373 }
1374
1375 @Override
1376 public ViewDelegate getViewDelegate() {
1377 return this;
1378 }
1379
1380 @Override
1381 public ScrollDelegate getScrollDelegate() {
1382 return this;
1383 }
1384
1385 public static WebViewClassic fromWebView(WebView webView) {
1386 return webView == null ? null : (WebViewClassic) webView.getWebViewProvider();
1387 }
1388
1389 // Accessors, purely for convenience (and to reduce code churn during webview proxy migration).
1390 int getScrollX() {
1391 return mWebView.getScrollX();
1392 }
1393
1394 int getScrollY() {
1395 return mWebView.getScrollY();
1396 }
1397
1398 int getWidth() {
1399 return mWebView.getWidth();
1400 }
1401
1402 int getHeight() {
1403 return mWebView.getHeight();
1404 }
1405
1406 Context getContext() {
1407 return mContext;
1408 }
1409
1410 void invalidate() {
1411 mWebView.invalidate();
1412 }
1413
1414 // Setters for the Scroll X & Y, without invoking the onScrollChanged etc code paths.
1415 void setScrollXRaw(int mScrollX) {
1416 mWebViewPrivate.setScrollXRaw(mScrollX);
1417 }
1418
1419 void setScrollYRaw(int mScrollY) {
1420 mWebViewPrivate.setScrollYRaw(mScrollY);
1421 }
1422
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001423 private static class TrustStorageListener extends BroadcastReceiver {
1424 @Override
1425 public void onReceive(Context context, Intent intent) {
1426 if (intent.getAction().equals(KeyChain.ACTION_STORAGE_CHANGED)) {
1427 handleCertTrustChanged();
1428 }
1429 }
1430 }
1431 private static TrustStorageListener sTrustStorageListener;
1432
1433 /**
1434 * Handles update to the trust storage.
1435 */
1436 private static void handleCertTrustChanged() {
1437 // send a message for indicating trust storage change
1438 WebViewCore.sendStaticMessage(EventHub.TRUST_STORAGE_UPDATED, null);
1439 }
1440
1441 /*
1442 * @param context This method expects this to be a valid context.
1443 */
1444 private static void setupTrustStorageListener(Context context) {
1445 if (sTrustStorageListener != null ) {
1446 return;
1447 }
1448 IntentFilter filter = new IntentFilter();
1449 filter.addAction(KeyChain.ACTION_STORAGE_CHANGED);
1450 sTrustStorageListener = new TrustStorageListener();
1451 Intent current =
1452 context.getApplicationContext().registerReceiver(sTrustStorageListener, filter);
1453 if (current != null) {
1454 handleCertTrustChanged();
1455 }
1456 }
1457
1458 private static class ProxyReceiver extends BroadcastReceiver {
1459 @Override
1460 public void onReceive(Context context, Intent intent) {
1461 if (intent.getAction().equals(Proxy.PROXY_CHANGE_ACTION)) {
1462 handleProxyBroadcast(intent);
1463 }
1464 }
1465 }
1466
1467 /*
1468 * Receiver for PROXY_CHANGE_ACTION, will be null when it is not added handling broadcasts.
1469 */
1470 private static ProxyReceiver sProxyReceiver;
1471
1472 /*
1473 * @param context This method expects this to be a valid context
1474 */
1475 private static synchronized void setupProxyListener(Context context) {
1476 if (sProxyReceiver != null || sNotificationsEnabled == false) {
1477 return;
1478 }
1479 IntentFilter filter = new IntentFilter();
1480 filter.addAction(Proxy.PROXY_CHANGE_ACTION);
1481 sProxyReceiver = new ProxyReceiver();
1482 Intent currentProxy = context.getApplicationContext().registerReceiver(
1483 sProxyReceiver, filter);
1484 if (currentProxy != null) {
1485 handleProxyBroadcast(currentProxy);
1486 }
1487 }
1488
1489 /*
1490 * @param context This method expects this to be a valid context
1491 */
1492 private static synchronized void disableProxyListener(Context context) {
1493 if (sProxyReceiver == null)
1494 return;
1495
1496 context.getApplicationContext().unregisterReceiver(sProxyReceiver);
1497 sProxyReceiver = null;
1498 }
1499
1500 private static void handleProxyBroadcast(Intent intent) {
1501 ProxyProperties proxyProperties = (ProxyProperties)intent.getExtra(Proxy.EXTRA_PROXY_INFO);
1502 if (proxyProperties == null || proxyProperties.getHost() == null) {
1503 WebViewCore.sendStaticMessage(EventHub.PROXY_CHANGED, null);
1504 return;
1505 }
1506 WebViewCore.sendStaticMessage(EventHub.PROXY_CHANGED, proxyProperties);
1507 }
1508
1509 /*
1510 * A variable to track if there is a receiver added for ACTION_PACKAGE_ADDED
1511 * or ACTION_PACKAGE_REMOVED.
1512 */
1513 private static boolean sPackageInstallationReceiverAdded = false;
1514
1515 /*
1516 * A set of Google packages we monitor for the
1517 * navigator.isApplicationInstalled() API. Add additional packages as
1518 * needed.
1519 */
1520 private static Set<String> sGoogleApps;
1521 static {
1522 sGoogleApps = new HashSet<String>();
1523 sGoogleApps.add("com.google.android.youtube");
1524 }
1525
1526 private static class PackageListener extends BroadcastReceiver {
1527 @Override
1528 public void onReceive(Context context, Intent intent) {
1529 final String action = intent.getAction();
1530 final String packageName = intent.getData().getSchemeSpecificPart();
1531 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
1532 if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) {
1533 // if it is replacing, refreshPlugins() when adding
1534 return;
1535 }
1536
1537 if (sGoogleApps.contains(packageName)) {
1538 if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
1539 WebViewCore.sendStaticMessage(EventHub.ADD_PACKAGE_NAME, packageName);
1540 } else {
1541 WebViewCore.sendStaticMessage(EventHub.REMOVE_PACKAGE_NAME, packageName);
1542 }
1543 }
1544
1545 PluginManager pm = PluginManager.getInstance(context);
1546 if (pm.containsPluginPermissionAndSignatures(packageName)) {
1547 pm.refreshPlugins(Intent.ACTION_PACKAGE_ADDED.equals(action));
1548 }
1549 }
1550 }
1551
1552 private void setupPackageListener(Context context) {
1553
1554 /*
1555 * we must synchronize the instance check and the creation of the
1556 * receiver to ensure that only ONE receiver exists for all WebView
1557 * instances.
1558 */
Jonathan Dixon3c909522012-02-28 18:45:06 +00001559 synchronized (WebViewClassic.class) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001560
1561 // if the receiver already exists then we do not need to register it
1562 // again
1563 if (sPackageInstallationReceiverAdded) {
1564 return;
1565 }
1566
1567 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
1568 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
1569 filter.addDataScheme("package");
1570 BroadcastReceiver packageListener = new PackageListener();
1571 context.getApplicationContext().registerReceiver(packageListener, filter);
1572 sPackageInstallationReceiverAdded = true;
1573 }
1574
1575 // check if any of the monitored apps are already installed
1576 AsyncTask<Void, Void, Set<String>> task = new AsyncTask<Void, Void, Set<String>>() {
1577
1578 @Override
1579 protected Set<String> doInBackground(Void... unused) {
1580 Set<String> installedPackages = new HashSet<String>();
1581 PackageManager pm = mContext.getPackageManager();
1582 for (String name : sGoogleApps) {
1583 try {
1584 pm.getPackageInfo(name,
1585 PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
1586 installedPackages.add(name);
1587 } catch (PackageManager.NameNotFoundException e) {
1588 // package not found
1589 }
1590 }
1591 return installedPackages;
1592 }
1593
1594 // Executes on the UI thread
1595 @Override
1596 protected void onPostExecute(Set<String> installedPackages) {
1597 if (mWebViewCore != null) {
1598 mWebViewCore.sendMessage(EventHub.ADD_PACKAGE_NAMES, installedPackages);
1599 }
1600 }
1601 };
1602 task.execute();
1603 }
1604
1605 void updateMultiTouchSupport(Context context) {
1606 mZoomManager.updateMultiTouchSupport(context);
1607 }
1608
1609 private void init() {
Jonathan Dixon3c909522012-02-28 18:45:06 +00001610 OnTrimMemoryListener.init(mContext);
Jonathan Dixon3c909522012-02-28 18:45:06 +00001611 mWebView.setWillNotDraw(false);
1612 mWebView.setFocusable(true);
1613 mWebView.setFocusableInTouchMode(true);
1614 mWebView.setClickable(true);
1615 mWebView.setLongClickable(true);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001616
Jonathan Dixon3c909522012-02-28 18:45:06 +00001617 final ViewConfiguration configuration = ViewConfiguration.get(mContext);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001618 int slop = configuration.getScaledTouchSlop();
1619 mTouchSlopSquare = slop * slop;
1620 slop = configuration.getScaledDoubleTapSlop();
1621 mDoubleTapSlopSquare = slop * slop;
Jonathan Dixon3c909522012-02-28 18:45:06 +00001622 final float density = mContext.getResources().getDisplayMetrics().density;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001623 // use one line height, 16 based on our current default font, for how
1624 // far we allow a touch be away from the edge of a link
1625 mNavSlop = (int) (16 * density);
1626 mZoomManager.init(density);
1627 mMaximumFling = configuration.getScaledMaximumFlingVelocity();
1628
1629 // Compute the inverse of the density squared.
1630 DRAG_LAYER_INVERSE_DENSITY_SQUARED = 1 / (density * density);
1631
1632 mOverscrollDistance = configuration.getScaledOverscrollDistance();
1633 mOverflingDistance = configuration.getScaledOverflingDistance();
1634
Jonathan Dixon3c909522012-02-28 18:45:06 +00001635 setScrollBarStyle(mWebViewPrivate.super_getScrollBarStyle());
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001636 // Initially use a size of two, since the user is likely to only hold
1637 // down two keys at a time (shift + another key)
1638 mKeysPressed = new Vector<Integer>(2);
1639 mHTML5VideoViewProxy = null ;
1640 }
1641
1642 @Override
1643 public boolean shouldDelayChildPressedState() {
1644 return true;
1645 }
1646
1647 /**
1648 * Adds accessibility APIs to JavaScript.
1649 *
1650 * Note: This method is responsible to performing the necessary
1651 * check if the accessibility APIs should be exposed.
1652 */
1653 private void addAccessibilityApisToJavaScript() {
1654 if (AccessibilityManager.getInstance(mContext).isEnabled()
1655 && getSettings().getJavaScriptEnabled()) {
1656 // exposing the TTS for now ...
Jonathan Dixon3c909522012-02-28 18:45:06 +00001657 final Context ctx = mContext;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001658 if (ctx != null) {
1659 final String packageName = ctx.getPackageName();
1660 if (packageName != null) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00001661 mTextToSpeech = new TextToSpeech(ctx, null, null,
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001662 packageName + ".**webview**", true);
1663 addJavascriptInterface(mTextToSpeech, ALIAS_ACCESSIBILITY_JS_INTERFACE);
1664 }
1665 }
1666 }
1667 }
1668
1669 /**
1670 * Removes accessibility APIs from JavaScript.
1671 */
1672 private void removeAccessibilityApisFromJavaScript() {
1673 // exposing the TTS for now ...
1674 if (mTextToSpeech != null) {
1675 removeJavascriptInterface(ALIAS_ACCESSIBILITY_JS_INTERFACE);
1676 mTextToSpeech.shutdown();
1677 mTextToSpeech = null;
1678 }
1679 }
1680
1681 @Override
1682 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001683 info.setScrollable(isScrollableForAccessibility());
1684 }
1685
1686 @Override
1687 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001688 event.setScrollable(isScrollableForAccessibility());
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00001689 event.setScrollX(getScrollX());
1690 event.setScrollY(getScrollY());
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001691 final int convertedContentWidth = contentToViewX(getContentWidth());
Jonathan Dixon3c909522012-02-28 18:45:06 +00001692 final int adjustedViewWidth = getWidth() - mWebView.getPaddingLeft()
1693 - mWebView.getPaddingLeft();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001694 event.setMaxScrollX(Math.max(convertedContentWidth - adjustedViewWidth, 0));
1695 final int convertedContentHeight = contentToViewY(getContentHeight());
Jonathan Dixon3c909522012-02-28 18:45:06 +00001696 final int adjustedViewHeight = getHeight() - mWebView.getPaddingTop()
1697 - mWebView.getPaddingBottom();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001698 event.setMaxScrollY(Math.max(convertedContentHeight - adjustedViewHeight, 0));
1699 }
1700
1701 private boolean isScrollableForAccessibility() {
Jonathan Dixon3c909522012-02-28 18:45:06 +00001702 return (contentToViewX(getContentWidth()) > getWidth() - mWebView.getPaddingLeft()
1703 - mWebView.getPaddingRight()
1704 || contentToViewY(getContentHeight()) > getHeight() - mWebView.getPaddingTop()
1705 - mWebView.getPaddingBottom());
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001706 }
1707
1708 @Override
1709 public void setOverScrollMode(int mode) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00001710 if (mode != View.OVER_SCROLL_NEVER) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001711 if (mOverScrollGlow == null) {
1712 mOverScrollGlow = new OverScrollGlow(this);
1713 }
1714 } else {
1715 mOverScrollGlow = null;
1716 }
1717 }
1718
1719 /* package */ void adjustDefaultZoomDensity(int zoomDensity) {
1720 final float density = mContext.getResources().getDisplayMetrics().density
1721 * 100 / zoomDensity;
1722 updateDefaultZoomDensity(density);
1723 }
1724
1725 /* package */ void updateDefaultZoomDensity(float density) {
1726 mNavSlop = (int) (16 * density);
1727 mZoomManager.updateDefaultZoomDensity(density);
1728 }
1729
1730 /* package */ boolean onSavePassword(String schemePlusHost, String username,
1731 String password, final Message resumeMsg) {
Michael Kolbd2bfdfd2012-03-30 13:06:00 -07001732 boolean rVal = false;
1733 if (resumeMsg == null) {
1734 // null resumeMsg implies saving password silently
1735 mDatabase.setUsernamePassword(schemePlusHost, username, password);
1736 } else {
1737 if (mResumeMsg != null) {
1738 Log.w(LOGTAG, "onSavePassword should not be called while dialog is up");
1739 resumeMsg.sendToTarget();
1740 return true;
1741 }
1742 mResumeMsg = resumeMsg;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001743 final Message remember = mPrivateHandler.obtainMessage(
1744 REMEMBER_PASSWORD);
1745 remember.getData().putString("host", schemePlusHost);
1746 remember.getData().putString("username", username);
1747 remember.getData().putString("password", password);
1748 remember.obj = resumeMsg;
1749
1750 final Message neverRemember = mPrivateHandler.obtainMessage(
1751 NEVER_REMEMBER_PASSWORD);
1752 neverRemember.getData().putString("host", schemePlusHost);
1753 neverRemember.getData().putString("username", username);
1754 neverRemember.getData().putString("password", password);
1755 neverRemember.obj = resumeMsg;
1756
Jonathan Dixon3c909522012-02-28 18:45:06 +00001757 new AlertDialog.Builder(mContext)
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001758 .setTitle(com.android.internal.R.string.save_password_label)
1759 .setMessage(com.android.internal.R.string.save_password_message)
1760 .setPositiveButton(com.android.internal.R.string.save_password_notnow,
1761 new DialogInterface.OnClickListener() {
1762 @Override
1763 public void onClick(DialogInterface dialog, int which) {
Michael Kolbd2bfdfd2012-03-30 13:06:00 -07001764 if (mResumeMsg != null) {
1765 resumeMsg.sendToTarget();
1766 mResumeMsg = null;
1767 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001768 }
1769 })
1770 .setNeutralButton(com.android.internal.R.string.save_password_remember,
1771 new DialogInterface.OnClickListener() {
1772 @Override
1773 public void onClick(DialogInterface dialog, int which) {
Michael Kolbd2bfdfd2012-03-30 13:06:00 -07001774 if (mResumeMsg != null) {
1775 remember.sendToTarget();
1776 mResumeMsg = null;
1777 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001778 }
1779 })
1780 .setNegativeButton(com.android.internal.R.string.save_password_never,
1781 new DialogInterface.OnClickListener() {
1782 @Override
1783 public void onClick(DialogInterface dialog, int which) {
Michael Kolbd2bfdfd2012-03-30 13:06:00 -07001784 if (mResumeMsg != null) {
1785 neverRemember.sendToTarget();
1786 mResumeMsg = null;
1787 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001788 }
1789 })
1790 .setOnCancelListener(new OnCancelListener() {
1791 @Override
1792 public void onCancel(DialogInterface dialog) {
Michael Kolbd2bfdfd2012-03-30 13:06:00 -07001793 if (mResumeMsg != null) {
1794 resumeMsg.sendToTarget();
1795 mResumeMsg = null;
1796 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001797 }
1798 }).show();
1799 // Return true so that WebViewCore will pause while the dialog is
1800 // up.
1801 rVal = true;
1802 }
Michael Kolbd2bfdfd2012-03-30 13:06:00 -07001803 return rVal;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001804 }
1805
1806 @Override
1807 public void setScrollBarStyle(int style) {
1808 if (style == View.SCROLLBARS_INSIDE_INSET
1809 || style == View.SCROLLBARS_OUTSIDE_INSET) {
1810 mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = false;
1811 } else {
1812 mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = true;
1813 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001814 }
1815
1816 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00001817 * See {@link WebView#setHorizontalScrollbarOverlay(boolean)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001818 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00001819 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001820 public void setHorizontalScrollbarOverlay(boolean overlay) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001821 mOverlayHorizontalScrollbar = overlay;
1822 }
1823
1824 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00001825 * See {@link WebView#setVerticalScrollbarOverlay(boolean)
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001826 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00001827 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001828 public void setVerticalScrollbarOverlay(boolean overlay) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001829 mOverlayVerticalScrollbar = overlay;
1830 }
1831
1832 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00001833 * See {@link WebView#overlayHorizontalScrollbar()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001834 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00001835 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001836 public boolean overlayHorizontalScrollbar() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001837 return mOverlayHorizontalScrollbar;
1838 }
1839
1840 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00001841 * See {@link WebView#overlayVerticalScrollbar()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001842 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00001843 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001844 public boolean overlayVerticalScrollbar() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001845 return mOverlayVerticalScrollbar;
1846 }
1847
1848 /*
1849 * Return the width of the view where the content of WebView should render
1850 * to.
1851 * Note: this can be called from WebCoreThread.
1852 */
1853 /* package */ int getViewWidth() {
Jonathan Dixon3c909522012-02-28 18:45:06 +00001854 if (!mWebView.isVerticalScrollBarEnabled() || mOverlayVerticalScrollbar) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001855 return getWidth();
1856 } else {
Jonathan Dixon3c909522012-02-28 18:45:06 +00001857 return Math.max(0, getWidth() - mWebView.getVerticalScrollbarWidth());
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001858 }
1859 }
1860
Jonathan Dixon3c909522012-02-28 18:45:06 +00001861 // Interface to enable the browser to override title bar handling.
1862 public interface TitleBarDelegate {
1863 int getTitleHeight();
1864 public void onSetEmbeddedTitleBar(final View title);
1865 }
1866
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001867 /**
1868 * Returns the height (in pixels) of the embedded title bar (if any). Does not care about
1869 * scrolling
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001870 */
1871 protected int getTitleHeight() {
Jonathan Dixon3c909522012-02-28 18:45:06 +00001872 if (mWebView instanceof TitleBarDelegate) {
1873 return ((TitleBarDelegate) mWebView).getTitleHeight();
1874 }
Michael Kolb8116da52012-04-02 16:16:16 -07001875 return 0;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001876 }
1877
1878 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00001879 * See {@link WebView#getVisibleTitleHeight()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001880 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00001881 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001882 @Deprecated
1883 public int getVisibleTitleHeight() {
1884 // Actually, this method returns the height of the embedded title bar if one is set via the
1885 // hidden setEmbeddedTitleBar method.
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001886 return getVisibleTitleHeightImpl();
1887 }
1888
1889 private int getVisibleTitleHeightImpl() {
1890 // need to restrict mScrollY due to over scroll
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00001891 return Math.max(getTitleHeight() - Math.max(0, getScrollY()),
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001892 getOverlappingActionModeHeight());
1893 }
1894
1895 private int mCachedOverlappingActionModeHeight = -1;
1896
1897 private int getOverlappingActionModeHeight() {
1898 if (mFindCallback == null) {
1899 return 0;
1900 }
1901 if (mCachedOverlappingActionModeHeight < 0) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00001902 mWebView.getGlobalVisibleRect(mGlobalVisibleRect, mGlobalVisibleOffset);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001903 mCachedOverlappingActionModeHeight = Math.max(0,
1904 mFindCallback.getActionModeGlobalBottom() - mGlobalVisibleRect.top);
1905 }
1906 return mCachedOverlappingActionModeHeight;
1907 }
1908
1909 /*
1910 * Return the height of the view where the content of WebView should render
1911 * to. Note that this excludes mTitleBar, if there is one.
1912 * Note: this can be called from WebCoreThread.
1913 */
1914 /* package */ int getViewHeight() {
1915 return getViewHeightWithTitle() - getVisibleTitleHeightImpl();
1916 }
1917
1918 int getViewHeightWithTitle() {
1919 int height = getHeight();
Jonathan Dixon3c909522012-02-28 18:45:06 +00001920 if (mWebView.isHorizontalScrollBarEnabled() && !mOverlayHorizontalScrollbar) {
1921 height -= mWebViewPrivate.getHorizontalScrollbarHeight();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001922 }
1923 return height;
1924 }
1925
1926 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00001927 * See {@link WebView#getCertificate()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001928 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00001929 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001930 public SslCertificate getCertificate() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001931 return mCertificate;
1932 }
1933
1934 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00001935 * See {@link WebView#setCertificate(SslCertificate)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001936 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00001937 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001938 public void setCertificate(SslCertificate certificate) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001939 if (DebugFlags.WEB_VIEW) {
1940 Log.v(LOGTAG, "setCertificate=" + certificate);
1941 }
1942 // here, the certificate can be null (if the site is not secure)
1943 mCertificate = certificate;
1944 }
1945
1946 //-------------------------------------------------------------------------
1947 // Methods called by activity
1948 //-------------------------------------------------------------------------
1949
1950 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00001951 * See {@link WebView#savePassword(String, String, String)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001952 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00001953 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001954 public void savePassword(String host, String username, String password) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001955 mDatabase.setUsernamePassword(host, username, password);
1956 }
1957
1958 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00001959 * See {@link WebView#setHttpAuthUsernamePassword(String, String, String, String)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001960 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00001961 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001962 public void setHttpAuthUsernamePassword(String host, String realm,
1963 String username, String password) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001964 mDatabase.setHttpAuthUsernamePassword(host, realm, username, password);
1965 }
1966
1967 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00001968 * See {@link WebView#getHttpAuthUsernamePassword(String, String)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001969 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00001970 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001971 public String[] getHttpAuthUsernamePassword(String host, String realm) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001972 return mDatabase.getHttpAuthUsernamePassword(host, realm);
1973 }
1974
1975 /**
1976 * Remove Find or Select ActionModes, if active.
1977 */
1978 private void clearActionModes() {
1979 if (mSelectCallback != null) {
1980 mSelectCallback.finish();
1981 }
1982 if (mFindCallback != null) {
1983 mFindCallback.finish();
1984 }
1985 }
1986
1987 /**
1988 * Called to clear state when moving from one page to another, or changing
1989 * in some other way that makes elements associated with the current page
John Reckb2676f72012-03-02 14:13:06 -08001990 * (such as ActionModes) no longer relevant.
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001991 */
1992 private void clearHelpers() {
John Reckb2676f72012-03-02 14:13:06 -08001993 hideSoftKeyboard();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08001994 clearActionModes();
1995 dismissFullScreenMode();
1996 cancelSelectDialog();
1997 }
1998
1999 private void cancelSelectDialog() {
2000 if (mListBoxDialog != null) {
2001 mListBoxDialog.cancel();
2002 mListBoxDialog = null;
2003 }
2004 }
2005
2006 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002007 * See {@link WebView#destroy()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002008 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002009 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002010 public void destroy() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002011 destroyImpl();
2012 }
2013
2014 private void destroyImpl() {
Michael Kolbd78ad292012-03-09 09:38:30 -08002015 mCallbackProxy.blockMessages();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002016 clearHelpers();
2017 if (mListBoxDialog != null) {
2018 mListBoxDialog.dismiss();
2019 mListBoxDialog = null;
2020 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002021 if (mNativeClass != 0) nativeStopGL();
2022 if (mWebViewCore != null) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002023 // Tell WebViewCore to destroy itself
2024 synchronized (this) {
2025 WebViewCore webViewCore = mWebViewCore;
2026 mWebViewCore = null; // prevent using partial webViewCore
2027 webViewCore.destroy();
2028 }
2029 // Remove any pending messages that might not be serviced yet.
2030 mPrivateHandler.removeCallbacksAndMessages(null);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002031 }
2032 if (mNativeClass != 0) {
2033 nativeDestroy();
2034 mNativeClass = 0;
2035 }
2036 }
2037
2038 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002039 * See {@link WebView#enablePlatformNotifications()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002040 */
2041 @Deprecated
2042 public static void enablePlatformNotifications() {
Jonathan Dixon3c909522012-02-28 18:45:06 +00002043 synchronized (WebViewClassic.class) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002044 sNotificationsEnabled = true;
2045 Context context = JniUtil.getContext();
2046 if (context != null)
2047 setupProxyListener(context);
2048 }
2049 }
2050
2051 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002052 * See {@link WebView#disablePlatformNotifications()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002053 */
2054 @Deprecated
2055 public static void disablePlatformNotifications() {
Jonathan Dixon3c909522012-02-28 18:45:06 +00002056 synchronized (WebViewClassic.class) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002057 sNotificationsEnabled = false;
2058 Context context = JniUtil.getContext();
2059 if (context != null)
2060 disableProxyListener(context);
2061 }
2062 }
2063
2064 /**
2065 * Sets JavaScript engine flags.
2066 *
2067 * @param flags JS engine flags in a String
2068 *
Jonathan Dixoncd93e152012-03-02 19:19:44 +00002069 * This is an implementation detail.
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002070 */
2071 public void setJsFlags(String flags) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002072 mWebViewCore.sendMessage(EventHub.SET_JS_FLAGS, flags);
2073 }
2074
2075 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002076 * See {@link WebView#setNetworkAvailable(boolean)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002077 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002078 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002079 public void setNetworkAvailable(boolean networkUp) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002080 mWebViewCore.sendMessage(EventHub.SET_NETWORK_STATE,
2081 networkUp ? 1 : 0, 0);
2082 }
2083
2084 /**
2085 * Inform WebView about the current network type.
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002086 */
2087 public void setNetworkType(String type, String subtype) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002088 Map<String, String> map = new HashMap<String, String>();
2089 map.put("type", type);
2090 map.put("subtype", subtype);
2091 mWebViewCore.sendMessage(EventHub.SET_NETWORK_TYPE, map);
2092 }
Jonathan Dixon19b80112012-03-02 18:18:00 +00002093
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002094 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002095 * See {@link WebView#saveState(Bundle)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002096 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002097 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002098 public WebBackForwardList saveState(Bundle outState) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002099 if (outState == null) {
2100 return null;
2101 }
2102 // We grab a copy of the back/forward list because a client of WebView
2103 // may have invalidated the history list by calling clearHistory.
2104 WebBackForwardList list = copyBackForwardList();
2105 final int currentIndex = list.getCurrentIndex();
2106 final int size = list.getSize();
2107 // We should fail saving the state if the list is empty or the index is
2108 // not in a valid range.
2109 if (currentIndex < 0 || currentIndex >= size || size == 0) {
2110 return null;
2111 }
2112 outState.putInt("index", currentIndex);
2113 // FIXME: This should just be a byte[][] instead of ArrayList but
2114 // Parcel.java does not have the code to handle multi-dimensional
2115 // arrays.
2116 ArrayList<byte[]> history = new ArrayList<byte[]>(size);
2117 for (int i = 0; i < size; i++) {
2118 WebHistoryItem item = list.getItemAtIndex(i);
2119 if (null == item) {
2120 // FIXME: this shouldn't happen
2121 // need to determine how item got set to null
2122 Log.w(LOGTAG, "saveState: Unexpected null history item.");
2123 return null;
2124 }
2125 byte[] data = item.getFlattenedData();
2126 if (data == null) {
2127 // It would be very odd to not have any data for a given history
2128 // item. And we will fail to rebuild the history list without
2129 // flattened data.
2130 return null;
2131 }
2132 history.add(data);
2133 }
2134 outState.putSerializable("history", history);
2135 if (mCertificate != null) {
2136 outState.putBundle("certificate",
2137 SslCertificate.saveState(mCertificate));
2138 }
2139 outState.putBoolean("privateBrowsingEnabled", isPrivateBrowsingEnabled());
2140 mZoomManager.saveZoomState(outState);
2141 return list;
2142 }
2143
2144 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002145 * See {@link WebView#savePicture(Bundle, File)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002146 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002147 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002148 @Deprecated
2149 public boolean savePicture(Bundle b, final File dest) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002150 if (dest == null || b == null) {
2151 return false;
2152 }
2153 final Picture p = capturePicture();
2154 // Use a temporary file while writing to ensure the destination file
2155 // contains valid data.
2156 final File temp = new File(dest.getPath() + ".writing");
2157 new Thread(new Runnable() {
2158 @Override
2159 public void run() {
2160 FileOutputStream out = null;
2161 try {
2162 out = new FileOutputStream(temp);
2163 p.writeToStream(out);
2164 // Writing the picture succeeded, rename the temporary file
2165 // to the destination.
2166 temp.renameTo(dest);
2167 } catch (Exception e) {
2168 // too late to do anything about it.
2169 } finally {
2170 if (out != null) {
2171 try {
2172 out.close();
2173 } catch (Exception e) {
2174 // Can't do anything about that
2175 }
2176 }
2177 temp.delete();
2178 }
2179 }
2180 }).start();
2181 // now update the bundle
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00002182 b.putInt("scrollX", getScrollX());
2183 b.putInt("scrollY", getScrollY());
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002184 mZoomManager.saveZoomState(b);
2185 return true;
2186 }
2187
2188 private void restoreHistoryPictureFields(Picture p, Bundle b) {
2189 int sx = b.getInt("scrollX", 0);
2190 int sy = b.getInt("scrollY", 0);
2191
2192 mDrawHistory = true;
2193 mHistoryPicture = p;
2194
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00002195 setScrollXRaw(sx);
2196 setScrollYRaw(sy);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002197 mZoomManager.restoreZoomState(b);
2198 final float scale = mZoomManager.getScale();
2199 mHistoryWidth = Math.round(p.getWidth() * scale);
2200 mHistoryHeight = Math.round(p.getHeight() * scale);
2201
2202 invalidate();
2203 }
2204
2205 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002206 * See {@link WebView#restorePicture(Bundle, File)};
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002207 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002208 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002209 @Deprecated
2210 public boolean restorePicture(Bundle b, File src) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002211 if (src == null || b == null) {
2212 return false;
2213 }
2214 if (!src.exists()) {
2215 return false;
2216 }
2217 try {
2218 final FileInputStream in = new FileInputStream(src);
2219 final Bundle copy = new Bundle(b);
2220 new Thread(new Runnable() {
2221 @Override
2222 public void run() {
2223 try {
2224 final Picture p = Picture.createFromStream(in);
2225 if (p != null) {
2226 // Post a runnable on the main thread to update the
2227 // history picture fields.
2228 mPrivateHandler.post(new Runnable() {
2229 @Override
2230 public void run() {
2231 restoreHistoryPictureFields(p, copy);
2232 }
2233 });
2234 }
2235 } finally {
2236 try {
2237 in.close();
2238 } catch (Exception e) {
2239 // Nothing we can do now.
2240 }
2241 }
2242 }
2243 }).start();
2244 } catch (FileNotFoundException e){
2245 e.printStackTrace();
2246 }
2247 return true;
2248 }
2249
2250 /**
2251 * Saves the view data to the output stream. The output is highly
2252 * version specific, and may not be able to be loaded by newer versions
2253 * of WebView.
2254 * @param stream The {@link OutputStream} to save to
John Reckee3b5622012-04-19 15:27:58 -07002255 * @param callback The {@link ValueCallback} to call with the result
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002256 */
John Reckee3b5622012-04-19 15:27:58 -07002257 public void saveViewState(OutputStream stream, ValueCallback<Boolean> callback) {
2258 if (mWebViewCore == null) {
2259 callback.onReceiveValue(false);
2260 return;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002261 }
John Reckee3b5622012-04-19 15:27:58 -07002262 mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SAVE_VIEW_STATE,
2263 new WebViewCore.SaveViewStateRequest(stream, callback));
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002264 }
2265
2266 /**
2267 * Loads the view data from the input stream. See
Romain Guyba6be8a2012-04-23 18:22:09 -07002268 * {@link #saveViewState(java.io.OutputStream, ValueCallback)} for more information.
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002269 * @param stream The {@link InputStream} to load from
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002270 */
John Reckee3b5622012-04-19 15:27:58 -07002271 public void loadViewState(InputStream stream) {
2272 mBlockWebkitViewMessages = true;
2273 new AsyncTask<InputStream, Void, DrawData>() {
2274
2275 @Override
2276 protected DrawData doInBackground(InputStream... params) {
2277 try {
2278 return ViewStateSerializer.deserializeViewState(params[0]);
2279 } catch (IOException e) {
2280 return null;
2281 }
2282 }
2283
2284 @Override
2285 protected void onPostExecute(DrawData draw) {
2286 if (draw == null) {
2287 Log.e(LOGTAG, "Failed to load view state!");
2288 return;
2289 }
2290 int viewWidth = getViewWidth();
2291 int viewHeight = getViewHeightWithTitle() - getTitleHeight();
2292 draw.mViewSize = new Point(viewWidth, viewHeight);
2293 draw.mViewState.mDefaultScale = getDefaultZoomScale();
2294 mLoadedPicture = draw;
2295 setNewPicture(mLoadedPicture, true);
2296 mLoadedPicture.mViewState = null;
2297 }
2298
2299 }.execute(stream);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002300 }
2301
2302 /**
2303 * Clears the view state set with {@link #loadViewState(InputStream)}.
2304 * This WebView will then switch to showing the content from webkit
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002305 */
2306 public void clearViewState() {
2307 mBlockWebkitViewMessages = false;
2308 mLoadedPicture = null;
2309 invalidate();
2310 }
2311
2312 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002313 * See {@link WebView#restoreState(Bundle)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002314 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002315 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002316 public WebBackForwardList restoreState(Bundle inState) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002317 WebBackForwardList returnList = null;
2318 if (inState == null) {
2319 return returnList;
2320 }
2321 if (inState.containsKey("index") && inState.containsKey("history")) {
2322 mCertificate = SslCertificate.restoreState(
2323 inState.getBundle("certificate"));
2324
2325 final WebBackForwardList list = mCallbackProxy.getBackForwardList();
2326 final int index = inState.getInt("index");
2327 // We can't use a clone of the list because we need to modify the
2328 // shared copy, so synchronize instead to prevent concurrent
2329 // modifications.
2330 synchronized (list) {
2331 final List<byte[]> history =
2332 (List<byte[]>) inState.getSerializable("history");
2333 final int size = history.size();
2334 // Check the index bounds so we don't crash in native code while
2335 // restoring the history index.
2336 if (index < 0 || index >= size) {
2337 return null;
2338 }
2339 for (int i = 0; i < size; i++) {
2340 byte[] data = history.remove(0);
2341 if (data == null) {
2342 // If we somehow have null data, we cannot reconstruct
2343 // the item and thus our history list cannot be rebuilt.
2344 return null;
2345 }
2346 WebHistoryItem item = new WebHistoryItem(data);
2347 list.addHistoryItem(item);
2348 }
2349 // Grab the most recent copy to return to the caller.
2350 returnList = copyBackForwardList();
2351 // Update the copy to have the correct index.
2352 returnList.setCurrentIndex(index);
2353 }
2354 // Restore private browsing setting.
2355 if (inState.getBoolean("privateBrowsingEnabled")) {
2356 getSettings().setPrivateBrowsingEnabled(true);
2357 }
2358 mZoomManager.restoreZoomState(inState);
2359 // Remove all pending messages because we are restoring previous
2360 // state.
2361 mWebViewCore.removeMessages();
2362 // Send a restore state message.
2363 mWebViewCore.sendMessage(EventHub.RESTORE_STATE, index);
2364 }
2365 return returnList;
2366 }
2367
2368 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002369 * See {@link WebView#loadUrl(String, Map)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002370 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002371 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002372 public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002373 loadUrlImpl(url, additionalHttpHeaders);
2374 }
2375
2376 private void loadUrlImpl(String url, Map<String, String> extraHeaders) {
2377 switchOutDrawHistory();
2378 WebViewCore.GetUrlData arg = new WebViewCore.GetUrlData();
2379 arg.mUrl = url;
2380 arg.mExtraHeaders = extraHeaders;
2381 mWebViewCore.sendMessage(EventHub.LOAD_URL, arg);
2382 clearHelpers();
2383 }
2384
2385 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002386 * See {@link WebView#loadUrl(String)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002387 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002388 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002389 public void loadUrl(String url) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002390 loadUrlImpl(url);
2391 }
2392
2393 private void loadUrlImpl(String url) {
2394 if (url == null) {
2395 return;
2396 }
2397 loadUrlImpl(url, null);
2398 }
2399
2400 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002401 * See {@link WebView#postUrl(String, byte[])}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002402 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002403 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002404 public void postUrl(String url, byte[] postData) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002405 if (URLUtil.isNetworkUrl(url)) {
2406 switchOutDrawHistory();
2407 WebViewCore.PostUrlData arg = new WebViewCore.PostUrlData();
2408 arg.mUrl = url;
2409 arg.mPostData = postData;
2410 mWebViewCore.sendMessage(EventHub.POST_URL, arg);
2411 clearHelpers();
2412 } else {
2413 loadUrlImpl(url);
2414 }
2415 }
2416
2417 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002418 * See {@link WebView#loadData(String, String, String)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002419 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002420 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002421 public void loadData(String data, String mimeType, String encoding) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002422 loadDataImpl(data, mimeType, encoding);
2423 }
2424
2425 private void loadDataImpl(String data, String mimeType, String encoding) {
2426 StringBuilder dataUrl = new StringBuilder("data:");
2427 dataUrl.append(mimeType);
2428 if ("base64".equals(encoding)) {
2429 dataUrl.append(";base64");
2430 }
2431 dataUrl.append(",");
2432 dataUrl.append(data);
2433 loadUrlImpl(dataUrl.toString());
2434 }
2435
2436 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002437 * See {@link WebView#loadDataWithBaseURL(String, String, String, String, String)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002438 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002439 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002440 public void loadDataWithBaseURL(String baseUrl, String data,
2441 String mimeType, String encoding, String historyUrl) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002442
2443 if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) {
2444 loadDataImpl(data, mimeType, encoding);
2445 return;
2446 }
2447 switchOutDrawHistory();
2448 WebViewCore.BaseUrlData arg = new WebViewCore.BaseUrlData();
2449 arg.mBaseUrl = baseUrl;
2450 arg.mData = data;
2451 arg.mMimeType = mimeType;
2452 arg.mEncoding = encoding;
2453 arg.mHistoryUrl = historyUrl;
2454 mWebViewCore.sendMessage(EventHub.LOAD_DATA, arg);
2455 clearHelpers();
2456 }
2457
2458 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002459 * See {@link WebView#saveWebArchive(String)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002460 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002461 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002462 public void saveWebArchive(String filename) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002463 saveWebArchiveImpl(filename, false, null);
2464 }
2465
2466 /* package */ static class SaveWebArchiveMessage {
2467 SaveWebArchiveMessage (String basename, boolean autoname, ValueCallback<String> callback) {
2468 mBasename = basename;
2469 mAutoname = autoname;
2470 mCallback = callback;
2471 }
2472
2473 /* package */ final String mBasename;
2474 /* package */ final boolean mAutoname;
2475 /* package */ final ValueCallback<String> mCallback;
2476 /* package */ String mResultFile;
2477 }
2478
2479 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002480 * See {@link WebView#saveWebArchive(String, boolean, ValueCallback)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002481 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002482 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002483 public void saveWebArchive(String basename, boolean autoname, ValueCallback<String> callback) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002484 saveWebArchiveImpl(basename, autoname, callback);
2485 }
2486
2487 private void saveWebArchiveImpl(String basename, boolean autoname,
2488 ValueCallback<String> callback) {
2489 mWebViewCore.sendMessage(EventHub.SAVE_WEBARCHIVE,
2490 new SaveWebArchiveMessage(basename, autoname, callback));
2491 }
2492
2493 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002494 * See {@link WebView#stopLoading()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002495 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002496 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002497 public void stopLoading() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002498 // TODO: should we clear all the messages in the queue before sending
2499 // STOP_LOADING?
2500 switchOutDrawHistory();
2501 mWebViewCore.sendMessage(EventHub.STOP_LOADING);
2502 }
2503
2504 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002505 * See {@link WebView#reload()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002506 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002507 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002508 public void reload() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002509 clearHelpers();
2510 switchOutDrawHistory();
2511 mWebViewCore.sendMessage(EventHub.RELOAD);
2512 }
2513
2514 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002515 * See {@link WebView#canGoBack()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002516 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002517 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002518 public boolean canGoBack() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002519 WebBackForwardList l = mCallbackProxy.getBackForwardList();
2520 synchronized (l) {
2521 if (l.getClearPending()) {
2522 return false;
2523 } else {
2524 return l.getCurrentIndex() > 0;
2525 }
2526 }
2527 }
2528
2529 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002530 * See {@link WebView#goBack()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002531 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002532 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002533 public void goBack() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002534 goBackOrForwardImpl(-1);
2535 }
2536
2537 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002538 * See {@link WebView#canGoForward()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002539 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002540 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002541 public boolean canGoForward() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002542 WebBackForwardList l = mCallbackProxy.getBackForwardList();
2543 synchronized (l) {
2544 if (l.getClearPending()) {
2545 return false;
2546 } else {
2547 return l.getCurrentIndex() < l.getSize() - 1;
2548 }
2549 }
2550 }
2551
2552 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002553 * See {@link WebView#goForward()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002554 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002555 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002556 public void goForward() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002557 goBackOrForwardImpl(1);
2558 }
2559
2560 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002561 * See {@link WebView#canGoBackOrForward(int)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002562 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002563 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002564 public boolean canGoBackOrForward(int steps) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002565 WebBackForwardList l = mCallbackProxy.getBackForwardList();
2566 synchronized (l) {
2567 if (l.getClearPending()) {
2568 return false;
2569 } else {
2570 int newIndex = l.getCurrentIndex() + steps;
2571 return newIndex >= 0 && newIndex < l.getSize();
2572 }
2573 }
2574 }
2575
2576 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002577 * See {@link WebView#goBackOrForward(int)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002578 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002579 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002580 public void goBackOrForward(int steps) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002581 goBackOrForwardImpl(steps);
2582 }
2583
2584 private void goBackOrForwardImpl(int steps) {
2585 goBackOrForward(steps, false);
2586 }
2587
2588 private void goBackOrForward(int steps, boolean ignoreSnapshot) {
2589 if (steps != 0) {
2590 clearHelpers();
2591 mWebViewCore.sendMessage(EventHub.GO_BACK_FORWARD, steps,
2592 ignoreSnapshot ? 1 : 0);
2593 }
2594 }
2595
2596 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002597 * See {@link WebView#isPrivateBrowsingEnabled()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002598 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002599 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002600 public boolean isPrivateBrowsingEnabled() {
Michael Kolb13d7c032012-04-26 10:43:08 -07002601 WebSettingsClassic settings = getSettings();
2602 return (settings != null) ? settings.isPrivateBrowsingEnabled() : false;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002603 }
2604
2605 private void startPrivateBrowsing() {
2606 getSettings().setPrivateBrowsingEnabled(true);
2607 }
2608
2609 private boolean extendScroll(int y) {
2610 int finalY = mScroller.getFinalY();
2611 int newY = pinLocY(finalY + y);
2612 if (newY == finalY) return false;
2613 mScroller.setFinalY(newY);
2614 mScroller.extendDuration(computeDuration(0, y));
2615 return true;
2616 }
2617
2618 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002619 * See {@link WebView#pageUp(boolean)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002620 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002621 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002622 public boolean pageUp(boolean top) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002623 if (mNativeClass == 0) {
2624 return false;
2625 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002626 if (top) {
2627 // go to the top of the document
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00002628 return pinScrollTo(getScrollX(), 0, true, 0);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002629 }
2630 // Page up
2631 int h = getHeight();
2632 int y;
2633 if (h > 2 * PAGE_SCROLL_OVERLAP) {
2634 y = -h + PAGE_SCROLL_OVERLAP;
2635 } else {
2636 y = -h / 2;
2637 }
2638 return mScroller.isFinished() ? pinScrollBy(0, y, true, 0)
2639 : extendScroll(y);
2640 }
2641
2642 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002643 * See {@link WebView#pageDown(boolean)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002644 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002645 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002646 public boolean pageDown(boolean bottom) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002647 if (mNativeClass == 0) {
2648 return false;
2649 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002650 if (bottom) {
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00002651 return pinScrollTo(getScrollX(), computeRealVerticalScrollRange(), true, 0);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002652 }
2653 // Page down.
2654 int h = getHeight();
2655 int y;
2656 if (h > 2 * PAGE_SCROLL_OVERLAP) {
2657 y = h - PAGE_SCROLL_OVERLAP;
2658 } else {
2659 y = h / 2;
2660 }
2661 return mScroller.isFinished() ? pinScrollBy(0, y, true, 0)
2662 : extendScroll(y);
2663 }
2664
2665 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002666 * See {@link WebView#clearView()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002667 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002668 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002669 public void clearView() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002670 mContentWidth = 0;
2671 mContentHeight = 0;
Chris Craikc2c95432012-04-25 15:13:52 -07002672 setBaseLayer(0, false, false);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002673 mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT);
2674 }
2675
2676 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002677 * See {@link WebView#capturePicture()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002678 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002679 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002680 public Picture capturePicture() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002681 if (mNativeClass == 0) return null;
2682 Picture result = new Picture();
2683 nativeCopyBaseContentToPicture(result);
2684 return result;
2685 }
2686
2687 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002688 * See {@link WebView#getScale()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002689 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002690 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002691 public float getScale() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002692 return mZoomManager.getScale();
2693 }
2694
2695 /**
2696 * Compute the reading level scale of the WebView
2697 * @param scale The current scale.
2698 * @return The reading level scale.
2699 */
2700 /*package*/ float computeReadingLevelScale(float scale) {
2701 return mZoomManager.computeReadingLevelScale(scale);
2702 }
2703
2704 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002705 * See {@link WebView#setInitialScale(int)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002706 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002707 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002708 public void setInitialScale(int scaleInPercent) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002709 mZoomManager.setInitialScaleInPercent(scaleInPercent);
2710 }
2711
2712 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002713 * See {@link WebView#invokeZoomPicker()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002714 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002715 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002716 public void invokeZoomPicker() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002717 if (!getSettings().supportZoom()) {
2718 Log.w(LOGTAG, "This WebView doesn't support zoom.");
2719 return;
2720 }
2721 clearHelpers();
2722 mZoomManager.invokeZoomPicker();
2723 }
2724
2725 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002726 * See {@link WebView#getHitTestResult()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002727 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002728 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002729 public HitTestResult getHitTestResult() {
John Reckb2676f72012-03-02 14:13:06 -08002730 return mInitialHitTestResult;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002731 }
2732
John Reckb2676f72012-03-02 14:13:06 -08002733 // No left edge for double-tap zoom alignment
2734 static final int NO_LEFTEDGE = -1;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002735
2736 int getBlockLeftEdge(int x, int y, float readingScale) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002737 float invReadingScale = 1.0f / readingScale;
2738 int readingWidth = (int) (getViewWidth() * invReadingScale);
2739 int left = NO_LEFTEDGE;
2740 if (mFocusedNode != null) {
2741 final int length = mFocusedNode.mEnclosingParentRects.length;
2742 for (int i = 0; i < length; i++) {
2743 Rect rect = mFocusedNode.mEnclosingParentRects[i];
2744 if (rect.width() < mFocusedNode.mHitTestSlop) {
2745 // ignore bounding boxes that are too small
2746 continue;
Mangesh Ghiware518e83b2012-04-10 17:42:49 -07002747 } else if (rect.width() > readingWidth) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002748 // stop when bounding box doesn't fit the screen width
2749 // at reading scale
2750 break;
2751 }
2752
2753 left = rect.left;
2754 }
2755 }
2756
2757 return left;
2758 }
2759
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002760 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002761 * See {@link WebView#requestFocusNodeHref(Message)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002762 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002763 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002764 public void requestFocusNodeHref(Message hrefMsg) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002765 if (hrefMsg == null) {
2766 return;
2767 }
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00002768 int contentX = viewToContentX(mLastTouchX + getScrollX());
2769 int contentY = viewToContentY(mLastTouchY + getScrollY());
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002770 if (mFocusedNode != null && mFocusedNode.mHitTestX == contentX
2771 && mFocusedNode.mHitTestY == contentY) {
2772 hrefMsg.getData().putString(FocusNodeHref.URL, mFocusedNode.mLinkUrl);
2773 hrefMsg.getData().putString(FocusNodeHref.TITLE, mFocusedNode.mAnchorText);
2774 hrefMsg.getData().putString(FocusNodeHref.SRC, mFocusedNode.mImageUrl);
2775 hrefMsg.sendToTarget();
2776 return;
2777 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002778 mWebViewCore.sendMessage(EventHub.REQUEST_CURSOR_HREF,
2779 contentX, contentY, hrefMsg);
2780 }
2781
2782 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00002783 * See {@link WebView#requestImageRef(Message)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002784 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00002785 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002786 public void requestImageRef(Message msg) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002787 if (0 == mNativeClass) return; // client isn't initialized
John Reckb2676f72012-03-02 14:13:06 -08002788 String url = mFocusedNode != null ? mFocusedNode.mImageUrl : null;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002789 Bundle data = msg.getData();
John Reckb2676f72012-03-02 14:13:06 -08002790 data.putString("url", url);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002791 msg.setData(data);
2792 msg.sendToTarget();
2793 }
2794
2795 static int pinLoc(int x, int viewMax, int docMax) {
2796// Log.d(LOGTAG, "-- pinLoc " + x + " " + viewMax + " " + docMax);
2797 if (docMax < viewMax) { // the doc has room on the sides for "blank"
2798 // pin the short document to the top/left of the screen
2799 x = 0;
2800// Log.d(LOGTAG, "--- center " + x);
2801 } else if (x < 0) {
2802 x = 0;
2803// Log.d(LOGTAG, "--- zero");
2804 } else if (x + viewMax > docMax) {
2805 x = docMax - viewMax;
2806// Log.d(LOGTAG, "--- pin " + x);
2807 }
2808 return x;
2809 }
2810
2811 // Expects x in view coordinates
2812 int pinLocX(int x) {
2813 if (mInOverScrollMode) return x;
2814 return pinLoc(x, getViewWidth(), computeRealHorizontalScrollRange());
2815 }
2816
2817 // Expects y in view coordinates
2818 int pinLocY(int y) {
2819 if (mInOverScrollMode) return y;
2820 return pinLoc(y, getViewHeightWithTitle(),
2821 computeRealVerticalScrollRange() + getTitleHeight());
2822 }
2823
2824 /**
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002825 * Given a distance in view space, convert it to content space. Note: this
2826 * does not reflect translation, just scaling, so this should not be called
2827 * with coordinates, but should be called for dimensions like width or
2828 * height.
2829 */
2830 private int viewToContentDimension(int d) {
2831 return Math.round(d * mZoomManager.getInvScale());
2832 }
2833
2834 /**
2835 * Given an x coordinate in view space, convert it to content space. Also
John Reckb2676f72012-03-02 14:13:06 -08002836 * may be used for absolute heights.
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002837 */
2838 /*package*/ int viewToContentX(int x) {
2839 return viewToContentDimension(x);
2840 }
2841
2842 /**
2843 * Given a y coordinate in view space, convert it to content space.
2844 * Takes into account the height of the title bar if there is one
2845 * embedded into the WebView.
2846 */
2847 /*package*/ int viewToContentY(int y) {
2848 return viewToContentDimension(y - getTitleHeight());
2849 }
2850
2851 /**
2852 * Given a x coordinate in view space, convert it to content space.
2853 * Returns the result as a float.
2854 */
2855 private float viewToContentXf(int x) {
2856 return x * mZoomManager.getInvScale();
2857 }
2858
2859 /**
2860 * Given a y coordinate in view space, convert it to content space.
2861 * Takes into account the height of the title bar if there is one
2862 * embedded into the WebView. Returns the result as a float.
2863 */
2864 private float viewToContentYf(int y) {
2865 return (y - getTitleHeight()) * mZoomManager.getInvScale();
2866 }
2867
2868 /**
2869 * Given a distance in content space, convert it to view space. Note: this
2870 * does not reflect translation, just scaling, so this should not be called
2871 * with coordinates, but should be called for dimensions like width or
2872 * height.
2873 */
2874 /*package*/ int contentToViewDimension(int d) {
2875 return Math.round(d * mZoomManager.getScale());
2876 }
2877
2878 /**
2879 * Given an x coordinate in content space, convert it to view
2880 * space.
2881 */
2882 /*package*/ int contentToViewX(int x) {
2883 return contentToViewDimension(x);
2884 }
2885
2886 /**
2887 * Given a y coordinate in content space, convert it to view
2888 * space. Takes into account the height of the title bar.
2889 */
2890 /*package*/ int contentToViewY(int y) {
2891 return contentToViewDimension(y) + getTitleHeight();
2892 }
2893
2894 private Rect contentToViewRect(Rect x) {
2895 return new Rect(contentToViewX(x.left), contentToViewY(x.top),
2896 contentToViewX(x.right), contentToViewY(x.bottom));
2897 }
2898
2899 /* To invalidate a rectangle in content coordinates, we need to transform
2900 the rect into view coordinates, so we can then call invalidate(...).
2901
2902 Normally, we would just call contentToView[XY](...), which eventually
2903 calls Math.round(coordinate * mActualScale). However, for invalidates,
2904 we need to account for the slop that occurs with antialiasing. To
2905 address that, we are a little more liberal in the size of the rect that
2906 we invalidate.
2907
2908 This liberal calculation calls floor() for the top/left, and ceil() for
2909 the bottom/right coordinates. This catches the possible extra pixels of
2910 antialiasing that we might have missed with just round().
2911 */
2912
2913 // Called by JNI to invalidate the View, given rectangle coordinates in
2914 // content space
2915 private void viewInvalidate(int l, int t, int r, int b) {
2916 final float scale = mZoomManager.getScale();
2917 final int dy = getTitleHeight();
Jonathan Dixon3c909522012-02-28 18:45:06 +00002918 mWebView.invalidate((int)Math.floor(l * scale),
2919 (int)Math.floor(t * scale) + dy,
2920 (int)Math.ceil(r * scale),
2921 (int)Math.ceil(b * scale) + dy);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002922 }
2923
2924 // Called by JNI to invalidate the View after a delay, given rectangle
2925 // coordinates in content space
2926 private void viewInvalidateDelayed(long delay, int l, int t, int r, int b) {
2927 final float scale = mZoomManager.getScale();
2928 final int dy = getTitleHeight();
Jonathan Dixon3c909522012-02-28 18:45:06 +00002929 mWebView.postInvalidateDelayed(delay,
2930 (int)Math.floor(l * scale),
2931 (int)Math.floor(t * scale) + dy,
2932 (int)Math.ceil(r * scale),
2933 (int)Math.ceil(b * scale) + dy);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002934 }
2935
2936 private void invalidateContentRect(Rect r) {
2937 viewInvalidate(r.left, r.top, r.right, r.bottom);
2938 }
2939
2940 // stop the scroll animation, and don't let a subsequent fling add
2941 // to the existing velocity
2942 private void abortAnimation() {
2943 mScroller.abortAnimation();
2944 mLastVelocity = 0;
2945 }
2946
2947 /* call from webcoreview.draw(), so we're still executing in the UI thread
2948 */
2949 private void recordNewContentSize(int w, int h, boolean updateLayout) {
2950
2951 // premature data from webkit, ignore
2952 if ((w | h) == 0) {
2953 return;
2954 }
2955
2956 // don't abort a scroll animation if we didn't change anything
2957 if (mContentWidth != w || mContentHeight != h) {
2958 // record new dimensions
2959 mContentWidth = w;
2960 mContentHeight = h;
2961 // If history Picture is drawn, don't update scroll. They will be
2962 // updated when we get out of that mode.
2963 if (!mDrawHistory) {
2964 // repin our scroll, taking into account the new content size
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00002965 updateScrollCoordinates(pinLocX(getScrollX()), pinLocY(getScrollY()));
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002966 if (!mScroller.isFinished()) {
2967 // We are in the middle of a scroll. Repin the final scroll
2968 // position.
2969 mScroller.setFinalX(pinLocX(mScroller.getFinalX()));
2970 mScroller.setFinalY(pinLocY(mScroller.getFinalY()));
2971 }
2972 }
2973 }
2974 contentSizeChanged(updateLayout);
2975 }
2976
2977 // Used to avoid sending many visible rect messages.
2978 private Rect mLastVisibleRectSent = new Rect();
2979 private Rect mLastGlobalRect = new Rect();
2980 private Rect mVisibleRect = new Rect();
2981 private Rect mGlobalVisibleRect = new Rect();
2982 private Point mScrollOffset = new Point();
2983
2984 Rect sendOurVisibleRect() {
2985 if (mZoomManager.isPreventingWebkitUpdates()) return mLastVisibleRectSent;
2986 calcOurContentVisibleRect(mVisibleRect);
2987 // Rect.equals() checks for null input.
2988 if (!mVisibleRect.equals(mLastVisibleRectSent)) {
2989 if (!mBlockWebkitViewMessages) {
2990 mScrollOffset.set(mVisibleRect.left, mVisibleRect.top);
2991 mWebViewCore.removeMessages(EventHub.SET_SCROLL_OFFSET);
2992 mWebViewCore.sendMessage(EventHub.SET_SCROLL_OFFSET,
John Reckb2676f72012-03-02 14:13:06 -08002993 mSendScrollEvent ? 1 : 0, mScrollOffset);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002994 }
2995 mLastVisibleRectSent.set(mVisibleRect);
2996 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
2997 }
Jonathan Dixon3c909522012-02-28 18:45:06 +00002998 if (mWebView.getGlobalVisibleRect(mGlobalVisibleRect)
Jonathan Dixonded37ed92012-02-13 17:26:46 -08002999 && !mGlobalVisibleRect.equals(mLastGlobalRect)) {
3000 if (DebugFlags.WEB_VIEW) {
3001 Log.v(LOGTAG, "sendOurVisibleRect=(" + mGlobalVisibleRect.left + ","
3002 + mGlobalVisibleRect.top + ",r=" + mGlobalVisibleRect.right + ",b="
3003 + mGlobalVisibleRect.bottom);
3004 }
3005 // TODO: the global offset is only used by windowRect()
3006 // in ChromeClientAndroid ; other clients such as touch
3007 // and mouse events could return view + screen relative points.
3008 if (!mBlockWebkitViewMessages) {
3009 mWebViewCore.sendMessage(EventHub.SET_GLOBAL_BOUNDS, mGlobalVisibleRect);
3010 }
3011 mLastGlobalRect.set(mGlobalVisibleRect);
3012 }
3013 return mVisibleRect;
3014 }
3015
3016 private Point mGlobalVisibleOffset = new Point();
3017 // Sets r to be the visible rectangle of our webview in view coordinates
3018 private void calcOurVisibleRect(Rect r) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00003019 mWebView.getGlobalVisibleRect(r, mGlobalVisibleOffset);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003020 r.offset(-mGlobalVisibleOffset.x, -mGlobalVisibleOffset.y);
3021 }
3022
3023 // Sets r to be our visible rectangle in content coordinates
3024 private void calcOurContentVisibleRect(Rect r) {
3025 calcOurVisibleRect(r);
3026 r.left = viewToContentX(r.left);
3027 // viewToContentY will remove the total height of the title bar. Add
3028 // the visible height back in to account for the fact that if the title
3029 // bar is partially visible, the part of the visible rect which is
3030 // displaying our content is displaced by that amount.
3031 r.top = viewToContentY(r.top + getVisibleTitleHeightImpl());
3032 r.right = viewToContentX(r.right);
3033 r.bottom = viewToContentY(r.bottom);
3034 }
3035
3036 private Rect mContentVisibleRect = new Rect();
3037 // Sets r to be our visible rectangle in content coordinates. We use this
3038 // method on the native side to compute the position of the fixed layers.
3039 // Uses floating coordinates (necessary to correctly place elements when
3040 // the scale factor is not 1)
3041 private void calcOurContentVisibleRectF(RectF r) {
3042 calcOurVisibleRect(mContentVisibleRect);
Teng-Hui Zhu9d281572012-04-24 16:53:41 -07003043 r.left = viewToContentXf(mContentVisibleRect.left) / mWebView.getScaleX();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003044 // viewToContentY will remove the total height of the title bar. Add
3045 // the visible height back in to account for the fact that if the title
3046 // bar is partially visible, the part of the visible rect which is
3047 // displaying our content is displaced by that amount.
Teng-Hui Zhu9d281572012-04-24 16:53:41 -07003048 r.top = viewToContentYf(mContentVisibleRect.top + getVisibleTitleHeightImpl()) / mWebView.getScaleY();
3049 r.right = viewToContentXf(mContentVisibleRect.right) / mWebView.getScaleX();
3050 r.bottom = viewToContentYf(mContentVisibleRect.bottom) / mWebView.getScaleY();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003051 }
3052
3053 static class ViewSizeData {
3054 int mWidth;
3055 int mHeight;
3056 float mHeightWidthRatio;
3057 int mActualViewHeight;
3058 int mTextWrapWidth;
3059 int mAnchorX;
3060 int mAnchorY;
3061 float mScale;
3062 boolean mIgnoreHeight;
3063 }
3064
3065 /**
3066 * Compute unzoomed width and height, and if they differ from the last
3067 * values we sent, send them to webkit (to be used as new viewport)
3068 *
3069 * @param force ensures that the message is sent to webkit even if the width
3070 * or height has not changed since the last message
3071 *
3072 * @return true if new values were sent
3073 */
3074 boolean sendViewSizeZoom(boolean force) {
3075 if (mBlockWebkitViewMessages) return false;
3076 if (mZoomManager.isPreventingWebkitUpdates()) return false;
3077
3078 int viewWidth = getViewWidth();
3079 int newWidth = Math.round(viewWidth * mZoomManager.getInvScale());
3080 // This height could be fixed and be different from actual visible height.
3081 int viewHeight = getViewHeightWithTitle() - getTitleHeight();
3082 int newHeight = Math.round(viewHeight * mZoomManager.getInvScale());
3083 // Make the ratio more accurate than (newHeight / newWidth), since the
3084 // latter both are calculated and rounded.
3085 float heightWidthRatio = (float) viewHeight / viewWidth;
3086 /*
3087 * Because the native side may have already done a layout before the
3088 * View system was able to measure us, we have to send a height of 0 to
3089 * remove excess whitespace when we grow our width. This will trigger a
3090 * layout and a change in content size. This content size change will
3091 * mean that contentSizeChanged will either call this method directly or
3092 * indirectly from onSizeChanged.
3093 */
3094 if (newWidth > mLastWidthSent && mWrapContent) {
3095 newHeight = 0;
3096 heightWidthRatio = 0;
3097 }
3098 // Actual visible content height.
3099 int actualViewHeight = Math.round(getViewHeight() * mZoomManager.getInvScale());
3100 // Avoid sending another message if the dimensions have not changed.
3101 if (newWidth != mLastWidthSent || newHeight != mLastHeightSent || force ||
3102 actualViewHeight != mLastActualHeightSent) {
3103 ViewSizeData data = new ViewSizeData();
3104 data.mWidth = newWidth;
3105 data.mHeight = newHeight;
3106 data.mHeightWidthRatio = heightWidthRatio;
3107 data.mActualViewHeight = actualViewHeight;
3108 data.mTextWrapWidth = Math.round(viewWidth / mZoomManager.getTextWrapScale());
3109 data.mScale = mZoomManager.getScale();
3110 data.mIgnoreHeight = mZoomManager.isFixedLengthAnimationInProgress()
3111 && !mHeightCanMeasure;
3112 data.mAnchorX = mZoomManager.getDocumentAnchorX();
3113 data.mAnchorY = mZoomManager.getDocumentAnchorY();
3114 mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, data);
3115 mLastWidthSent = newWidth;
3116 mLastHeightSent = newHeight;
3117 mLastActualHeightSent = actualViewHeight;
3118 mZoomManager.clearDocumentAnchor();
3119 return true;
3120 }
3121 return false;
3122 }
3123
3124 /**
3125 * Update the double-tap zoom.
3126 */
3127 /* package */ void updateDoubleTapZoom(int doubleTapZoom) {
3128 mZoomManager.updateDoubleTapZoom(doubleTapZoom);
3129 }
3130
3131 private int computeRealHorizontalScrollRange() {
3132 if (mDrawHistory) {
3133 return mHistoryWidth;
3134 } else {
3135 // to avoid rounding error caused unnecessary scrollbar, use floor
3136 return (int) Math.floor(mContentWidth * mZoomManager.getScale());
3137 }
3138 }
3139
3140 @Override
Jonathan Dixon3c909522012-02-28 18:45:06 +00003141 public int computeHorizontalScrollRange() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003142 int range = computeRealHorizontalScrollRange();
3143
3144 // Adjust reported range if overscrolled to compress the scroll bars
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00003145 final int scrollX = getScrollX();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003146 final int overscrollRight = computeMaxScrollX();
3147 if (scrollX < 0) {
3148 range -= scrollX;
3149 } else if (scrollX > overscrollRight) {
3150 range += scrollX - overscrollRight;
3151 }
3152
3153 return range;
3154 }
3155
3156 @Override
Jonathan Dixon3c909522012-02-28 18:45:06 +00003157 public int computeHorizontalScrollOffset() {
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00003158 return Math.max(getScrollX(), 0);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003159 }
3160
3161 private int computeRealVerticalScrollRange() {
3162 if (mDrawHistory) {
3163 return mHistoryHeight;
3164 } else {
3165 // to avoid rounding error caused unnecessary scrollbar, use floor
3166 return (int) Math.floor(mContentHeight * mZoomManager.getScale());
3167 }
3168 }
3169
3170 @Override
Jonathan Dixon3c909522012-02-28 18:45:06 +00003171 public int computeVerticalScrollRange() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003172 int range = computeRealVerticalScrollRange();
3173
3174 // Adjust reported range if overscrolled to compress the scroll bars
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00003175 final int scrollY = getScrollY();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003176 final int overscrollBottom = computeMaxScrollY();
3177 if (scrollY < 0) {
3178 range -= scrollY;
3179 } else if (scrollY > overscrollBottom) {
3180 range += scrollY - overscrollBottom;
3181 }
3182
3183 return range;
3184 }
3185
3186 @Override
Jonathan Dixon3c909522012-02-28 18:45:06 +00003187 public int computeVerticalScrollOffset() {
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00003188 return Math.max(getScrollY() - getTitleHeight(), 0);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003189 }
3190
3191 @Override
Jonathan Dixon3c909522012-02-28 18:45:06 +00003192 public int computeVerticalScrollExtent() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003193 return getViewHeight();
3194 }
3195
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003196 @Override
Jonathan Dixon3c909522012-02-28 18:45:06 +00003197 public void onDrawVerticalScrollBar(Canvas canvas,
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003198 Drawable scrollBar,
3199 int l, int t, int r, int b) {
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00003200 if (getScrollY() < 0) {
3201 t -= getScrollY();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003202 }
3203 scrollBar.setBounds(l, t + getVisibleTitleHeightImpl(), r, b);
3204 scrollBar.draw(canvas);
3205 }
3206
3207 @Override
Jonathan Dixon3c909522012-02-28 18:45:06 +00003208 public void onOverScrolled(int scrollX, int scrollY, boolean clampedX,
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003209 boolean clampedY) {
3210 // Special-case layer scrolling so that we do not trigger normal scroll
3211 // updating.
George Mountfcff68f2012-03-20 10:21:57 -07003212 if (mTouchMode == TOUCH_DRAG_TEXT_MODE) {
3213 scrollEditText(scrollX, scrollY);
3214 return;
3215 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003216 if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
3217 scrollLayerTo(scrollX, scrollY);
3218 return;
3219 }
3220 mInOverScrollMode = false;
3221 int maxX = computeMaxScrollX();
3222 int maxY = computeMaxScrollY();
3223 if (maxX == 0) {
3224 // do not over scroll x if the page just fits the screen
3225 scrollX = pinLocX(scrollX);
3226 } else if (scrollX < 0 || scrollX > maxX) {
3227 mInOverScrollMode = true;
3228 }
3229 if (scrollY < 0 || scrollY > maxY) {
3230 mInOverScrollMode = true;
3231 }
3232
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00003233 int oldX = getScrollX();
3234 int oldY = getScrollY();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003235
Jonathan Dixon3c909522012-02-28 18:45:06 +00003236 mWebViewPrivate.super_scrollTo(scrollX, scrollY);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003237
3238 if (mOverScrollGlow != null) {
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00003239 mOverScrollGlow.pullGlow(getScrollX(), getScrollY(), oldX, oldY, maxX, maxY);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003240 }
3241 }
3242
3243 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00003244 * See {@link WebView#getUrl()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003245 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003246 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003247 public String getUrl() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003248 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
3249 return h != null ? h.getUrl() : null;
3250 }
3251
3252 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00003253 * See {@link WebView#getOriginalUrl()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003254 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003255 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003256 public String getOriginalUrl() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003257 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
3258 return h != null ? h.getOriginalUrl() : null;
3259 }
3260
3261 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00003262 * See {@link WebView#getTitle()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003263 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003264 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003265 public String getTitle() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003266 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
3267 return h != null ? h.getTitle() : null;
3268 }
3269
3270 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00003271 * See {@link WebView#getFavicon()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003272 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003273 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003274 public Bitmap getFavicon() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003275 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
3276 return h != null ? h.getFavicon() : null;
3277 }
3278
3279 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00003280 * See {@link WebView#getTouchIconUrl()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003281 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003282 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003283 public String getTouchIconUrl() {
3284 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
3285 return h != null ? h.getTouchIconUrl() : null;
3286 }
3287
3288 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00003289 * See {@link WebView#getProgress()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003290 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003291 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003292 public int getProgress() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003293 return mCallbackProxy.getProgress();
3294 }
3295
3296 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00003297 * See {@link WebView#getContentHeight()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003298 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003299 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003300 public int getContentHeight() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003301 return mContentHeight;
3302 }
3303
3304 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00003305 * See {@link WebView#getContentWidth()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003306 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003307 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003308 public int getContentWidth() {
3309 return mContentWidth;
3310 }
3311
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003312 public int getPageBackgroundColor() {
3313 return nativeGetBackgroundColor();
3314 }
3315
3316 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00003317 * See {@link WebView#pauseTimers()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003318 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003319 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003320 public void pauseTimers() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003321 mWebViewCore.sendMessage(EventHub.PAUSE_TIMERS);
3322 }
3323
3324 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00003325 * See {@link WebView#resumeTimers()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003326 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003327 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003328 public void resumeTimers() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003329 mWebViewCore.sendMessage(EventHub.RESUME_TIMERS);
3330 }
3331
3332 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00003333 * See {@link WebView#onPause()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003334 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003335 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003336 public void onPause() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003337 if (!mIsPaused) {
3338 mIsPaused = true;
3339 mWebViewCore.sendMessage(EventHub.ON_PAUSE);
3340 // We want to pause the current playing video when switching out
3341 // from the current WebView/tab.
3342 if (mHTML5VideoViewProxy != null) {
3343 mHTML5VideoViewProxy.pauseAndDispatch();
3344 }
3345 if (mNativeClass != 0) {
3346 nativeSetPauseDrawing(mNativeClass, true);
3347 }
3348
3349 cancelSelectDialog();
3350 WebCoreThreadWatchdog.pause();
3351 }
3352 }
3353
3354 @Override
Jonathan Dixon3c909522012-02-28 18:45:06 +00003355 public void onWindowVisibilityChanged(int visibility) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003356 updateDrawingState();
3357 }
3358
3359 void updateDrawingState() {
3360 if (mNativeClass == 0 || mIsPaused) return;
Jonathan Dixon3c909522012-02-28 18:45:06 +00003361 if (mWebView.getWindowVisibility() != View.VISIBLE) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003362 nativeSetPauseDrawing(mNativeClass, true);
Jonathan Dixon3c909522012-02-28 18:45:06 +00003363 } else if (mWebView.getVisibility() != View.VISIBLE) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003364 nativeSetPauseDrawing(mNativeClass, true);
3365 } else {
3366 nativeSetPauseDrawing(mNativeClass, false);
3367 }
3368 }
3369
3370 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00003371 * See {@link WebView#onResume()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003372 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003373 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003374 public void onResume() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003375 if (mIsPaused) {
3376 mIsPaused = false;
3377 mWebViewCore.sendMessage(EventHub.ON_RESUME);
3378 if (mNativeClass != 0) {
3379 nativeSetPauseDrawing(mNativeClass, false);
3380 }
3381 }
3382 // Ensure that the watchdog has a currently valid Context to be able to display
3383 // a prompt dialog. For example, if the Activity was finished whilst the WebCore
3384 // thread was blocked and the Activity is started again, we may reuse the blocked
3385 // thread, but we'll have a new Activity.
3386 WebCoreThreadWatchdog.updateContext(mContext);
3387 // We get a call to onResume for new WebViews (i.e. mIsPaused will be false). We need
3388 // to ensure that the Watchdog thread is running for the new WebView, so call
3389 // it outside the if block above.
3390 WebCoreThreadWatchdog.resume();
3391 }
3392
3393 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00003394 * See {@link WebView#isPaused()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003395 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003396 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003397 public boolean isPaused() {
3398 return mIsPaused;
3399 }
3400
3401 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00003402 * See {@link WebView#freeMemory()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003403 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003404 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003405 public void freeMemory() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003406 mWebViewCore.sendMessage(EventHub.FREE_MEMORY);
3407 }
3408
3409 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00003410 * See {@link WebView#clearCache(boolean)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003411 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003412 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003413 public void clearCache(boolean includeDiskFiles) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003414 // Note: this really needs to be a static method as it clears cache for all
3415 // WebView. But we need mWebViewCore to send message to WebCore thread, so
3416 // we can't make this static.
3417 mWebViewCore.sendMessage(EventHub.CLEAR_CACHE,
3418 includeDiskFiles ? 1 : 0, 0);
3419 }
3420
3421 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00003422 * See {@link WebView#clearFormData()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003423 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003424 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003425 public void clearFormData() {
George Mountbcd5dd72012-03-01 08:39:03 -08003426 if (mAutoCompletePopup != null) {
3427 mAutoCompletePopup.clearAdapter();
3428 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003429 }
3430
3431 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00003432 * See {@link WebView#clearHistory()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003433 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003434 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003435 public void clearHistory() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003436 mCallbackProxy.getBackForwardList().setClearPending();
3437 mWebViewCore.sendMessage(EventHub.CLEAR_HISTORY);
3438 }
3439
3440 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00003441 * See {@link WebView#clearSslPreferences()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003442 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003443 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003444 public void clearSslPreferences() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003445 mWebViewCore.sendMessage(EventHub.CLEAR_SSL_PREF_TABLE);
3446 }
3447
3448 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00003449 * See {@link WebView#copyBackForwardList()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003450 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003451 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003452 public WebBackForwardList copyBackForwardList() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003453 return mCallbackProxy.getBackForwardList().clone();
3454 }
3455
Jonathan Dixon19b80112012-03-02 18:18:00 +00003456 /**
Victoria Leased405a432012-03-22 15:53:48 -07003457 * See {@link WebView#setFindListener(WebView.FindListener)}.
3458 * @hide
Victoria Leaseabeb6a72012-03-05 16:29:12 -08003459 */
Victoria Leased405a432012-03-22 15:53:48 -07003460 public void setFindListener(WebView.FindListener listener) {
Victoria Leaseabeb6a72012-03-05 16:29:12 -08003461 mFindListener = listener;
3462 }
3463
3464 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00003465 * See {@link WebView#findNext(boolean)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003466 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003467 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003468 public void findNext(boolean forward) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003469 if (0 == mNativeClass) return; // client isn't initialized
Victoria Lease0b8413b2012-03-26 13:04:10 -07003470 if (mFindRequest != null) {
3471 mWebViewCore.sendMessage(EventHub.FIND_NEXT, forward ? 1 : 0, mFindRequest);
3472 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003473 }
3474
Jonathan Dixon19b80112012-03-02 18:18:00 +00003475 /**
3476 * See {@link WebView#findAll(String)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003477 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003478 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003479 public int findAll(String find) {
3480 return findAllBody(find, false);
3481 }
3482
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003483 public void findAllAsync(String find) {
3484 findAllBody(find, true);
3485 }
3486
3487 private int findAllBody(String find, boolean isAsync) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003488 if (0 == mNativeClass) return 0; // client isn't initialized
Victoria Lease0b8413b2012-03-26 13:04:10 -07003489 mFindRequest = null;
Victoria Leaseabeb6a72012-03-05 16:29:12 -08003490 if (find == null) return 0;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003491 mWebViewCore.removeMessages(EventHub.FIND_ALL);
Victoria Lease0b8413b2012-03-26 13:04:10 -07003492 mFindRequest = new WebViewCore.FindAllRequest(find);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003493 if (isAsync) {
Victoria Lease0b8413b2012-03-26 13:04:10 -07003494 mWebViewCore.sendMessage(EventHub.FIND_ALL, mFindRequest);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003495 return 0; // no need to wait for response
3496 }
Victoria Lease0b8413b2012-03-26 13:04:10 -07003497 synchronized(mFindRequest) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003498 try {
Victoria Lease0b8413b2012-03-26 13:04:10 -07003499 mWebViewCore.sendMessageAtFrontOfQueue(EventHub.FIND_ALL, mFindRequest);
3500 while (mFindRequest.mMatchCount == -1) {
3501 mFindRequest.wait();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003502 }
3503 }
3504 catch (InterruptedException e) {
3505 return 0;
3506 }
Victoria Lease0b8413b2012-03-26 13:04:10 -07003507 return mFindRequest.mMatchCount;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003508 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003509 }
3510
3511 /**
3512 * Start an ActionMode for finding text in this WebView. Only works if this
3513 * WebView is attached to the view system.
3514 * @param text If non-null, will be the initial text to search for.
3515 * Otherwise, the last String searched for in this WebView will
3516 * be used to start.
3517 * @param showIme If true, show the IME, assuming the user will begin typing.
3518 * If false and text is non-null, perform a find all.
3519 * @return boolean True if the find dialog is shown, false otherwise.
3520 */
3521 public boolean showFindDialog(String text, boolean showIme) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003522 FindActionModeCallback callback = new FindActionModeCallback(mContext);
Jonathan Dixon3c909522012-02-28 18:45:06 +00003523 if (mWebView.getParent() == null || mWebView.startActionMode(callback) == null) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003524 // Could not start the action mode, so end Find on page
3525 return false;
3526 }
3527 mCachedOverlappingActionModeHeight = -1;
3528 mFindCallback = callback;
3529 setFindIsUp(true);
3530 mFindCallback.setWebView(this);
3531 if (showIme) {
3532 mFindCallback.showSoftInput();
3533 } else if (text != null) {
3534 mFindCallback.setText(text);
3535 mFindCallback.findAll();
3536 return true;
3537 }
3538 if (text == null) {
Victoria Lease0b8413b2012-03-26 13:04:10 -07003539 text = mFindRequest == null ? null : mFindRequest.mSearchText;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003540 }
3541 if (text != null) {
3542 mFindCallback.setText(text);
3543 mFindCallback.findAll();
3544 }
3545 return true;
3546 }
3547
3548 /**
3549 * Keep track of the find callback so that we can remove its titlebar if
3550 * necessary.
3551 */
3552 private FindActionModeCallback mFindCallback;
3553
3554 /**
3555 * Toggle whether the find dialog is showing, for both native and Java.
3556 */
3557 private void setFindIsUp(boolean isUp) {
3558 mFindIsUp = isUp;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003559 }
3560
3561 // Used to know whether the find dialog is open. Affects whether
3562 // or not we draw the highlights for matches.
3563 private boolean mFindIsUp;
3564
Victoria Lease0b8413b2012-03-26 13:04:10 -07003565 // Keep track of the last find request sent.
3566 private WebViewCore.FindAllRequest mFindRequest = null;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003567
3568 /**
3569 * Return the first substring consisting of the address of a physical
3570 * location. Currently, only addresses in the United States are detected,
3571 * and consist of:
3572 * - a house number
3573 * - a street name
3574 * - a street type (Road, Circle, etc), either spelled out or abbreviated
3575 * - a city name
3576 * - a state or territory, either spelled out or two-letter abbr.
3577 * - an optional 5 digit or 9 digit zip code.
3578 *
3579 * All names must be correctly capitalized, and the zip code, if present,
3580 * must be valid for the state. The street type must be a standard USPS
3581 * spelling or abbreviation. The state or territory must also be spelled
3582 * or abbreviated using USPS standards. The house number may not exceed
3583 * five digits.
3584 * @param addr The string to search for addresses.
3585 *
3586 * @return the address, or if no address is found, return null.
3587 */
3588 public static String findAddress(String addr) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003589 return findAddress(addr, false);
3590 }
3591
3592 /**
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003593 * Return the first substring consisting of the address of a physical
3594 * location. Currently, only addresses in the United States are detected,
3595 * and consist of:
3596 * - a house number
3597 * - a street name
3598 * - a street type (Road, Circle, etc), either spelled out or abbreviated
3599 * - a city name
3600 * - a state or territory, either spelled out or two-letter abbr.
3601 * - an optional 5 digit or 9 digit zip code.
3602 *
3603 * Names are optionally capitalized, and the zip code, if present,
3604 * must be valid for the state. The street type must be a standard USPS
3605 * spelling or abbreviation. The state or territory must also be spelled
3606 * or abbreviated using USPS standards. The house number may not exceed
3607 * five digits.
3608 * @param addr The string to search for addresses.
3609 * @param caseInsensitive addr Set to true to make search ignore case.
3610 *
3611 * @return the address, or if no address is found, return null.
3612 */
3613 public static String findAddress(String addr, boolean caseInsensitive) {
3614 return WebViewCore.nativeFindAddress(addr, caseInsensitive);
3615 }
3616
Jonathan Dixon19b80112012-03-02 18:18:00 +00003617 /**
3618 * See {@link WebView#clearMatches()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003619 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003620 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003621 public void clearMatches() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003622 if (mNativeClass == 0)
3623 return;
3624 mWebViewCore.removeMessages(EventHub.FIND_ALL);
3625 mWebViewCore.sendMessage(EventHub.FIND_ALL, null);
3626 }
3627
3628
3629 /**
3630 * Called when the find ActionMode ends.
3631 */
3632 void notifyFindDialogDismissed() {
3633 mFindCallback = null;
3634 mCachedOverlappingActionModeHeight = -1;
3635 if (mWebViewCore == null) {
3636 return;
3637 }
3638 clearMatches();
3639 setFindIsUp(false);
3640 // Now that the dialog has been removed, ensure that we scroll to a
3641 // location that is not beyond the end of the page.
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00003642 pinScrollTo(getScrollX(), getScrollY(), false, 0);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003643 invalidate();
3644 }
3645
3646 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00003647 * See {@link WebView#documentHasImages(Message)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003648 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00003649 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003650 public void documentHasImages(Message response) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003651 if (response == null) {
3652 return;
3653 }
3654 mWebViewCore.sendMessage(EventHub.DOC_HAS_IMAGES, response);
3655 }
3656
3657 /**
3658 * Request the scroller to abort any ongoing animation
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003659 */
3660 public void stopScroll() {
3661 mScroller.forceFinished(true);
3662 mLastVelocity = 0;
3663 }
3664
3665 @Override
3666 public void computeScroll() {
3667 if (mScroller.computeScrollOffset()) {
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00003668 int oldX = getScrollX();
3669 int oldY = getScrollY();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003670 int x = mScroller.getCurrX();
3671 int y = mScroller.getCurrY();
3672 invalidate(); // So we draw again
3673
3674 if (!mScroller.isFinished()) {
3675 int rangeX = computeMaxScrollX();
3676 int rangeY = computeMaxScrollY();
3677 int overflingDistance = mOverflingDistance;
3678
3679 // Use the layer's scroll data if needed.
3680 if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
3681 oldX = mScrollingLayerRect.left;
3682 oldY = mScrollingLayerRect.top;
3683 rangeX = mScrollingLayerRect.right;
3684 rangeY = mScrollingLayerRect.bottom;
3685 // No overscrolling for layers.
3686 overflingDistance = 0;
George Mountfcff68f2012-03-20 10:21:57 -07003687 } else if (mTouchMode == TOUCH_DRAG_TEXT_MODE) {
3688 oldX = getTextScrollX();
3689 oldY = getTextScrollY();
3690 rangeX = getMaxTextScrollX();
3691 rangeY = getMaxTextScrollY();
3692 overflingDistance = 0;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003693 }
3694
Jonathan Dixon3c909522012-02-28 18:45:06 +00003695 mWebViewPrivate.overScrollBy(x - oldX, y - oldY, oldX, oldY,
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003696 rangeX, rangeY,
3697 overflingDistance, overflingDistance, false);
3698
3699 if (mOverScrollGlow != null) {
3700 mOverScrollGlow.absorbGlow(x, y, oldX, oldY, rangeX, rangeY);
3701 }
3702 } else {
George Mountfcff68f2012-03-20 10:21:57 -07003703 if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003704 // Update the layer position instead of WebView.
3705 scrollLayerTo(x, y);
George Mountfcff68f2012-03-20 10:21:57 -07003706 } else if (mTouchMode == TOUCH_DRAG_TEXT_MODE) {
3707 scrollEditText(x, y);
3708 } else {
3709 setScrollXRaw(x);
3710 setScrollYRaw(y);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003711 }
3712 abortAnimation();
3713 nativeSetIsScrolling(false);
3714 if (!mBlockWebkitViewMessages) {
3715 WebViewCore.resumePriority();
3716 if (!mSelectingText) {
3717 WebViewCore.resumeUpdatePicture(mWebViewCore);
3718 }
3719 }
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00003720 if (oldX != getScrollX() || oldY != getScrollY()) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003721 sendOurVisibleRect();
3722 }
3723 }
3724 } else {
Jonathan Dixon3c909522012-02-28 18:45:06 +00003725 mWebViewPrivate.super_computeScroll();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003726 }
3727 }
3728
3729 private void scrollLayerTo(int x, int y) {
George Mountbcd5dd72012-03-01 08:39:03 -08003730 int dx = mScrollingLayerRect.left - x;
3731 int dy = mScrollingLayerRect.top - y;
John Recke72620c2012-03-29 18:10:31 -07003732 if (dx == 0 && dy == 0) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003733 return;
3734 }
3735 if (mSelectingText) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003736 if (mSelectCursorBaseLayerId == mCurrentScrollingLayerId) {
3737 mSelectCursorBase.offset(dx, dy);
George Mountf9c1f992012-03-21 16:06:10 -07003738 mSelectCursorBaseTextQuad.offset(dx, dy);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003739 }
3740 if (mSelectCursorExtentLayerId == mCurrentScrollingLayerId) {
3741 mSelectCursorExtent.offset(dx, dy);
George Mountf9c1f992012-03-21 16:06:10 -07003742 mSelectCursorExtentTextQuad.offset(dx, dy);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003743 }
George Mount30d773f2012-04-20 08:34:44 -07003744 } else if (mHandleAlpha.getAlpha() > 0) {
3745 // stop fading as we're not going to move with the layer.
3746 mHandleAlphaAnimator.end();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003747 }
George Mountf70276a2012-03-12 14:22:10 -07003748 if (mAutoCompletePopup != null &&
3749 mCurrentScrollingLayerId == mEditTextLayerId) {
George Mount7102eb22012-04-10 13:41:51 -07003750 mEditTextContentBounds.offset(dx, dy);
George Mountf70276a2012-03-12 14:22:10 -07003751 mAutoCompletePopup.resetRect();
George Mountbcd5dd72012-03-01 08:39:03 -08003752 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003753 nativeScrollLayer(mCurrentScrollingLayerId, x, y);
3754 mScrollingLayerRect.left = x;
3755 mScrollingLayerRect.top = y;
3756 mWebViewCore.sendMessage(WebViewCore.EventHub.SCROLL_LAYER, mCurrentScrollingLayerId,
3757 mScrollingLayerRect);
Jonathan Dixon3c909522012-02-28 18:45:06 +00003758 mWebViewPrivate.onScrollChanged(getScrollX(), getScrollY(), getScrollX(), getScrollY());
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003759 invalidate();
3760 }
3761
3762 private static int computeDuration(int dx, int dy) {
3763 int distance = Math.max(Math.abs(dx), Math.abs(dy));
3764 int duration = distance * 1000 / STD_SPEED;
3765 return Math.min(duration, MAX_DURATION);
3766 }
3767
3768 // helper to pin the scrollBy parameters (already in view coordinates)
3769 // returns true if the scroll was changed
3770 private boolean pinScrollBy(int dx, int dy, boolean animate, int animationDuration) {
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00003771 return pinScrollTo(getScrollX() + dx, getScrollY() + dy, animate, animationDuration);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003772 }
3773 // helper to pin the scrollTo parameters (already in view coordinates)
3774 // returns true if the scroll was changed
3775 private boolean pinScrollTo(int x, int y, boolean animate, int animationDuration) {
Victoria Lease3beeb1c2012-03-09 09:03:40 -08003776 abortAnimation();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003777 x = pinLocX(x);
3778 y = pinLocY(y);
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00003779 int dx = x - getScrollX();
3780 int dy = y - getScrollY();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003781
3782 if ((dx | dy) == 0) {
3783 return false;
3784 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003785 if (animate) {
3786 // Log.d(LOGTAG, "startScroll: " + dx + " " + dy);
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00003787 mScroller.startScroll(getScrollX(), getScrollY(), dx, dy,
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003788 animationDuration > 0 ? animationDuration : computeDuration(dx, dy));
Jonathan Dixon3c909522012-02-28 18:45:06 +00003789 mWebViewPrivate.awakenScrollBars(mScroller.getDuration());
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003790 invalidate();
3791 } else {
Jonathan Dixon3c909522012-02-28 18:45:06 +00003792 mWebView.scrollTo(x, y);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003793 }
3794 return true;
3795 }
3796
3797 // Scale from content to view coordinates, and pin.
3798 // Also called by jni webview.cpp
3799 private boolean setContentScrollBy(int cx, int cy, boolean animate) {
3800 if (mDrawHistory) {
3801 // disallow WebView to change the scroll position as History Picture
3802 // is used in the view system.
3803 // TODO: as we switchOutDrawHistory when trackball or navigation
3804 // keys are hit, this should be safe. Right?
3805 return false;
3806 }
3807 cx = contentToViewDimension(cx);
3808 cy = contentToViewDimension(cy);
3809 if (mHeightCanMeasure) {
3810 // move our visible rect according to scroll request
3811 if (cy != 0) {
3812 Rect tempRect = new Rect();
3813 calcOurVisibleRect(tempRect);
3814 tempRect.offset(cx, cy);
Jonathan Dixon3c909522012-02-28 18:45:06 +00003815 mWebView.requestRectangleOnScreen(tempRect);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003816 }
3817 // FIXME: We scroll horizontally no matter what because currently
3818 // ScrollView and ListView will not scroll horizontally.
3819 // FIXME: Why do we only scroll horizontally if there is no
3820 // vertical scroll?
3821// Log.d(LOGTAG, "setContentScrollBy cy=" + cy);
3822 return cy == 0 && cx != 0 && pinScrollBy(cx, 0, animate, 0);
3823 } else {
3824 return pinScrollBy(cx, cy, animate, 0);
3825 }
3826 }
3827
3828 /**
3829 * Called by CallbackProxy when the page starts loading.
3830 * @param url The URL of the page which has started loading.
3831 */
3832 /* package */ void onPageStarted(String url) {
3833 // every time we start a new page, we want to reset the
3834 // WebView certificate: if the new site is secure, we
3835 // will reload it and get a new certificate set;
3836 // if the new site is not secure, the certificate must be
3837 // null, and that will be the case
Jonathan Dixon3c909522012-02-28 18:45:06 +00003838 mWebView.setCertificate(null);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003839
3840 // reset the flag since we set to true in if need after
3841 // loading is see onPageFinished(Url)
3842 mAccessibilityScriptInjected = false;
3843 }
3844
3845 /**
3846 * Called by CallbackProxy when the page finishes loading.
3847 * @param url The URL of the page which has finished loading.
3848 */
3849 /* package */ void onPageFinished(String url) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003850 mZoomManager.onPageFinished(url);
3851 injectAccessibilityForUrl(url);
3852 }
3853
3854 /**
3855 * This method injects accessibility in the loaded document if accessibility
3856 * is enabled. If JavaScript is enabled we try to inject a URL specific script.
3857 * If no URL specific script is found or JavaScript is disabled we fallback to
3858 * the default {@link AccessibilityInjector} implementation.
3859 * </p>
3860 * If the URL has the "axs" paramter set to 1 it has already done the
3861 * script injection so we do nothing. If the parameter is set to 0
3862 * the URL opts out accessibility script injection so we fall back to
3863 * the default {@link AccessibilityInjector}.
3864 * </p>
3865 * Note: If the user has not opted-in the accessibility script injection no scripts
3866 * are injected rather the default {@link AccessibilityInjector} implementation
3867 * is used.
3868 *
3869 * @param url The URL loaded by this {@link WebView}.
3870 */
3871 private void injectAccessibilityForUrl(String url) {
3872 if (mWebViewCore == null) {
3873 return;
3874 }
3875 AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
3876
3877 if (!accessibilityManager.isEnabled()) {
3878 // it is possible that accessibility was turned off between reloads
3879 ensureAccessibilityScriptInjectorInstance(false);
3880 return;
3881 }
3882
3883 if (!getSettings().getJavaScriptEnabled()) {
3884 // no JS so we fallback to the basic buil-in support
3885 ensureAccessibilityScriptInjectorInstance(true);
3886 return;
3887 }
3888
3889 // check the URL "axs" parameter to choose appropriate action
3890 int axsParameterValue = getAxsUrlParameterValue(url);
3891 if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED) {
3892 boolean onDeviceScriptInjectionEnabled = (Settings.Secure.getInt(mContext
3893 .getContentResolver(), Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1);
3894 if (onDeviceScriptInjectionEnabled) {
3895 ensureAccessibilityScriptInjectorInstance(false);
3896 // neither script injected nor script injection opted out => we inject
Jonathan Dixon3c909522012-02-28 18:45:06 +00003897 mWebView.loadUrl(getScreenReaderInjectingJs());
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003898 // TODO: Set this flag after successfull script injection. Maybe upon injection
3899 // the chooser should update the meta tag and we check it to declare success
3900 mAccessibilityScriptInjected = true;
3901 } else {
3902 // injection disabled so we fallback to the basic built-in support
3903 ensureAccessibilityScriptInjectorInstance(true);
3904 }
3905 } else if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT) {
3906 // injection opted out so we fallback to the basic buil-in support
3907 ensureAccessibilityScriptInjectorInstance(true);
3908 } else if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED) {
3909 ensureAccessibilityScriptInjectorInstance(false);
3910 // the URL provides accessibility but we still need to add our generic script
Jonathan Dixon3c909522012-02-28 18:45:06 +00003911 mWebView.loadUrl(getScreenReaderInjectingJs());
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003912 } else {
3913 Log.e(LOGTAG, "Unknown URL value for the \"axs\" URL parameter: " + axsParameterValue);
3914 }
3915 }
3916
3917 /**
3918 * Ensures the instance of the {@link AccessibilityInjector} to be present ot not.
3919 *
3920 * @param present True to ensure an insance, false to ensure no instance.
3921 */
3922 private void ensureAccessibilityScriptInjectorInstance(boolean present) {
3923 if (present) {
3924 if (mAccessibilityInjector == null) {
3925 mAccessibilityInjector = new AccessibilityInjector(this);
3926 }
3927 } else {
3928 mAccessibilityInjector = null;
3929 }
3930 }
3931
3932 /**
3933 * Gets JavaScript that injects a screen-reader.
3934 *
3935 * @return The JavaScript snippet.
3936 */
3937 private String getScreenReaderInjectingJs() {
3938 String screenReaderUrl = Settings.Secure.getString(mContext.getContentResolver(),
3939 Settings.Secure.ACCESSIBILITY_SCREEN_READER_URL);
3940 return String.format(ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE, screenReaderUrl);
3941 }
3942
3943 /**
3944 * Gets the "axs" URL parameter value.
3945 *
3946 * @param url A url to fetch the paramter from.
3947 * @return The parameter value if such, -1 otherwise.
3948 */
3949 private int getAxsUrlParameterValue(String url) {
3950 if (mMatchAxsUrlParameterPattern == null) {
3951 mMatchAxsUrlParameterPattern = Pattern.compile(PATTERN_MATCH_AXS_URL_PARAMETER);
3952 }
3953 Matcher matcher = mMatchAxsUrlParameterPattern.matcher(url);
3954 if (matcher.find()) {
3955 String keyValuePair = url.substring(matcher.start(), matcher.end());
3956 return Integer.parseInt(keyValuePair.split("=")[1]);
3957 }
3958 return -1;
3959 }
3960
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003961 // scale from content to view coordinates, and pin
John Recka60a1892012-04-19 10:48:20 -07003962 private void contentScrollTo(int cx, int cy, boolean animate) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003963 if (mDrawHistory) {
3964 // disallow WebView to change the scroll position as History Picture
3965 // is used in the view system.
3966 return;
3967 }
John Recka60a1892012-04-19 10:48:20 -07003968 int vx = contentToViewX(cx);
3969 int vy = contentToViewY(cy);
3970 pinScrollTo(vx, vy, animate, 0);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003971 }
3972
3973 /**
3974 * These are from webkit, and are in content coordinate system (unzoomed)
3975 */
3976 private void contentSizeChanged(boolean updateLayout) {
3977 // suppress 0,0 since we usually see real dimensions soon after
3978 // this avoids drawing the prev content in a funny place. If we find a
3979 // way to consolidate these notifications, this check may become
3980 // obsolete
3981 if ((mContentWidth | mContentHeight) == 0) {
3982 return;
3983 }
3984
3985 if (mHeightCanMeasure) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00003986 if (mWebView.getMeasuredHeight() != contentToViewDimension(mContentHeight)
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003987 || updateLayout) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00003988 mWebView.requestLayout();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003989 }
3990 } else if (mWidthCanMeasure) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00003991 if (mWebView.getMeasuredWidth() != contentToViewDimension(mContentWidth)
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003992 || updateLayout) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00003993 mWebView.requestLayout();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08003994 }
3995 } else {
3996 // If we don't request a layout, try to send our view size to the
3997 // native side to ensure that WebCore has the correct dimensions.
3998 sendViewSizeZoom(false);
3999 }
4000 }
4001
4002 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00004003 * See {@link WebView#setWebViewClient(WebViewClient)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004004 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00004005 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004006 public void setWebViewClient(WebViewClient client) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004007 mCallbackProxy.setWebViewClient(client);
4008 }
4009
4010 /**
4011 * Gets the WebViewClient
4012 * @return the current WebViewClient instance.
4013 *
Jonathan Dixoncd93e152012-03-02 19:19:44 +00004014 * This is an implementation detail.
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004015 */
4016 public WebViewClient getWebViewClient() {
4017 return mCallbackProxy.getWebViewClient();
4018 }
4019
4020 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00004021 * See {@link WebView#setDownloadListener(DownloadListener)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004022 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00004023 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004024 public void setDownloadListener(DownloadListener listener) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004025 mCallbackProxy.setDownloadListener(listener);
4026 }
4027
4028 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00004029 * See {@link WebView#setWebChromeClient(WebChromeClient)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004030 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00004031 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004032 public void setWebChromeClient(WebChromeClient client) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004033 mCallbackProxy.setWebChromeClient(client);
4034 }
4035
4036 /**
4037 * Gets the chrome handler.
4038 * @return the current WebChromeClient instance.
4039 *
Jonathan Dixoncd93e152012-03-02 19:19:44 +00004040 * This is an implementation detail.
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004041 */
4042 public WebChromeClient getWebChromeClient() {
4043 return mCallbackProxy.getWebChromeClient();
4044 }
4045
4046 /**
4047 * Set the back/forward list client. This is an implementation of
4048 * WebBackForwardListClient for handling new items and changes in the
4049 * history index.
4050 * @param client An implementation of WebBackForwardListClient.
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004051 */
4052 public void setWebBackForwardListClient(WebBackForwardListClient client) {
4053 mCallbackProxy.setWebBackForwardListClient(client);
4054 }
4055
4056 /**
4057 * Gets the WebBackForwardListClient.
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004058 */
4059 public WebBackForwardListClient getWebBackForwardListClient() {
4060 return mCallbackProxy.getWebBackForwardListClient();
4061 }
4062
4063 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00004064 * See {@link WebView#setPictureListener(PictureListener)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004065 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00004066 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004067 @Deprecated
4068 public void setPictureListener(PictureListener listener) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004069 mPictureListener = listener;
4070 }
4071
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004072 /* FIXME: Debug only! Remove for SDK! */
4073 public void externalRepresentation(Message callback) {
4074 mWebViewCore.sendMessage(EventHub.REQUEST_EXT_REPRESENTATION, callback);
4075 }
4076
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004077 /* FIXME: Debug only! Remove for SDK! */
4078 public void documentAsText(Message callback) {
4079 mWebViewCore.sendMessage(EventHub.REQUEST_DOC_AS_TEXT, callback);
4080 }
4081
4082 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00004083 * See {@link WebView#addJavascriptInterface(Object, String)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004084 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00004085 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004086 public void addJavascriptInterface(Object object, String name) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004087 if (object == null) {
4088 return;
4089 }
4090 WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData();
4091 arg.mObject = object;
4092 arg.mInterfaceName = name;
4093 mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg);
4094 }
4095
4096 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00004097 * See {@link WebView#removeJavascriptInterface(String)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004098 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00004099 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004100 public void removeJavascriptInterface(String interfaceName) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004101 if (mWebViewCore != null) {
4102 WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData();
4103 arg.mInterfaceName = interfaceName;
4104 mWebViewCore.sendMessage(EventHub.REMOVE_JS_INTERFACE, arg);
4105 }
4106 }
4107
4108 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00004109 * See {@link WebView#getSettings()}
4110 * Note this returns WebSettingsClassic, a sub-class of WebSettings, which can be used
4111 * to access extension APIs.
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004112 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00004113 @Override
Jonathan Dixon3c909522012-02-28 18:45:06 +00004114 public WebSettingsClassic getSettings() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004115 return (mWebViewCore != null) ? mWebViewCore.getSettings() : null;
4116 }
4117
Jonathan Dixon19b80112012-03-02 18:18:00 +00004118 /**
4119 * See {@link WebView#getPluginList()}
4120 */
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004121 @Deprecated
4122 public static synchronized PluginList getPluginList() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004123 return new PluginList();
4124 }
4125
Jonathan Dixon19b80112012-03-02 18:18:00 +00004126 /**
4127 * See {@link WebView#refreshPlugins(boolean)}
4128 */
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004129 @Deprecated
4130 public void refreshPlugins(boolean reloadOpenPages) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004131 }
4132
4133 //-------------------------------------------------------------------------
4134 // Override View methods
4135 //-------------------------------------------------------------------------
4136
4137 @Override
4138 protected void finalize() throws Throwable {
4139 try {
4140 if (mNativeClass != 0) {
4141 mPrivateHandler.post(new Runnable() {
4142 @Override
4143 public void run() {
4144 destroy();
4145 }
4146 });
4147 }
4148 } finally {
4149 super.finalize();
4150 }
4151 }
4152
John Reckb2676f72012-03-02 14:13:06 -08004153 private void drawContent(Canvas canvas) {
4154 if (mDrawHistory) {
4155 canvas.scale(mZoomManager.getScale(), mZoomManager.getScale());
4156 canvas.drawPicture(mHistoryPicture);
4157 return;
4158 }
4159 if (mNativeClass == 0) return;
4160
4161 boolean animateZoom = mZoomManager.isFixedLengthAnimationInProgress();
4162 boolean animateScroll = ((!mScroller.isFinished()
4163 || mVelocityTracker != null)
4164 && (mTouchMode != TOUCH_DRAG_MODE ||
Jeff Brown9d3bdbd2012-03-21 11:50:06 -07004165 mHeldMotionless != MOTIONLESS_TRUE));
John Reckb2676f72012-03-02 14:13:06 -08004166 if (mTouchMode == TOUCH_DRAG_MODE) {
4167 if (mHeldMotionless == MOTIONLESS_PENDING) {
4168 mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
4169 mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
4170 mHeldMotionless = MOTIONLESS_FALSE;
4171 }
4172 if (mHeldMotionless == MOTIONLESS_FALSE) {
4173 mPrivateHandler.sendMessageDelayed(mPrivateHandler
4174 .obtainMessage(DRAG_HELD_MOTIONLESS), MOTIONLESS_TIME);
4175 mPrivateHandler.sendMessageDelayed(mPrivateHandler
4176 .obtainMessage(AWAKEN_SCROLL_BARS),
4177 ViewConfiguration.getScrollDefaultDelay());
4178 mHeldMotionless = MOTIONLESS_PENDING;
4179 }
4180 }
4181 int saveCount = canvas.save();
4182 if (animateZoom) {
4183 mZoomManager.animateZoom(canvas);
4184 } else if (!canvas.isHardwareAccelerated()) {
4185 canvas.scale(mZoomManager.getScale(), mZoomManager.getScale());
4186 }
4187
4188 boolean UIAnimationsRunning = false;
4189 // Currently for each draw we compute the animation values;
4190 // We may in the future decide to do that independently.
4191 if (mNativeClass != 0 && !canvas.isHardwareAccelerated()
4192 && nativeEvaluateLayersAnimations(mNativeClass)) {
4193 UIAnimationsRunning = true;
4194 // If we have unfinished (or unstarted) animations,
4195 // we ask for a repaint. We only need to do this in software
4196 // rendering (with hardware rendering we already have a different
4197 // method of requesting a repaint)
4198 mWebViewCore.sendMessage(EventHub.NOTIFY_ANIMATION_STARTED);
4199 invalidate();
4200 }
4201
4202 // decide which adornments to draw
4203 int extras = DRAW_EXTRAS_NONE;
4204 if (!mFindIsUp && mSelectingText) {
4205 extras = DRAW_EXTRAS_SELECTION;
4206 }
4207
4208 calcOurContentVisibleRectF(mVisibleContentRect);
4209 if (canvas.isHardwareAccelerated()) {
4210 Rect glRectViewport = mGLViewportEmpty ? null : mGLRectViewport;
4211 Rect viewRectViewport = mGLViewportEmpty ? null : mViewRectViewport;
4212
Romain Guyd4caee02012-04-23 20:44:04 -07004213 int functor = nativeCreateDrawGLFunction(mNativeClass, glRectViewport,
John Reckb2676f72012-03-02 14:13:06 -08004214 viewRectViewport, mVisibleContentRect, getScale(), extras);
4215 ((HardwareCanvas) canvas).callDrawGLFunction(functor);
4216 if (mHardwareAccelSkia != getSettings().getHardwareAccelSkiaEnabled()) {
4217 mHardwareAccelSkia = getSettings().getHardwareAccelSkiaEnabled();
4218 nativeUseHardwareAccelSkia(mHardwareAccelSkia);
4219 }
4220
4221 } else {
4222 DrawFilter df = null;
4223 if (mZoomManager.isZoomAnimating() || UIAnimationsRunning) {
4224 df = mZoomFilter;
4225 } else if (animateScroll) {
4226 df = mScrollFilter;
4227 }
4228 canvas.setDrawFilter(df);
4229 // XXX: Revisit splitting content. Right now it causes a
4230 // synchronization problem with layers.
4231 int content = nativeDraw(canvas, mVisibleContentRect, mBackgroundColor,
4232 extras, false);
4233 canvas.setDrawFilter(null);
4234 if (!mBlockWebkitViewMessages && content != 0) {
4235 mWebViewCore.sendMessage(EventHub.SPLIT_PICTURE_SET, content, 0);
4236 }
4237 }
4238
4239 canvas.restoreToCount(saveCount);
George Mount30d773f2012-04-20 08:34:44 -07004240 drawTextSelectionHandles(canvas);
John Reckb2676f72012-03-02 14:13:06 -08004241
4242 if (extras == DRAW_EXTRAS_CURSOR_RING) {
4243 if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
4244 mTouchMode = TOUCH_SHORTPRESS_MODE;
4245 }
4246 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004247 }
4248
4249 /**
4250 * Draw the background when beyond bounds
4251 * @param canvas Canvas to draw into
4252 */
4253 private void drawOverScrollBackground(Canvas canvas) {
4254 if (mOverScrollBackground == null) {
4255 mOverScrollBackground = new Paint();
4256 Bitmap bm = BitmapFactory.decodeResource(
4257 mContext.getResources(),
4258 com.android.internal.R.drawable.status_bar_background);
4259 mOverScrollBackground.setShader(new BitmapShader(bm,
4260 Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
4261 mOverScrollBorder = new Paint();
4262 mOverScrollBorder.setStyle(Paint.Style.STROKE);
4263 mOverScrollBorder.setStrokeWidth(0);
4264 mOverScrollBorder.setColor(0xffbbbbbb);
4265 }
4266
4267 int top = 0;
4268 int right = computeRealHorizontalScrollRange();
4269 int bottom = top + computeRealVerticalScrollRange();
4270 // first draw the background and anchor to the top of the view
4271 canvas.save();
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00004272 canvas.translate(getScrollX(), getScrollY());
4273 canvas.clipRect(-getScrollX(), top - getScrollY(), right - getScrollX(), bottom
4274 - getScrollY(), Region.Op.DIFFERENCE);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004275 canvas.drawPaint(mOverScrollBackground);
4276 canvas.restore();
4277 // then draw the border
4278 canvas.drawRect(-1, top - 1, right, bottom, mOverScrollBorder);
4279 // next clip the region for the content
4280 canvas.clipRect(0, top, right, bottom);
4281 }
4282
4283 @Override
Jonathan Dixon3c909522012-02-28 18:45:06 +00004284 public void onDraw(Canvas canvas) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004285 if (inFullScreenMode()) {
4286 return; // no need to draw anything if we aren't visible.
4287 }
4288 // if mNativeClass is 0, the WebView is either destroyed or not
4289 // initialized. In either case, just draw the background color and return
4290 if (mNativeClass == 0) {
4291 canvas.drawColor(mBackgroundColor);
4292 return;
4293 }
4294
4295 // if both mContentWidth and mContentHeight are 0, it means there is no
4296 // valid Picture passed to WebView yet. This can happen when WebView
4297 // just starts. Draw the background and return.
4298 if ((mContentWidth | mContentHeight) == 0 && mHistoryPicture == null) {
4299 canvas.drawColor(mBackgroundColor);
4300 return;
4301 }
4302
4303 if (canvas.isHardwareAccelerated()) {
4304 mZoomManager.setHardwareAccelerated();
4305 } else {
4306 mWebViewCore.resumeWebKitDraw();
4307 }
4308
4309 int saveCount = canvas.save();
4310 if (mInOverScrollMode && !getSettings()
4311 .getUseWebViewBackgroundForOverscrollBackground()) {
4312 drawOverScrollBackground(canvas);
4313 }
Michael Kolb8116da52012-04-02 16:16:16 -07004314
4315 canvas.translate(0, getTitleHeight());
John Reckb2676f72012-03-02 14:13:06 -08004316 drawContent(canvas);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004317 canvas.restoreToCount(saveCount);
4318
4319 if (AUTO_REDRAW_HACK && mAutoRedraw) {
4320 invalidate();
4321 }
4322 mWebViewCore.signalRepaintDone();
4323
4324 if (mOverScrollGlow != null && mOverScrollGlow.drawEdgeGlows(canvas)) {
4325 invalidate();
4326 }
4327
4328 if (mFocusTransition != null) {
4329 mFocusTransition.draw(canvas);
4330 } else if (shouldDrawHighlightRect()) {
4331 RegionIterator iter = new RegionIterator(mTouchHighlightRegion);
4332 Rect r = new Rect();
4333 while (iter.next(r)) {
4334 canvas.drawRect(r, mTouchHightlightPaint);
4335 }
4336 }
4337 if (DEBUG_TOUCH_HIGHLIGHT) {
4338 if (getSettings().getNavDump()) {
4339 if ((mTouchHighlightX | mTouchHighlightY) != 0) {
4340 if (mTouchCrossHairColor == null) {
4341 mTouchCrossHairColor = new Paint();
4342 mTouchCrossHairColor.setColor(Color.RED);
4343 }
4344 canvas.drawLine(mTouchHighlightX - mNavSlop,
4345 mTouchHighlightY - mNavSlop, mTouchHighlightX
4346 + mNavSlop + 1, mTouchHighlightY + mNavSlop
4347 + 1, mTouchCrossHairColor);
4348 canvas.drawLine(mTouchHighlightX + mNavSlop + 1,
4349 mTouchHighlightY - mNavSlop, mTouchHighlightX
4350 - mNavSlop,
4351 mTouchHighlightY + mNavSlop + 1,
4352 mTouchCrossHairColor);
4353 }
4354 }
4355 }
4356 }
4357
4358 private void removeTouchHighlight() {
4359 mWebViewCore.removeMessages(EventHub.HIT_TEST);
4360 mPrivateHandler.removeMessages(HIT_TEST_RESULT);
4361 setTouchHighlightRects(null);
4362 }
4363
4364 @Override
4365 public void setLayoutParams(ViewGroup.LayoutParams params) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00004366 if (params.height == AbsoluteLayout.LayoutParams.WRAP_CONTENT) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004367 mWrapContent = true;
4368 }
Jonathan Dixon3c909522012-02-28 18:45:06 +00004369 mWebViewPrivate.super_setLayoutParams(params);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004370 }
4371
4372 @Override
4373 public boolean performLongClick() {
4374 // performLongClick() is the result of a delayed message. If we switch
4375 // to windows overview, the WebView will be temporarily removed from the
4376 // view system. In that case, do nothing.
Jonathan Dixon3c909522012-02-28 18:45:06 +00004377 if (mWebView.getParent() == null) return false;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004378
4379 // A multi-finger gesture can look like a long press; make sure we don't take
4380 // long press actions if we're scaling.
4381 final ScaleGestureDetector detector = mZoomManager.getMultiTouchGestureDetector();
4382 if (detector != null && detector.isInProgress()) {
4383 return false;
4384 }
4385
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004386 if (mSelectingText) return false; // long click does nothing on selection
4387 /* if long click brings up a context menu, the super function
4388 * returns true and we're done. Otherwise, nothing happened when
4389 * the user clicked. */
Jonathan Dixon3c909522012-02-28 18:45:06 +00004390 if (mWebViewPrivate.super_performLongClick()) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004391 return true;
4392 }
4393 /* In the case where the application hasn't already handled the long
4394 * click action, look for a word under the click. If one is found,
4395 * animate the text selection into view.
4396 * FIXME: no animation code yet */
4397 final boolean isSelecting = selectText();
4398 if (isSelecting) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00004399 mWebView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004400 } else if (focusCandidateIsEditableText()) {
4401 mSelectCallback = new SelectActionModeCallback();
4402 mSelectCallback.setWebView(this);
4403 mSelectCallback.setTextSelected(false);
Jonathan Dixon3c909522012-02-28 18:45:06 +00004404 mWebView.startActionMode(mSelectCallback);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004405 }
4406 return isSelecting;
4407 }
4408
4409 /**
4410 * Select the word at the last click point.
4411 *
Jonathan Dixoncd93e152012-03-02 19:19:44 +00004412 * This is an implementation detail.
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004413 */
4414 public boolean selectText() {
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00004415 int x = viewToContentX(mLastTouchX + getScrollX());
4416 int y = viewToContentY(mLastTouchY + getScrollY());
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004417 return selectText(x, y);
4418 }
4419
4420 /**
4421 * Select the word at the indicated content coordinates.
4422 */
4423 boolean selectText(int x, int y) {
George Mount07c9e292012-04-13 13:09:28 -07004424 if (mWebViewCore == null) {
4425 return false;
4426 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004427 mWebViewCore.sendMessage(EventHub.SELECT_WORD_AT, x, y);
4428 return true;
4429 }
4430
4431 private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
4432
4433 @Override
Jonathan Dixon3c909522012-02-28 18:45:06 +00004434 public void onConfigurationChanged(Configuration newConfig) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004435 mCachedOverlappingActionModeHeight = -1;
4436 if (mSelectingText && mOrientation != newConfig.orientation) {
4437 selectionDone();
4438 }
4439 mOrientation = newConfig.orientation;
4440 if (mWebViewCore != null && !mBlockWebkitViewMessages) {
4441 mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT);
4442 }
4443 }
4444
4445 /**
4446 * Keep track of the Callback so we can end its ActionMode or remove its
4447 * titlebar.
4448 */
4449 private SelectActionModeCallback mSelectCallback;
4450
Chris Craikc2c95432012-04-25 15:13:52 -07004451 void setBaseLayer(int layer, boolean showVisualIndicator,
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004452 boolean isPictureAfterFirstLayout) {
4453 if (mNativeClass == 0)
4454 return;
4455 boolean queueFull;
Chris Craikc2c95432012-04-25 15:13:52 -07004456 queueFull = nativeSetBaseLayer(mNativeClass, layer,
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004457 showVisualIndicator, isPictureAfterFirstLayout);
4458
John Reck834f66b2012-03-27 12:52:57 -07004459 if (queueFull) {
Chris Craik07b20112012-03-07 11:55:57 -08004460 mWebViewCore.pauseWebKitDraw();
John Reck834f66b2012-03-27 12:52:57 -07004461 } else {
4462 mWebViewCore.resumeWebKitDraw();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004463 }
4464
4465 if (mHTML5VideoViewProxy != null) {
4466 mHTML5VideoViewProxy.setBaseLayer(layer);
4467 }
4468 }
4469
4470 int getBaseLayer() {
4471 if (mNativeClass == 0) {
4472 return 0;
4473 }
4474 return nativeGetBaseLayer();
4475 }
4476
4477 private void onZoomAnimationStart() {
George Mount30d773f2012-04-20 08:34:44 -07004478 if (!mSelectingText && mHandleAlpha.getAlpha() > 0) {
4479 mHandleAlphaAnimator.end();
4480 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004481 }
4482
4483 private void onZoomAnimationEnd() {
George Mountbcd5dd72012-03-01 08:39:03 -08004484 mPrivateHandler.sendEmptyMessage(RELOCATE_AUTO_COMPLETE_POPUP);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004485 }
4486
4487 void onFixedLengthZoomAnimationStart() {
4488 WebViewCore.pauseUpdatePicture(getWebViewCore());
4489 onZoomAnimationStart();
4490 }
4491
4492 void onFixedLengthZoomAnimationEnd() {
4493 if (!mBlockWebkitViewMessages && !mSelectingText) {
4494 WebViewCore.resumeUpdatePicture(mWebViewCore);
4495 }
4496 onZoomAnimationEnd();
4497 }
4498
4499 private static final int ZOOM_BITS = Paint.FILTER_BITMAP_FLAG |
4500 Paint.DITHER_FLAG |
4501 Paint.SUBPIXEL_TEXT_FLAG;
4502 private static final int SCROLL_BITS = Paint.FILTER_BITMAP_FLAG |
4503 Paint.DITHER_FLAG;
4504
4505 private final DrawFilter mZoomFilter =
4506 new PaintFlagsDrawFilter(ZOOM_BITS, Paint.LINEAR_TEXT_FLAG);
4507 // If we need to trade better quality for speed, set mScrollFilter to null
4508 private final DrawFilter mScrollFilter =
4509 new PaintFlagsDrawFilter(SCROLL_BITS, 0);
4510
George Mount30d773f2012-04-20 08:34:44 -07004511 private class SelectionHandleAlpha {
4512 private int mAlpha = 0;
4513 public void setAlpha(int alpha) {
4514 mAlpha = alpha;
4515 if (mSelectHandleCenter != null) {
4516 mSelectHandleCenter.setAlpha(alpha);
4517 mSelectHandleLeft.setAlpha(alpha);
4518 mSelectHandleRight.setAlpha(alpha);
4519 // TODO: Use partial invalidate
4520 invalidate();
4521 }
4522 }
4523
4524 public int getAlpha() {
4525 return mAlpha;
4526 }
4527
4528 }
4529
4530 private void startSelectingText() {
4531 mSelectingText = true;
4532 mHandleAlphaAnimator.setIntValues(255);
4533 mHandleAlphaAnimator.start();
4534 }
4535 private void endSelectingText() {
4536 mSelectingText = false;
4537 mHandleAlphaAnimator.setIntValues(0);
4538 mHandleAlphaAnimator.start();
4539 }
4540
George Mount9a676bf2012-03-01 16:28:12 -08004541 private void ensureSelectionHandles() {
4542 if (mSelectHandleCenter == null) {
4543 mSelectHandleCenter = mContext.getResources().getDrawable(
4544 com.android.internal.R.drawable.text_select_handle_middle);
4545 mSelectHandleLeft = mContext.getResources().getDrawable(
4546 com.android.internal.R.drawable.text_select_handle_left);
4547 mSelectHandleRight = mContext.getResources().getDrawable(
4548 com.android.internal.R.drawable.text_select_handle_right);
George Mount30d773f2012-04-20 08:34:44 -07004549 mHandleAlpha.setAlpha(mHandleAlpha.getAlpha());
George Mountf9c1f992012-03-21 16:06:10 -07004550 mSelectHandleCenterOffset = new Point(0,
4551 -mSelectHandleCenter.getIntrinsicHeight());
4552 mSelectHandleLeftOffset = new Point(0,
4553 -mSelectHandleLeft.getIntrinsicHeight());
4554 mSelectHandleRightOffset = new Point(
4555 -mSelectHandleLeft.getIntrinsicWidth() / 2,
4556 -mSelectHandleRight.getIntrinsicHeight());
George Mount9a676bf2012-03-01 16:28:12 -08004557 }
4558 }
4559
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004560 private void drawTextSelectionHandles(Canvas canvas) {
George Mount30d773f2012-04-20 08:34:44 -07004561 if (mHandleAlpha.getAlpha() == 0) {
4562 return;
4563 }
George Mount9a676bf2012-03-01 16:28:12 -08004564 ensureSelectionHandles();
George Mount30d773f2012-04-20 08:34:44 -07004565 if (mSelectingText) {
4566 int[] handles = new int[4];
4567 getSelectionHandles(handles);
4568 int start_x = contentToViewDimension(handles[0]);
4569 int start_y = contentToViewDimension(handles[1]);
4570 int end_x = contentToViewDimension(handles[2]);
4571 int end_y = contentToViewDimension(handles[3]);
4572
4573 if (mIsCaretSelection) {
4574 // Caret handle is centered
4575 start_x -= (mSelectHandleCenter.getIntrinsicWidth() / 2);
4576 mSelectHandleCenter.setBounds(start_x, start_y,
4577 start_x + mSelectHandleCenter.getIntrinsicWidth(),
4578 start_y + mSelectHandleCenter.getIntrinsicHeight());
4579 } else {
4580 // Magic formula copied from TextView
4581 start_x -= (mSelectHandleLeft.getIntrinsicWidth() * 3) / 4;
4582 mSelectHandleLeft.setBounds(start_x, start_y,
4583 start_x + mSelectHandleLeft.getIntrinsicWidth(),
4584 start_y + mSelectHandleLeft.getIntrinsicHeight());
4585 end_x -= mSelectHandleRight.getIntrinsicWidth() / 4;
4586 mSelectHandleRight.setBounds(end_x, end_y,
4587 end_x + mSelectHandleRight.getIntrinsicWidth(),
4588 end_y + mSelectHandleRight.getIntrinsicHeight());
4589 }
4590 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004591
4592 if (mIsCaretSelection) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004593 mSelectHandleCenter.draw(canvas);
4594 } else {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004595 mSelectHandleLeft.draw(canvas);
4596 mSelectHandleRight.draw(canvas);
4597 }
4598 }
4599
4600 /**
4601 * Takes an int[4] array as an output param with the values being
4602 * startX, startY, endX, endY
4603 */
4604 private void getSelectionHandles(int[] handles) {
George Mountf9c1f992012-03-21 16:06:10 -07004605 handles[0] = mSelectCursorBase.x;
4606 handles[1] = mSelectCursorBase.y;
4607 handles[2] = mSelectCursorExtent.x;
4608 handles[3] = mSelectCursorExtent.y;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004609 if (!nativeIsBaseFirst(mNativeClass)) {
4610 int swap = handles[0];
4611 handles[0] = handles[2];
4612 handles[2] = swap;
4613 swap = handles[1];
4614 handles[1] = handles[3];
4615 handles[3] = swap;
4616 }
4617 }
4618
4619 // draw history
4620 private boolean mDrawHistory = false;
4621 private Picture mHistoryPicture = null;
4622 private int mHistoryWidth = 0;
4623 private int mHistoryHeight = 0;
4624
4625 // Only check the flag, can be called from WebCore thread
4626 boolean drawHistory() {
4627 return mDrawHistory;
4628 }
4629
4630 int getHistoryPictureWidth() {
4631 return (mHistoryPicture != null) ? mHistoryPicture.getWidth() : 0;
4632 }
4633
4634 // Should only be called in UI thread
4635 void switchOutDrawHistory() {
4636 if (null == mWebViewCore) return; // CallbackProxy may trigger this
4637 if (mDrawHistory && (getProgress() == 100 || nativeHasContent())) {
4638 mDrawHistory = false;
4639 mHistoryPicture = null;
4640 invalidate();
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00004641 int oldScrollX = getScrollX();
4642 int oldScrollY = getScrollY();
4643 setScrollXRaw(pinLocX(getScrollX()));
4644 setScrollYRaw(pinLocY(getScrollY()));
4645 if (oldScrollX != getScrollX() || oldScrollY != getScrollY()) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00004646 mWebViewPrivate.onScrollChanged(getScrollX(), getScrollY(), oldScrollX, oldScrollY);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004647 } else {
4648 sendOurVisibleRect();
4649 }
4650 }
4651 }
4652
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004653 /**
4654 * Delete text from start to end in the focused textfield. If there is no
4655 * focus, or if start == end, silently fail. If start and end are out of
4656 * order, swap them.
4657 * @param start Beginning of selection to delete.
4658 * @param end End of selection to delete.
4659 */
4660 /* package */ void deleteSelection(int start, int end) {
4661 mTextGeneration++;
4662 WebViewCore.TextSelectionData data
4663 = new WebViewCore.TextSelectionData(start, end, 0);
4664 mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, mTextGeneration, 0,
4665 data);
4666 }
4667
4668 /**
4669 * Set the selection to (start, end) in the focused textfield. If start and
4670 * end are out of order, swap them.
4671 * @param start Beginning of selection.
4672 * @param end End of selection.
4673 */
4674 /* package */ void setSelection(int start, int end) {
4675 if (mWebViewCore != null) {
4676 mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end);
4677 }
4678 }
4679
4680 @Override
4681 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
4682 if (mInputConnection == null) {
4683 mInputConnection = new WebViewInputConnection();
George Mountbcd5dd72012-03-01 08:39:03 -08004684 mAutoCompletePopup = new AutoCompletePopup(mContext, this,
4685 mInputConnection);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004686 }
4687 mInputConnection.setupEditorInfo(outAttrs);
4688 return mInputConnection;
4689 }
4690
George Mountbcd5dd72012-03-01 08:39:03 -08004691 private void relocateAutoCompletePopup() {
4692 if (mAutoCompletePopup != null) {
4693 mAutoCompletePopup.resetRect();
4694 mAutoCompletePopup.setText(mInputConnection.getEditable());
4695 }
4696 }
4697
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004698 /**
4699 * Called in response to a message from webkit telling us that the soft
4700 * keyboard should be launched.
4701 */
4702 private void displaySoftKeyboard(boolean isTextView) {
4703 InputMethodManager imm = (InputMethodManager)
Jonathan Dixon3c909522012-02-28 18:45:06 +00004704 mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004705
4706 // bring it back to the default level scale so that user can enter text
4707 boolean zoom = mZoomManager.getScale() < mZoomManager.getDefaultScale();
4708 if (zoom) {
4709 mZoomManager.setZoomCenter(mLastTouchX, mLastTouchY);
4710 mZoomManager.setZoomScale(mZoomManager.getDefaultScale(), false);
4711 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004712 // Used by plugins and contentEditable.
4713 // Also used if the navigation cache is out of date, and
4714 // does not recognize that a textfield is in focus. In that
4715 // case, use WebView as the targeted view.
4716 // see http://b/issue?id=2457459
Jonathan Dixon3c909522012-02-28 18:45:06 +00004717 imm.showSoftInput(mWebView, 0);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004718 }
4719
4720 // Called by WebKit to instruct the UI to hide the keyboard
4721 private void hideSoftKeyboard() {
4722 InputMethodManager imm = InputMethodManager.peekInstance();
John Reckb2676f72012-03-02 14:13:06 -08004723 if (imm != null && (imm.isActive(mWebView))) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00004724 imm.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004725 }
4726 }
4727
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004728 /**
George Mountbcd5dd72012-03-01 08:39:03 -08004729 * Called by AutoCompletePopup to find saved form data associated with the
4730 * textfield
4731 * @param name Name of the textfield.
4732 * @param nodePointer Pointer to the node of the textfield, so it can be
4733 * compared to the currently focused textfield when the data is
4734 * retrieved.
4735 * @param autoFillable true if WebKit has determined this field is part of
4736 * a form that can be auto filled.
4737 * @param autoComplete true if the attribute "autocomplete" is set to true
4738 * on the textfield.
4739 */
4740 /* package */ void requestFormData(String name, int nodePointer,
4741 boolean autoFillable, boolean autoComplete) {
4742 if (mWebViewCore.getSettings().getSaveFormData()) {
4743 Message update = mPrivateHandler.obtainMessage(REQUEST_FORM_DATA);
4744 update.arg1 = nodePointer;
4745 RequestFormData updater = new RequestFormData(name, getUrl(),
4746 update, autoFillable, autoComplete);
4747 Thread t = new Thread(updater);
4748 t.start();
4749 }
4750 }
4751
4752 /*
4753 * This class requests an Adapter for the AutoCompletePopup which shows past
4754 * entries stored in the database. It is a Runnable so that it can be done
4755 * in its own thread, without slowing down the UI.
4756 */
4757 private class RequestFormData implements Runnable {
4758 private String mName;
4759 private String mUrl;
4760 private Message mUpdateMessage;
4761 private boolean mAutoFillable;
4762 private boolean mAutoComplete;
4763 private WebSettingsClassic mWebSettings;
4764
4765 public RequestFormData(String name, String url, Message msg,
4766 boolean autoFillable, boolean autoComplete) {
4767 mName = name;
4768 mUrl = WebTextView.urlForAutoCompleteData(url);
4769 mUpdateMessage = msg;
4770 mAutoFillable = autoFillable;
4771 mAutoComplete = autoComplete;
4772 mWebSettings = getSettings();
4773 }
4774
4775 @Override
4776 public void run() {
4777 ArrayList<String> pastEntries = new ArrayList<String>();
4778
4779 if (mAutoFillable) {
4780 // Note that code inside the adapter click handler in AutoCompletePopup depends
4781 // on the AutoFill item being at the top of the drop down list. If you change
4782 // the order, make sure to do it there too!
4783 if (mWebSettings != null && mWebSettings.getAutoFillProfile() != null) {
4784 pastEntries.add(mWebView.getResources().getText(
4785 com.android.internal.R.string.autofill_this_form).toString() +
4786 " " +
4787 mAutoFillData.getPreviewString());
4788 mAutoCompletePopup.setIsAutoFillProfileSet(true);
4789 } else {
4790 // There is no autofill profile set up yet, so add an option that
4791 // will invite the user to set their profile up.
4792 pastEntries.add(mWebView.getResources().getText(
4793 com.android.internal.R.string.setup_autofill).toString());
4794 mAutoCompletePopup.setIsAutoFillProfileSet(false);
4795 }
4796 }
4797
4798 if (mAutoComplete) {
4799 pastEntries.addAll(mDatabase.getFormData(mUrl, mName));
4800 }
4801
4802 if (pastEntries.size() > 0) {
4803 ArrayAdapter<String> adapter = new ArrayAdapter<String>(
4804 mContext,
4805 com.android.internal.R.layout.web_text_view_dropdown,
4806 pastEntries);
4807 mUpdateMessage.obj = adapter;
4808 mUpdateMessage.sendToTarget();
4809 }
4810 }
4811 }
4812
4813 /**
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004814 * Dump the display tree to "/sdcard/displayTree.txt"
4815 *
Jonathan Dixoncd93e152012-03-02 19:19:44 +00004816 * debug only
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004817 */
4818 public void dumpDisplayTree() {
4819 nativeDumpDisplayTree(getUrl());
4820 }
4821
4822 /**
4823 * Dump the dom tree to adb shell if "toFile" is False, otherwise dump it to
4824 * "/sdcard/domTree.txt"
4825 *
Jonathan Dixoncd93e152012-03-02 19:19:44 +00004826 * debug only
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004827 */
4828 public void dumpDomTree(boolean toFile) {
4829 mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE, toFile ? 1 : 0, 0);
4830 }
4831
4832 /**
4833 * Dump the render tree to adb shell if "toFile" is False, otherwise dump it
4834 * to "/sdcard/renderTree.txt"
4835 *
Jonathan Dixoncd93e152012-03-02 19:19:44 +00004836 * debug only
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004837 */
4838 public void dumpRenderTree(boolean toFile) {
4839 mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE, toFile ? 1 : 0, 0);
4840 }
4841
4842 /**
4843 * Called by DRT on UI thread, need to proxy to WebCore thread.
4844 *
Jonathan Dixoncd93e152012-03-02 19:19:44 +00004845 * debug only
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004846 */
Steve Block5ba2efe2011-08-03 13:57:49 +01004847 public void setUseMockDeviceOrientation() {
4848 mWebViewCore.sendMessage(EventHub.SET_USE_MOCK_DEVICE_ORIENTATION);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004849 }
4850
4851 /**
4852 * Called by DRT on WebCore thread.
4853 *
Jonathan Dixoncd93e152012-03-02 19:19:44 +00004854 * debug only
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004855 */
4856 public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
4857 boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
4858 mWebViewCore.setMockDeviceOrientation(canProvideAlpha, alpha, canProvideBeta, beta,
4859 canProvideGamma, gamma);
4860 }
4861
4862 // This is used to determine long press with the center key. Does not
4863 // affect long press with the trackball/touch.
4864 private boolean mGotCenterDown = false;
4865
4866 @Override
4867 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
4868 if (mBlockWebkitViewMessages) {
4869 return false;
4870 }
4871 // send complex characters to webkit for use by JS and plugins
4872 if (keyCode == KeyEvent.KEYCODE_UNKNOWN && event.getCharacters() != null) {
4873 // pass the key to DOM
George Mountdbef1c52012-03-28 14:17:13 -07004874 sendBatchableInputMessage(EventHub.KEY_DOWN, 0, 0, event);
4875 sendBatchableInputMessage(EventHub.KEY_UP, 0, 0, event);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004876 // return true as DOM handles the key
4877 return true;
4878 }
4879 return false;
4880 }
4881
4882 private boolean isEnterActionKey(int keyCode) {
4883 return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
4884 || keyCode == KeyEvent.KEYCODE_ENTER
4885 || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER;
4886 }
4887
George Mountbcd5dd72012-03-01 08:39:03 -08004888 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
4889 if (mAutoCompletePopup != null) {
4890 return mAutoCompletePopup.onKeyPreIme(keyCode, event);
4891 }
4892 return false;
4893 }
4894
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004895 @Override
4896 public boolean onKeyDown(int keyCode, KeyEvent event) {
4897 if (DebugFlags.WEB_VIEW) {
4898 Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis()
4899 + "keyCode=" + keyCode
4900 + ", " + event + ", unicode=" + event.getUnicodeChar());
4901 }
4902 if (mIsCaretSelection) {
4903 selectionDone();
4904 }
4905 if (mBlockWebkitViewMessages) {
4906 return false;
4907 }
4908
4909 // don't implement accelerator keys here; defer to host application
4910 if (event.isCtrlPressed()) {
4911 return false;
4912 }
4913
4914 if (mNativeClass == 0) {
4915 return false;
4916 }
4917
4918 // do this hack up front, so it always works, regardless of touch-mode
4919 if (AUTO_REDRAW_HACK && (keyCode == KeyEvent.KEYCODE_CALL)) {
4920 mAutoRedraw = !mAutoRedraw;
4921 if (mAutoRedraw) {
4922 invalidate();
4923 }
4924 return true;
4925 }
4926
4927 // Bubble up the key event if
4928 // 1. it is a system key; or
4929 // 2. the host application wants to handle it;
4930 if (event.isSystem()
4931 || mCallbackProxy.uiOverrideKeyEvent(event)) {
4932 return false;
4933 }
4934
4935 // accessibility support
4936 if (accessibilityScriptInjected()) {
4937 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
4938 // if an accessibility script is injected we delegate to it the key handling.
4939 // this script is a screen reader which is a fully fledged solution for blind
4940 // users to navigate in and interact with web pages.
George Mountdbef1c52012-03-28 14:17:13 -07004941 sendBatchableInputMessage(EventHub.KEY_DOWN, 0, 0, event);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004942 return true;
4943 } else {
4944 // Clean up if accessibility was disabled after loading the current URL.
4945 mAccessibilityScriptInjected = false;
4946 }
4947 } else if (mAccessibilityInjector != null) {
4948 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
4949 if (mAccessibilityInjector.onKeyEvent(event)) {
4950 // if an accessibility injector is present (no JavaScript enabled or the site
4951 // opts out injecting our JavaScript screen reader) we let it decide whether
4952 // to act on and consume the event.
4953 return true;
4954 }
4955 } else {
4956 // Clean up if accessibility was disabled after loading the current URL.
4957 mAccessibilityInjector = null;
4958 }
4959 }
4960
4961 if (keyCode == KeyEvent.KEYCODE_PAGE_UP) {
4962 if (event.hasNoModifiers()) {
4963 pageUp(false);
4964 return true;
4965 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
4966 pageUp(true);
4967 return true;
4968 }
4969 }
4970
4971 if (keyCode == KeyEvent.KEYCODE_PAGE_DOWN) {
4972 if (event.hasNoModifiers()) {
4973 pageDown(false);
4974 return true;
4975 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
4976 pageDown(true);
4977 return true;
4978 }
4979 }
4980
4981 if (keyCode == KeyEvent.KEYCODE_MOVE_HOME && event.hasNoModifiers()) {
4982 pageUp(true);
4983 return true;
4984 }
4985
4986 if (keyCode == KeyEvent.KEYCODE_MOVE_END && event.hasNoModifiers()) {
4987 pageDown(true);
4988 return true;
4989 }
4990
4991 if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
4992 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
4993 switchOutDrawHistory();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004994 }
4995
4996 if (isEnterActionKey(keyCode)) {
4997 switchOutDrawHistory();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08004998 if (event.getRepeatCount() == 0) {
4999 if (mSelectingText) {
5000 return true; // discard press if copy in progress
5001 }
5002 mGotCenterDown = true;
5003 mPrivateHandler.sendMessageDelayed(mPrivateHandler
5004 .obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005005 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005006 }
5007
5008 if (getSettings().getNavDump()) {
5009 switch (keyCode) {
5010 case KeyEvent.KEYCODE_4:
5011 dumpDisplayTree();
5012 break;
5013 case KeyEvent.KEYCODE_5:
5014 case KeyEvent.KEYCODE_6:
5015 dumpDomTree(keyCode == KeyEvent.KEYCODE_5);
5016 break;
5017 case KeyEvent.KEYCODE_7:
5018 case KeyEvent.KEYCODE_8:
5019 dumpRenderTree(keyCode == KeyEvent.KEYCODE_7);
5020 break;
5021 }
5022 }
5023
John Reckb2676f72012-03-02 14:13:06 -08005024 // pass the key to DOM
John Reck4fa40372012-03-06 14:31:45 -08005025 sendKeyEvent(event);
John Reckb2676f72012-03-02 14:13:06 -08005026 // return true as DOM handles the key
5027 return true;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005028 }
5029
5030 @Override
5031 public boolean onKeyUp(int keyCode, KeyEvent event) {
5032 if (DebugFlags.WEB_VIEW) {
5033 Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis()
5034 + ", " + event + ", unicode=" + event.getUnicodeChar());
5035 }
5036 if (mBlockWebkitViewMessages) {
5037 return false;
5038 }
5039
5040 if (mNativeClass == 0) {
5041 return false;
5042 }
5043
5044 // special CALL handling when cursor node's href is "tel:XXX"
John Reckb2676f72012-03-02 14:13:06 -08005045 if (keyCode == KeyEvent.KEYCODE_CALL
5046 && mInitialHitTestResult != null
5047 && mInitialHitTestResult.getType() == HitTestResult.PHONE_TYPE) {
5048 String text = mInitialHitTestResult.getExtra();
5049 Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(text));
5050 mContext.startActivity(intent);
5051 return true;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005052 }
5053
5054 // Bubble up the key event if
5055 // 1. it is a system key; or
5056 // 2. the host application wants to handle it;
5057 if (event.isSystem()
5058 || mCallbackProxy.uiOverrideKeyEvent(event)) {
5059 return false;
5060 }
5061
5062 // accessibility support
5063 if (accessibilityScriptInjected()) {
5064 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
5065 // if an accessibility script is injected we delegate to it the key handling.
5066 // this script is a screen reader which is a fully fledged solution for blind
5067 // users to navigate in and interact with web pages.
George Mountdbef1c52012-03-28 14:17:13 -07005068 sendBatchableInputMessage(EventHub.KEY_UP, 0, 0, event);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005069 return true;
5070 } else {
5071 // Clean up if accessibility was disabled after loading the current URL.
5072 mAccessibilityScriptInjected = false;
5073 }
5074 } else if (mAccessibilityInjector != null) {
5075 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
5076 if (mAccessibilityInjector.onKeyEvent(event)) {
5077 // if an accessibility injector is present (no JavaScript enabled or the site
5078 // opts out injecting our JavaScript screen reader) we let it decide whether to
5079 // act on and consume the event.
5080 return true;
5081 }
5082 } else {
5083 // Clean up if accessibility was disabled after loading the current URL.
5084 mAccessibilityInjector = null;
5085 }
5086 }
5087
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005088 if (isEnterActionKey(keyCode)) {
5089 // remove the long press message first
5090 mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
5091 mGotCenterDown = false;
5092
5093 if (mSelectingText) {
5094 copySelection();
5095 selectionDone();
5096 return true; // discard press if copy in progress
5097 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005098 }
5099
John Reckb2676f72012-03-02 14:13:06 -08005100 // pass the key to DOM
John Reck4fa40372012-03-06 14:31:45 -08005101 sendKeyEvent(event);
John Reckb2676f72012-03-02 14:13:06 -08005102 // return true as DOM handles the key
5103 return true;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005104 }
5105
5106 private boolean startSelectActionMode() {
5107 mSelectCallback = new SelectActionModeCallback();
5108 mSelectCallback.setTextSelected(!mIsCaretSelection);
5109 mSelectCallback.setWebView(this);
Jonathan Dixon3c909522012-02-28 18:45:06 +00005110 if (mWebView.startActionMode(mSelectCallback) == null) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005111 // There is no ActionMode, so do not allow the user to modify a
5112 // selection.
5113 selectionDone();
5114 return false;
5115 }
Jonathan Dixon3c909522012-02-28 18:45:06 +00005116 mWebView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005117 return true;
5118 }
5119
5120 private void showPasteWindow() {
5121 ClipboardManager cm = (ClipboardManager)(mContext
5122 .getSystemService(Context.CLIPBOARD_SERVICE));
5123 if (cm.hasPrimaryClip()) {
George Mountf9c1f992012-03-21 16:06:10 -07005124 Point cursorPoint = new Point(contentToViewX(mSelectCursorBase.x),
5125 contentToViewY(mSelectCursorBase.y));
5126 Point cursorTop = calculateCaretTop();
5127 cursorTop.set(contentToViewX(cursorTop.x),
5128 contentToViewY(cursorTop.y));
5129
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005130 int[] location = new int[2];
Jonathan Dixon3c909522012-02-28 18:45:06 +00005131 mWebView.getLocationInWindow(location);
George Mountf9c1f992012-03-21 16:06:10 -07005132 int offsetX = location[0] - getScrollX();
5133 int offsetY = location[1] - getScrollY();
5134 cursorPoint.offset(offsetX, offsetY);
5135 cursorTop.offset(offsetX, offsetY);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005136 if (mPasteWindow == null) {
5137 mPasteWindow = new PastePopupWindow();
5138 }
George Mountf9c1f992012-03-21 16:06:10 -07005139 mPasteWindow.show(cursorPoint, cursorTop, location[0], location[1]);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005140 }
5141 }
5142
George Mountf9c1f992012-03-21 16:06:10 -07005143 /**
5144 * Given segment AB, this finds the point C along AB that is closest to
5145 * point and then returns it scale along AB. The scale factor is AC/AB.
5146 *
5147 * @param x The x coordinate of the point near segment AB that determines
5148 * the scale factor.
5149 * @param y The y coordinate of the point near segment AB that determines
5150 * the scale factor.
5151 * @param a The first point of the line segment.
5152 * @param b The second point of the line segment.
5153 * @return The scale factor AC/AB, where C is the point on AB closest to
5154 * point.
5155 */
5156 private static float scaleAlongSegment(int x, int y, PointF a, PointF b) {
5157 // The bottom line of the text box is line AB
5158 float abX = b.x - a.x;
5159 float abY = b.y - a.y;
5160 float ab2 = (abX * abX) + (abY * abY);
5161
5162 // The line from first point in text bounds to bottom is AP
5163 float apX = x - a.x;
5164 float apY = y - a.y;
5165 float abDotAP = (apX * abX) + (apY * abY);
5166 float scale = abDotAP / ab2;
5167 return scale;
5168 }
5169
5170 /**
5171 * Assuming arbitrary shape of a quadralateral forming text bounds, this
5172 * calculates the top of a caret.
5173 */
5174 private Point calculateCaretTop() {
5175 float scale = scaleAlongSegment(mSelectCursorBase.x, mSelectCursorBase.y,
5176 mSelectCursorBaseTextQuad.p4, mSelectCursorBaseTextQuad.p3);
5177 int x = Math.round(scaleCoordinate(scale,
5178 mSelectCursorBaseTextQuad.p1.x, mSelectCursorBaseTextQuad.p2.x));
5179 int y = Math.round(scaleCoordinate(scale,
5180 mSelectCursorBaseTextQuad.p1.y, mSelectCursorBaseTextQuad.p2.y));
5181 return new Point(x, y);
5182 }
5183
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005184 private void hidePasteButton() {
5185 if (mPasteWindow != null) {
5186 mPasteWindow.hide();
5187 }
5188 }
5189
5190 private void syncSelectionCursors() {
5191 mSelectCursorBaseLayerId =
George Mountf9c1f992012-03-21 16:06:10 -07005192 nativeGetHandleLayerId(mNativeClass, HANDLE_ID_BASE,
5193 mSelectCursorBase, mSelectCursorBaseTextQuad);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005194 mSelectCursorExtentLayerId =
George Mountf9c1f992012-03-21 16:06:10 -07005195 nativeGetHandleLayerId(mNativeClass, HANDLE_ID_EXTENT,
5196 mSelectCursorExtent, mSelectCursorExtentTextQuad);
5197 }
5198
5199 private void adjustSelectionCursors() {
5200 boolean wasDraggingStart = (mSelectDraggingCursor == mSelectCursorBase);
5201 int oldX = mSelectDraggingCursor.x;
5202 int oldY = mSelectDraggingCursor.y;
5203 int oldStartX = mSelectCursorBase.x;
5204 int oldStartY = mSelectCursorBase.y;
5205 int oldEndX = mSelectCursorExtent.x;
5206 int oldEndY = mSelectCursorExtent.y;
5207
5208 syncSelectionCursors();
5209 boolean dragChanged = oldX != mSelectDraggingCursor.x ||
5210 oldY != mSelectDraggingCursor.y;
5211 if (dragChanged && !mIsCaretSelection) {
5212 boolean draggingStart;
5213 if (wasDraggingStart) {
5214 float endStart = distanceSquared(oldEndX, oldEndY,
5215 mSelectCursorBase);
5216 float endEnd = distanceSquared(oldEndX, oldEndY,
5217 mSelectCursorExtent);
5218 draggingStart = endStart > endEnd;
5219 } else {
5220 float startStart = distanceSquared(oldStartX, oldStartY,
5221 mSelectCursorBase);
5222 float startEnd = distanceSquared(oldStartX, oldStartY,
5223 mSelectCursorExtent);
5224 draggingStart = startStart > startEnd;
5225 }
5226 mSelectDraggingCursor = (draggingStart
5227 ? mSelectCursorBase : mSelectCursorExtent);
5228 mSelectDraggingTextQuad = (draggingStart
5229 ? mSelectCursorBaseTextQuad : mSelectCursorExtentTextQuad);
5230 mSelectDraggingOffset = (draggingStart
5231 ? mSelectHandleLeftOffset : mSelectHandleRightOffset);
5232 }
5233 mSelectDraggingCursor.set(oldX, oldY);
5234 }
5235
5236 private float distanceSquared(int x, int y, Point p) {
5237 float dx = p.x - x;
5238 float dy = p.y - y;
5239 return (dx * dx) + (dy * dy);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005240 }
5241
5242 private boolean setupWebkitSelect() {
5243 syncSelectionCursors();
George Mountf796fdf2012-03-19 14:51:55 -07005244 if (!mIsCaretSelection && !startSelectActionMode()) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005245 selectionDone();
5246 return false;
5247 }
George Mount30d773f2012-04-20 08:34:44 -07005248 startSelectingText();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005249 mTouchMode = TOUCH_DRAG_MODE;
5250 return true;
5251 }
5252
5253 private void updateWebkitSelection() {
5254 int[] handles = null;
5255 if (mIsCaretSelection) {
George Mountf9c1f992012-03-21 16:06:10 -07005256 mSelectCursorExtent.set(mSelectCursorBase.x, mSelectCursorBase.y);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005257 }
5258 if (mSelectingText) {
5259 handles = new int[4];
George Mountf9c1f992012-03-21 16:06:10 -07005260 handles[0] = mSelectCursorBase.x;
5261 handles[1] = mSelectCursorBase.y;
5262 handles[2] = mSelectCursorExtent.x;
5263 handles[3] = mSelectCursorExtent.y;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005264 } else {
5265 nativeSetTextSelection(mNativeClass, 0);
5266 }
5267 mWebViewCore.removeMessages(EventHub.SELECT_TEXT);
5268 mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SELECT_TEXT, handles);
5269 }
5270
5271 private void resetCaretTimer() {
5272 mPrivateHandler.removeMessages(CLEAR_CARET_HANDLE);
5273 if (!mSelectionStarted) {
5274 mPrivateHandler.sendEmptyMessageDelayed(CLEAR_CARET_HANDLE,
5275 CARET_HANDLE_STAMINA_MS);
5276 }
5277 }
5278
5279 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00005280 * See {@link WebView#emulateShiftHeld()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005281 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00005282 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005283 @Deprecated
5284 public void emulateShiftHeld() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005285 }
5286
5287 /**
5288 * Select all of the text in this WebView.
5289 *
Jonathan Dixoncd93e152012-03-02 19:19:44 +00005290 * This is an implementation detail.
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005291 */
5292 public void selectAll() {
5293 mWebViewCore.sendMessage(EventHub.SELECT_ALL);
5294 }
5295
5296 /**
5297 * Called when the selection has been removed.
5298 */
5299 void selectionDone() {
5300 if (mSelectingText) {
5301 hidePasteButton();
George Mount30d773f2012-04-20 08:34:44 -07005302 endSelectingText();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005303 // finish is idempotent, so this is fine even if selectionDone was
5304 // called by mSelectCallback.onDestroyActionMode
5305 if (mSelectCallback != null) {
5306 mSelectCallback.finish();
5307 mSelectCallback = null;
5308 }
5309 if (!mIsCaretSelection) {
5310 updateWebkitSelection();
5311 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005312 invalidate(); // redraw without selection
5313 mAutoScrollX = 0;
5314 mAutoScrollY = 0;
5315 mSentAutoScrollMessage = false;
5316 }
5317 }
5318
5319 /**
5320 * Copy the selection to the clipboard
5321 *
Jonathan Dixoncd93e152012-03-02 19:19:44 +00005322 * This is an implementation detail.
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005323 */
5324 public boolean copySelection() {
5325 boolean copiedSomething = false;
5326 String selection = getSelection();
5327 if (selection != null && selection != "") {
5328 if (DebugFlags.WEB_VIEW) {
5329 Log.v(LOGTAG, "copySelection \"" + selection + "\"");
5330 }
5331 Toast.makeText(mContext
5332 , com.android.internal.R.string.text_copied
5333 , Toast.LENGTH_SHORT).show();
5334 copiedSomething = true;
Jonathan Dixon3c909522012-02-28 18:45:06 +00005335 ClipboardManager cm = (ClipboardManager)mContext
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005336 .getSystemService(Context.CLIPBOARD_SERVICE);
5337 cm.setText(selection);
5338 int[] handles = new int[4];
5339 getSelectionHandles(handles);
5340 mWebViewCore.sendMessage(EventHub.COPY_TEXT, handles);
5341 }
5342 invalidate(); // remove selection region and pointer
5343 return copiedSomething;
5344 }
5345
5346 /**
5347 * Cut the selected text into the clipboard
5348 *
Jonathan Dixoncd93e152012-03-02 19:19:44 +00005349 * This is an implementation detail
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005350 */
5351 public void cutSelection() {
5352 copySelection();
5353 int[] handles = new int[4];
5354 getSelectionHandles(handles);
5355 mWebViewCore.sendMessage(EventHub.DELETE_TEXT, handles);
5356 }
5357
5358 /**
5359 * Paste text from the clipboard to the cursor position.
5360 *
Jonathan Dixoncd93e152012-03-02 19:19:44 +00005361 * This is an implementation detail
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005362 */
5363 public void pasteFromClipboard() {
Jonathan Dixon3c909522012-02-28 18:45:06 +00005364 ClipboardManager cm = (ClipboardManager)mContext
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005365 .getSystemService(Context.CLIPBOARD_SERVICE);
5366 ClipData clipData = cm.getPrimaryClip();
5367 if (clipData != null) {
5368 ClipData.Item clipItem = clipData.getItemAt(0);
5369 CharSequence pasteText = clipItem.getText();
5370 if (mInputConnection != null) {
5371 mInputConnection.replaceSelection(pasteText);
5372 }
5373 }
5374 }
5375
5376 /**
Jonathan Dixoncd93e152012-03-02 19:19:44 +00005377 * This is an implementation detail.
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005378 */
5379 public SearchBox getSearchBox() {
5380 if ((mWebViewCore == null) || (mWebViewCore.getBrowserFrame() == null)) {
5381 return null;
5382 }
5383 return mWebViewCore.getBrowserFrame().getSearchBox();
5384 }
5385
5386 /**
5387 * Returns the currently highlighted text as a string.
5388 */
5389 String getSelection() {
5390 if (mNativeClass == 0) return "";
5391 return nativeGetSelection();
5392 }
5393
5394 @Override
Jonathan Dixon3c909522012-02-28 18:45:06 +00005395 public void onAttachedToWindow() {
5396 if (mWebView.hasWindowFocus()) setActive(true);
5397 final ViewTreeObserver treeObserver = mWebView.getViewTreeObserver();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005398 if (mGlobalLayoutListener == null) {
5399 mGlobalLayoutListener = new InnerGlobalLayoutListener();
5400 treeObserver.addOnGlobalLayoutListener(mGlobalLayoutListener);
5401 }
5402 if (mScrollChangedListener == null) {
5403 mScrollChangedListener = new InnerScrollChangedListener();
5404 treeObserver.addOnScrollChangedListener(mScrollChangedListener);
5405 }
5406
5407 addAccessibilityApisToJavaScript();
5408
John Recka5408e62012-03-16 14:18:44 -07005409 updateHwAccelerated();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005410 }
5411
5412 @Override
Jonathan Dixon3c909522012-02-28 18:45:06 +00005413 public void onDetachedFromWindow() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005414 clearHelpers();
5415 mZoomManager.dismissZoomPicker();
Jonathan Dixon3c909522012-02-28 18:45:06 +00005416 if (mWebView.hasWindowFocus()) setActive(false);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005417
Jonathan Dixon3c909522012-02-28 18:45:06 +00005418 final ViewTreeObserver treeObserver = mWebView.getViewTreeObserver();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005419 if (mGlobalLayoutListener != null) {
5420 treeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener);
5421 mGlobalLayoutListener = null;
5422 }
5423 if (mScrollChangedListener != null) {
5424 treeObserver.removeOnScrollChangedListener(mScrollChangedListener);
5425 mScrollChangedListener = null;
5426 }
5427
5428 removeAccessibilityApisFromJavaScript();
John Recka5408e62012-03-16 14:18:44 -07005429 updateHwAccelerated();
Romain Guyba6be8a2012-04-23 18:22:09 -07005430
5431 if (mWebView.isHardwareAccelerated()) {
5432 int drawGLFunction = nativeGetDrawGLFunction(mNativeClass);
5433 if (drawGLFunction != 0) {
5434 mWebView.getViewRootImpl().detachFunctor(drawGLFunction);
5435 }
5436 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005437 }
5438
5439 @Override
Jonathan Dixon3c909522012-02-28 18:45:06 +00005440 public void onVisibilityChanged(View changedView, int visibility) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005441 // The zoomManager may be null if the webview is created from XML that
5442 // specifies the view's visibility param as not visible (see http://b/2794841)
5443 if (visibility != View.VISIBLE && mZoomManager != null) {
5444 mZoomManager.dismissZoomPicker();
5445 }
5446 updateDrawingState();
5447 }
5448
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005449 void setActive(boolean active) {
5450 if (active) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00005451 if (mWebView.hasFocus()) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005452 // If our window regained focus, and we have focus, then begin
5453 // drawing the cursor ring
John Reckb2676f72012-03-02 14:13:06 -08005454 mDrawCursorRing = true;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005455 setFocusControllerActive(true);
5456 } else {
5457 mDrawCursorRing = false;
John Reckb2676f72012-03-02 14:13:06 -08005458 setFocusControllerActive(false);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005459 }
5460 } else {
5461 if (!mZoomManager.isZoomPickerVisible()) {
5462 /*
5463 * The external zoom controls come in their own window, so our
5464 * window loses focus. Our policy is to not draw the cursor ring
5465 * if our window is not focused, but this is an exception since
5466 * the user can still navigate the web page with the zoom
5467 * controls showing.
5468 */
5469 mDrawCursorRing = false;
5470 }
5471 mKeysPressed.clear();
5472 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
5473 mTouchMode = TOUCH_DONE_MODE;
5474 setFocusControllerActive(false);
5475 }
5476 invalidate();
5477 }
5478
5479 // To avoid drawing the cursor ring, and remove the TextView when our window
5480 // loses focus.
5481 @Override
5482 public void onWindowFocusChanged(boolean hasWindowFocus) {
5483 setActive(hasWindowFocus);
5484 if (hasWindowFocus) {
5485 JWebCoreJavaBridge.setActiveWebView(this);
5486 if (mPictureUpdatePausedForFocusChange) {
5487 WebViewCore.resumeUpdatePicture(mWebViewCore);
5488 mPictureUpdatePausedForFocusChange = false;
5489 }
5490 } else {
5491 JWebCoreJavaBridge.removeActiveWebView(this);
5492 final WebSettings settings = getSettings();
5493 if (settings != null && settings.enableSmoothTransition() &&
5494 mWebViewCore != null && !WebViewCore.isUpdatePicturePaused(mWebViewCore)) {
5495 WebViewCore.pauseUpdatePicture(mWebViewCore);
5496 mPictureUpdatePausedForFocusChange = true;
5497 }
5498 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005499 }
5500
5501 /*
5502 * Pass a message to WebCore Thread, telling the WebCore::Page's
5503 * FocusController to be "inactive" so that it will
5504 * not draw the blinking cursor. It gets set to "active" to draw the cursor
5505 * in WebViewCore.cpp, when the WebCore thread receives key events/clicks.
5506 */
5507 /* package */ void setFocusControllerActive(boolean active) {
5508 if (mWebViewCore == null) return;
5509 mWebViewCore.sendMessage(EventHub.SET_ACTIVE, active ? 1 : 0, 0);
5510 // Need to send this message after the document regains focus.
5511 if (active && mListBoxMessage != null) {
5512 mWebViewCore.sendMessage(mListBoxMessage);
5513 mListBoxMessage = null;
5514 }
5515 }
5516
5517 @Override
Jonathan Dixon3c909522012-02-28 18:45:06 +00005518 public void onFocusChanged(boolean focused, int direction,
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005519 Rect previouslyFocusedRect) {
5520 if (DebugFlags.WEB_VIEW) {
5521 Log.v(LOGTAG, "MT focusChanged " + focused + ", " + direction);
5522 }
5523 if (focused) {
John Reckb2676f72012-03-02 14:13:06 -08005524 mDrawCursorRing = true;
5525 setFocusControllerActive(true);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005526 } else {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005527 mDrawCursorRing = false;
John Reckb2676f72012-03-02 14:13:06 -08005528 setFocusControllerActive(false);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005529 mKeysPressed.clear();
5530 }
John Reck413fab32012-03-07 11:02:15 -08005531 if (!mTouchHighlightRegion.isEmpty()) {
5532 mWebView.invalidate(mTouchHighlightRegion.getBounds());
5533 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005534 }
5535
5536 void setGLRectViewport() {
5537 // Use the getGlobalVisibleRect() to get the intersection among the parents
5538 // visible == false means we're clipped - send a null rect down to indicate that
5539 // we should not draw
Jonathan Dixon3c909522012-02-28 18:45:06 +00005540 boolean visible = mWebView.getGlobalVisibleRect(mGLRectViewport);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005541 if (visible) {
5542 // Then need to invert the Y axis, just for GL
Jonathan Dixon3c909522012-02-28 18:45:06 +00005543 View rootView = mWebView.getRootView();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005544 int rootViewHeight = rootView.getHeight();
5545 mViewRectViewport.set(mGLRectViewport);
5546 int savedWebViewBottom = mGLRectViewport.bottom;
5547 mGLRectViewport.bottom = rootViewHeight - mGLRectViewport.top - getVisibleTitleHeightImpl();
5548 mGLRectViewport.top = rootViewHeight - savedWebViewBottom;
5549 mGLViewportEmpty = false;
5550 } else {
5551 mGLViewportEmpty = true;
5552 }
5553 calcOurContentVisibleRectF(mVisibleContentRect);
Romain Guyd4caee02012-04-23 20:44:04 -07005554 nativeUpdateDrawGLFunction(mNativeClass, mGLViewportEmpty ? null : mGLRectViewport,
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005555 mGLViewportEmpty ? null : mViewRectViewport,
5556 mVisibleContentRect, getScale());
5557 }
5558
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005559 @Override
Jonathan Dixon3c909522012-02-28 18:45:06 +00005560 public boolean setFrame(int left, int top, int right, int bottom) {
5561 boolean changed = mWebViewPrivate.super_setFrame(left, top, right, bottom);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005562 if (!changed && mHeightCanMeasure) {
5563 // When mHeightCanMeasure is true, we will set mLastHeightSent to 0
5564 // in WebViewCore after we get the first layout. We do call
5565 // requestLayout() when we get contentSizeChanged(). But the View
5566 // system won't call onSizeChanged if the dimension is not changed.
5567 // In this case, we need to call sendViewSizeZoom() explicitly to
5568 // notify the WebKit about the new dimensions.
5569 sendViewSizeZoom(false);
5570 }
5571 setGLRectViewport();
5572 return changed;
5573 }
5574
5575 @Override
Jonathan Dixon3c909522012-02-28 18:45:06 +00005576 public void onSizeChanged(int w, int h, int ow, int oh) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005577 // adjust the max viewport width depending on the view dimensions. This
5578 // is to ensure the scaling is not going insane. So do not shrink it if
5579 // the view size is temporarily smaller, e.g. when soft keyboard is up.
5580 int newMaxViewportWidth = (int) (Math.max(w, h) / mZoomManager.getDefaultMinZoomScale());
5581 if (newMaxViewportWidth > sMaxViewportWidth) {
5582 sMaxViewportWidth = newMaxViewportWidth;
5583 }
5584
5585 mZoomManager.onSizeChanged(w, h, ow, oh);
5586
5587 if (mLoadedPicture != null && mDelaySetPicture == null) {
5588 // Size changes normally result in a new picture
5589 // Re-set the loaded picture to simulate that
5590 // However, do not update the base layer as that hasn't changed
5591 setNewPicture(mLoadedPicture, false);
5592 }
George Mountbcd5dd72012-03-01 08:39:03 -08005593 relocateAutoCompletePopup();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005594 }
5595
5596 @Override
Jonathan Dixon3c909522012-02-28 18:45:06 +00005597 public void onScrollChanged(int l, int t, int oldl, int oldt) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005598 if (!mInOverScrollMode) {
5599 sendOurVisibleRect();
5600 // update WebKit if visible title bar height changed. The logic is same
5601 // as getVisibleTitleHeightImpl.
5602 int titleHeight = getTitleHeight();
5603 if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) {
5604 sendViewSizeZoom(false);
5605 }
5606 }
5607 }
5608
5609 @Override
5610 public boolean dispatchKeyEvent(KeyEvent event) {
5611 switch (event.getAction()) {
5612 case KeyEvent.ACTION_DOWN:
5613 mKeysPressed.add(Integer.valueOf(event.getKeyCode()));
5614 break;
5615 case KeyEvent.ACTION_MULTIPLE:
5616 // Always accept the action.
5617 break;
5618 case KeyEvent.ACTION_UP:
5619 int location = mKeysPressed.indexOf(Integer.valueOf(event.getKeyCode()));
5620 if (location == -1) {
5621 // We did not receive the key down for this key, so do not
5622 // handle the key up.
5623 return false;
5624 } else {
5625 // We did receive the key down. Handle the key up, and
5626 // remove it from our pressed keys.
5627 mKeysPressed.remove(location);
5628 }
5629 break;
5630 default:
5631 // Accept the action. This should not happen, unless a new
5632 // action is added to KeyEvent.
5633 break;
5634 }
John Reckb2676f72012-03-02 14:13:06 -08005635 return mWebViewPrivate.super_dispatchKeyEvent(event);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005636 }
5637
5638 /*
5639 * Here is the snap align logic:
5640 * 1. If it starts nearly horizontally or vertically, snap align;
5641 * 2. If there is a dramitic direction change, let it go;
5642 *
5643 * Adjustable parameters. Angle is the radians on a unit circle, limited
5644 * to quadrant 1. Values range from 0f (horizontal) to PI/2 (vertical)
5645 */
5646 private static final float HSLOPE_TO_START_SNAP = .25f;
5647 private static final float HSLOPE_TO_BREAK_SNAP = .4f;
5648 private static final float VSLOPE_TO_START_SNAP = 1.25f;
5649 private static final float VSLOPE_TO_BREAK_SNAP = .95f;
5650 /*
5651 * These values are used to influence the average angle when entering
5652 * snap mode. If is is the first movement entering snap, we set the average
5653 * to the appropriate ideal. If the user is entering into snap after the
5654 * first movement, then we average the average angle with these values.
5655 */
5656 private static final float ANGLE_VERT = 2f;
5657 private static final float ANGLE_HORIZ = 0f;
5658 /*
5659 * The modified moving average weight.
5660 * Formula: MAV[t]=MAV[t-1] + (P[t]-MAV[t-1])/n
5661 */
5662 private static final float MMA_WEIGHT_N = 5;
5663
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005664 private boolean inFullScreenMode() {
5665 return mFullScreenHolder != null;
5666 }
5667
5668 private void dismissFullScreenMode() {
5669 if (inFullScreenMode()) {
5670 mFullScreenHolder.hide();
5671 mFullScreenHolder = null;
5672 invalidate();
5673 }
5674 }
5675
5676 void onPinchToZoomAnimationStart() {
5677 // cancel the single touch handling
5678 cancelTouch();
5679 onZoomAnimationStart();
5680 }
5681
5682 void onPinchToZoomAnimationEnd(ScaleGestureDetector detector) {
5683 onZoomAnimationEnd();
5684 // start a drag, TOUCH_PINCH_DRAG, can't use TOUCH_INIT_MODE as
5685 // it may trigger the unwanted click, can't use TOUCH_DRAG_MODE
5686 // as it may trigger the unwanted fling.
5687 mTouchMode = TOUCH_PINCH_DRAG;
5688 mConfirmMove = true;
5689 startTouch(detector.getFocusX(), detector.getFocusY(), mLastTouchTime);
5690 }
5691
5692 // See if there is a layer at x, y and switch to TOUCH_DRAG_LAYER_MODE if a
5693 // layer is found.
5694 private void startScrollingLayer(float x, float y) {
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00005695 int contentX = viewToContentX((int) x + getScrollX());
5696 int contentY = viewToContentY((int) y + getScrollY());
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005697 mCurrentScrollingLayerId = nativeScrollableLayer(contentX, contentY,
5698 mScrollingLayerRect, mScrollingLayerBounds);
5699 if (mCurrentScrollingLayerId != 0) {
5700 mTouchMode = TOUCH_DRAG_LAYER_MODE;
5701 }
5702 }
5703
5704 // 1/(density * density) used to compute the distance between points.
5705 // Computed in init().
5706 private float DRAG_LAYER_INVERSE_DENSITY_SQUARED;
5707
5708 // The distance between two points reported in onTouchEvent scaled by the
5709 // density of the screen.
5710 private static final int DRAG_LAYER_FINGER_DISTANCE = 20000;
5711
5712 @Override
5713 public boolean onHoverEvent(MotionEvent event) {
5714 if (mNativeClass == 0) {
5715 return false;
5716 }
John Reckb2676f72012-03-02 14:13:06 -08005717 int x = viewToContentX((int) event.getX() + getScrollX());
5718 int y = viewToContentY((int) event.getY() + getScrollY());
5719 mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, x, y);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005720 return true;
5721 }
5722
5723 @Override
5724 public boolean onTouchEvent(MotionEvent ev) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00005725 if (mNativeClass == 0 || (!mWebView.isClickable() && !mWebView.isLongClickable())) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005726 return false;
5727 }
5728
John Reck413fab32012-03-07 11:02:15 -08005729 if (!mWebView.isFocused()) {
5730 mWebView.requestFocus();
5731 }
5732
Jeff Brown9d3bdbd2012-03-21 11:50:06 -07005733 if (mInputDispatcher == null) {
5734 return false;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005735 }
5736
Jeff Brown9d3bdbd2012-03-21 11:50:06 -07005737 if (mInputDispatcher.postPointerEvent(ev, getScrollX(),
5738 getScrollY() - getTitleHeight(), mZoomManager.getInvScale())) {
5739 return true;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005740 } else {
Jeff Brown9d3bdbd2012-03-21 11:50:06 -07005741 Log.w(LOGTAG, "mInputDispatcher rejected the event!");
5742 return false;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005743 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005744 }
5745
5746 private float calculateDragAngle(int dx, int dy) {
5747 dx = Math.abs(dx);
5748 dy = Math.abs(dy);
5749 return (float) Math.atan2(dy, dx);
5750 }
5751
5752 /*
Jeff Brown9d3bdbd2012-03-21 11:50:06 -07005753 * Common code for single touch and multi-touch.
5754 * (x, y) denotes current focus point, which is the touch point for single touch
5755 * and the middle point for multi-touch.
5756 */
5757 private void handleTouchEventCommon(MotionEvent event, int action, int x, int y) {
5758 ScaleGestureDetector detector = mZoomManager.getMultiTouchGestureDetector();
5759
5760 long eventTime = event.getEventTime();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005761
5762 // Due to the touch screen edge effect, a touch closer to the edge
5763 // always snapped to the edge. As getViewWidth() can be different from
5764 // getWidth() due to the scrollbar, adjusting the point to match
5765 // getViewWidth(). Same applied to the height.
5766 x = Math.min(x, getViewWidth() - 1);
5767 y = Math.min(y, getViewHeightWithTitle() - 1);
5768
5769 int deltaX = mLastTouchX - x;
5770 int deltaY = mLastTouchY - y;
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00005771 int contentX = viewToContentX(x + getScrollX());
5772 int contentY = viewToContentY(y + getScrollY());
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005773
5774 switch (action) {
5775 case MotionEvent.ACTION_DOWN: {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005776 mConfirmMove = false;
5777 mInitialHitTestResult = null;
George Mountf70276a2012-03-12 14:22:10 -07005778 if (!mEditTextScroller.isFinished()) {
5779 mEditTextScroller.abortAnimation();
5780 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005781 if (!mScroller.isFinished()) {
5782 // stop the current scroll animation, but if this is
5783 // the start of a fling, allow it to add to the current
5784 // fling's velocity
5785 mScroller.abortAnimation();
5786 mTouchMode = TOUCH_DRAG_START_MODE;
5787 mConfirmMove = true;
5788 nativeSetIsScrolling(false);
5789 } else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) {
5790 mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP);
John Reckb2676f72012-03-02 14:13:06 -08005791 removeTouchHighlight();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005792 if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) {
5793 mTouchMode = TOUCH_DOUBLE_TAP_MODE;
5794 } else {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005795 mTouchMode = TOUCH_INIT_MODE;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005796 }
5797 } else { // the normal case
5798 mTouchMode = TOUCH_INIT_MODE;
Jeff Brown9d3bdbd2012-03-21 11:50:06 -07005799 // TODO: Have WebViewInputDispatch handle this
John Reckb2676f72012-03-02 14:13:06 -08005800 TouchHighlightData data = new TouchHighlightData();
5801 data.mX = contentX;
5802 data.mY = contentY;
5803 data.mNativeLayerRect = new Rect();
5804 data.mNativeLayer = nativeScrollableLayer(
5805 contentX, contentY, data.mNativeLayerRect, null);
5806 data.mSlop = viewToContentDimension(mNavSlop);
John Reck2d8c13b2012-04-12 14:15:54 -07005807 removeTouchHighlight();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005808 if (!mBlockWebkitViewMessages) {
John Reck2d8c13b2012-04-12 14:15:54 -07005809 mTouchHighlightRequested = SystemClock.uptimeMillis();
John Reckb2676f72012-03-02 14:13:06 -08005810 mWebViewCore.sendMessageAtFrontOfQueue(
5811 EventHub.HIT_TEST, data);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005812 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005813 if (mLogEvent && eventTime - mLastTouchUpTime < 1000) {
5814 EventLog.writeEvent(EventLogTags.BROWSER_DOUBLE_TAP_DURATION,
5815 (eventTime - mLastTouchUpTime), eventTime);
5816 }
5817 mSelectionStarted = false;
5818 if (mSelectingText) {
George Mountf9c1f992012-03-21 16:06:10 -07005819 ensureSelectionHandles();
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00005820 int shiftedY = y - getTitleHeight() + getScrollY();
5821 int shiftedX = x + getScrollX();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005822 if (mSelectHandleCenter != null && mSelectHandleCenter.getBounds()
5823 .contains(shiftedX, shiftedY)) {
5824 mSelectionStarted = true;
5825 mSelectDraggingCursor = mSelectCursorBase;
George Mountf9c1f992012-03-21 16:06:10 -07005826 mSelectDraggingOffset = mSelectHandleCenterOffset;
5827 mSelectDraggingTextQuad = mSelectCursorBaseTextQuad;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005828 mPrivateHandler.removeMessages(CLEAR_CARET_HANDLE);
5829 hidePasteButton();
5830 } else if (mSelectHandleLeft != null
5831 && mSelectHandleLeft.getBounds()
5832 .contains(shiftedX, shiftedY)) {
5833 mSelectionStarted = true;
5834 mSelectDraggingCursor = mSelectCursorBase;
George Mountf9c1f992012-03-21 16:06:10 -07005835 mSelectDraggingOffset = mSelectHandleLeftOffset;
5836 mSelectDraggingTextQuad = mSelectCursorBaseTextQuad;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005837 } else if (mSelectHandleRight != null
5838 && mSelectHandleRight.getBounds()
5839 .contains(shiftedX, shiftedY)) {
5840 mSelectionStarted = true;
5841 mSelectDraggingCursor = mSelectCursorExtent;
George Mountf9c1f992012-03-21 16:06:10 -07005842 mSelectDraggingOffset = mSelectHandleRightOffset;
5843 mSelectDraggingTextQuad = mSelectCursorExtentTextQuad;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005844 } else if (mIsCaretSelection) {
5845 selectionDone();
5846 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005847 if (DebugFlags.WEB_VIEW) {
5848 Log.v(LOGTAG, "select=" + contentX + "," + contentY);
5849 }
5850 }
5851 }
5852 // Trigger the link
5853 if (!mSelectingText && (mTouchMode == TOUCH_INIT_MODE
5854 || mTouchMode == TOUCH_DOUBLE_TAP_MODE)) {
5855 mPrivateHandler.sendEmptyMessageDelayed(
5856 SWITCH_TO_SHORTPRESS, TAP_TIMEOUT);
5857 mPrivateHandler.sendEmptyMessageDelayed(
5858 SWITCH_TO_LONGPRESS, LONG_PRESS_TIMEOUT);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005859 }
5860 startTouch(x, y, eventTime);
George Mountf70276a2012-03-12 14:22:10 -07005861 if (mIsEditingText) {
George Mount7102eb22012-04-10 13:41:51 -07005862 mTouchInEditText = mEditTextContentBounds
5863 .contains(contentX, contentY);
George Mountf70276a2012-03-12 14:22:10 -07005864 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005865 break;
5866 }
5867 case MotionEvent.ACTION_MOVE: {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005868 if (!mConfirmMove && (deltaX * deltaX + deltaY * deltaY)
5869 >= mTouchSlopSquare) {
5870 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
5871 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
5872 mConfirmMove = true;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005873 if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
5874 mTouchMode = TOUCH_INIT_MODE;
5875 }
John Reckb2676f72012-03-02 14:13:06 -08005876 removeTouchHighlight();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005877 }
5878 if (mSelectingText && mSelectionStarted) {
5879 if (DebugFlags.WEB_VIEW) {
5880 Log.v(LOGTAG, "extend=" + contentX + "," + contentY);
5881 }
Jonathan Dixon3c909522012-02-28 18:45:06 +00005882 ViewParent parent = mWebView.getParent();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005883 if (parent != null) {
5884 parent.requestDisallowInterceptTouchEvent(true);
5885 }
5886 if (deltaX != 0 || deltaY != 0) {
George Mount7102eb22012-04-10 13:41:51 -07005887 int handleX = contentX +
5888 viewToContentDimension(mSelectDraggingOffset.x);
5889 int handleY = contentY +
5890 viewToContentDimension(mSelectDraggingOffset.y);
5891 mSelectDraggingCursor.set(handleX, handleY);
5892 boolean inCursorText =
5893 mSelectDraggingTextQuad.containsPoint(handleX, handleY);
5894 boolean inEditBounds = mEditTextContentBounds
5895 .contains(handleX, handleY);
George Mount557748d2012-04-04 13:56:49 -07005896 if (mIsEditingText && !inEditBounds) {
5897 beginScrollEdit();
5898 } else {
5899 endScrollEdit();
5900 }
George Mount7102eb22012-04-10 13:41:51 -07005901 if (inCursorText || (mIsEditingText && !inEditBounds)) {
5902 snapDraggingCursor();
5903 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005904 updateWebkitSelection();
George Mount7102eb22012-04-10 13:41:51 -07005905 if (!inCursorText && mIsEditingText && inEditBounds) {
5906 // Visually snap even if we have moved the handle.
5907 snapDraggingCursor();
5908 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005909 mLastTouchX = x;
5910 mLastTouchY = y;
5911 invalidate();
5912 }
5913 break;
5914 }
5915
Jeff Brown9d3bdbd2012-03-21 11:50:06 -07005916 if (mTouchMode == TOUCH_DONE_MODE) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005917 // no dragging during scroll zoom animation, or when prevent
5918 // default is yes
5919 break;
5920 }
5921 if (mVelocityTracker == null) {
5922 Log.e(LOGTAG, "Got null mVelocityTracker when "
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005923 + " mTouchMode = " + mTouchMode);
5924 } else {
Jeff Brown9d3bdbd2012-03-21 11:50:06 -07005925 mVelocityTracker.addMovement(event);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005926 }
5927
5928 if (mTouchMode != TOUCH_DRAG_MODE &&
George Mountfcff68f2012-03-20 10:21:57 -07005929 mTouchMode != TOUCH_DRAG_LAYER_MODE &&
5930 mTouchMode != TOUCH_DRAG_TEXT_MODE) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005931
5932 if (!mConfirmMove) {
5933 break;
5934 }
5935
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005936 // Only lock dragging to one axis if we don't have a scale in progress.
5937 // Scaling implies free-roaming movement. Note this is only ever a question
5938 // if mZoomManager.supportsPanDuringZoom() is true.
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005939 mAverageAngle = calculateDragAngle(deltaX, deltaY);
5940 if (detector == null || !detector.isInProgress()) {
5941 // if it starts nearly horizontal or vertical, enforce it
5942 if (mAverageAngle < HSLOPE_TO_START_SNAP) {
5943 mSnapScrollMode = SNAP_X;
5944 mSnapPositive = deltaX > 0;
5945 mAverageAngle = ANGLE_HORIZ;
5946 } else if (mAverageAngle > VSLOPE_TO_START_SNAP) {
5947 mSnapScrollMode = SNAP_Y;
5948 mSnapPositive = deltaY > 0;
5949 mAverageAngle = ANGLE_VERT;
5950 }
5951 }
5952
5953 mTouchMode = TOUCH_DRAG_MODE;
5954 mLastTouchX = x;
5955 mLastTouchY = y;
5956 deltaX = 0;
5957 deltaY = 0;
5958
5959 startScrollingLayer(x, y);
5960 startDrag();
5961 }
5962
5963 // do pan
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005964 boolean keepScrollBarsVisible = false;
5965 if (deltaX == 0 && deltaY == 0) {
Jeff Brown9d3bdbd2012-03-21 11:50:06 -07005966 keepScrollBarsVisible = true;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08005967 } else {
5968 mAverageAngle +=
5969 (calculateDragAngle(deltaX, deltaY) - mAverageAngle)
5970 / MMA_WEIGHT_N;
5971 if (mSnapScrollMode != SNAP_NONE) {
5972 if (mSnapScrollMode == SNAP_Y) {
5973 // radical change means getting out of snap mode
5974 if (mAverageAngle < VSLOPE_TO_BREAK_SNAP) {
5975 mSnapScrollMode = SNAP_NONE;
5976 }
5977 }
5978 if (mSnapScrollMode == SNAP_X) {
5979 // radical change means getting out of snap mode
5980 if (mAverageAngle > HSLOPE_TO_BREAK_SNAP) {
5981 mSnapScrollMode = SNAP_NONE;
5982 }
5983 }
5984 } else {
5985 if (mAverageAngle < HSLOPE_TO_START_SNAP) {
5986 mSnapScrollMode = SNAP_X;
5987 mSnapPositive = deltaX > 0;
5988 mAverageAngle = (mAverageAngle + ANGLE_HORIZ) / 2;
5989 } else if (mAverageAngle > VSLOPE_TO_START_SNAP) {
5990 mSnapScrollMode = SNAP_Y;
5991 mSnapPositive = deltaY > 0;
5992 mAverageAngle = (mAverageAngle + ANGLE_VERT) / 2;
5993 }
5994 }
5995 if (mSnapScrollMode != SNAP_NONE) {
5996 if ((mSnapScrollMode & SNAP_X) == SNAP_X) {
5997 deltaY = 0;
5998 } else {
5999 deltaX = 0;
6000 }
6001 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006002 if (deltaX * deltaX + deltaY * deltaY > mTouchSlopSquare) {
6003 mHeldMotionless = MOTIONLESS_FALSE;
6004 nativeSetIsScrolling(true);
6005 } else {
6006 mHeldMotionless = MOTIONLESS_TRUE;
6007 nativeSetIsScrolling(false);
6008 keepScrollBarsVisible = true;
6009 }
6010
6011 mLastTouchTime = eventTime;
George Mountfcff68f2012-03-20 10:21:57 -07006012 boolean allDrag = doDrag(deltaX, deltaY);
6013 if (allDrag) {
6014 mLastTouchX = x;
6015 mLastTouchY = y;
6016 } else {
6017 int contentDeltaX = (int)Math.floor(deltaX * mZoomManager.getInvScale());
6018 int roundedDeltaX = contentToViewDimension(contentDeltaX);
6019 int contentDeltaY = (int)Math.floor(deltaY * mZoomManager.getInvScale());
6020 int roundedDeltaY = contentToViewDimension(contentDeltaY);
6021 mLastTouchX -= roundedDeltaX;
6022 mLastTouchY -= roundedDeltaY;
6023 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006024 }
6025
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006026 // Turn off scrollbars when dragging a layer.
6027 if (keepScrollBarsVisible &&
George Mountfcff68f2012-03-20 10:21:57 -07006028 mTouchMode != TOUCH_DRAG_LAYER_MODE &&
6029 mTouchMode != TOUCH_DRAG_TEXT_MODE) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006030 if (mHeldMotionless != MOTIONLESS_TRUE) {
6031 mHeldMotionless = MOTIONLESS_TRUE;
6032 invalidate();
6033 }
6034 // keep the scrollbar on the screen even there is no scroll
Jonathan Dixon3c909522012-02-28 18:45:06 +00006035 mWebViewPrivate.awakenScrollBars(ViewConfiguration.getScrollDefaultDelay(),
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006036 false);
6037 // Post a message so that we'll keep them alive while we're not scrolling.
6038 mPrivateHandler.sendMessageDelayed(mPrivateHandler
6039 .obtainMessage(AWAKEN_SCROLL_BARS),
6040 ViewConfiguration.getScrollDefaultDelay());
6041 // return false to indicate that we can't pan out of the
6042 // view space
Jeff Brown9d3bdbd2012-03-21 11:50:06 -07006043 return;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006044 } else {
6045 mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
6046 }
6047 break;
6048 }
6049 case MotionEvent.ACTION_UP: {
George Mount557748d2012-04-04 13:56:49 -07006050 endScrollEdit();
George Mountf796fdf2012-03-19 14:51:55 -07006051 if (!mConfirmMove && mIsEditingText && mSelectionStarted &&
6052 mIsCaretSelection) {
6053 showPasteWindow();
6054 stopTouch();
6055 break;
6056 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006057 mLastTouchUpTime = eventTime;
6058 if (mSentAutoScrollMessage) {
6059 mAutoScrollX = mAutoScrollY = 0;
6060 }
6061 switch (mTouchMode) {
6062 case TOUCH_DOUBLE_TAP_MODE: // double tap
6063 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
6064 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
Jeff Brown9d3bdbd2012-03-21 11:50:06 -07006065 mTouchMode = TOUCH_DONE_MODE;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006066 break;
6067 case TOUCH_INIT_MODE: // tap
6068 case TOUCH_SHORTPRESS_START_MODE:
6069 case TOUCH_SHORTPRESS_MODE:
6070 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
6071 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
Jeff Brown9d3bdbd2012-03-21 11:50:06 -07006072 if (!mConfirmMove) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006073 if (mSelectingText) {
6074 // tapping on selection or controls does nothing
6075 if (!mSelectionStarted) {
6076 selectionDone();
6077 }
6078 break;
6079 }
6080 // only trigger double tap if the WebView is
6081 // scalable
6082 if (mTouchMode == TOUCH_INIT_MODE
6083 && (canZoomIn() || canZoomOut())) {
6084 mPrivateHandler.sendEmptyMessageDelayed(
6085 RELEASE_SINGLE_TAP, ViewConfiguration
6086 .getDoubleTapTimeout());
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006087 }
6088 break;
6089 }
6090 case TOUCH_DRAG_MODE:
6091 case TOUCH_DRAG_LAYER_MODE:
George Mountfcff68f2012-03-20 10:21:57 -07006092 case TOUCH_DRAG_TEXT_MODE:
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006093 mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
6094 mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
6095 // if the user waits a while w/o moving before the
6096 // up, we don't want to do a fling
6097 if (eventTime - mLastTouchTime <= MIN_FLING_TIME) {
6098 if (mVelocityTracker == null) {
Jeff Brown9d3bdbd2012-03-21 11:50:06 -07006099 Log.e(LOGTAG, "Got null mVelocityTracker");
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006100 } else {
Jeff Brown9d3bdbd2012-03-21 11:50:06 -07006101 mVelocityTracker.addMovement(event);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006102 }
6103 // set to MOTIONLESS_IGNORE so that it won't keep
6104 // removing and sending message in
6105 // drawCoreAndCursorRing()
6106 mHeldMotionless = MOTIONLESS_IGNORE;
6107 doFling();
6108 break;
6109 } else {
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00006110 if (mScroller.springBack(getScrollX(), getScrollY(), 0,
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006111 computeMaxScrollX(), 0,
6112 computeMaxScrollY())) {
6113 invalidate();
6114 }
6115 }
6116 // redraw in high-quality, as we're done dragging
6117 mHeldMotionless = MOTIONLESS_TRUE;
6118 invalidate();
6119 // fall through
6120 case TOUCH_DRAG_START_MODE:
6121 // TOUCH_DRAG_START_MODE should not happen for the real
6122 // device as we almost certain will get a MOVE. But this
6123 // is possible on emulator.
6124 mLastVelocity = 0;
6125 WebViewCore.resumePriority();
6126 if (!mSelectingText) {
6127 WebViewCore.resumeUpdatePicture(mWebViewCore);
6128 }
6129 break;
6130 }
6131 stopTouch();
6132 break;
6133 }
6134 case MotionEvent.ACTION_CANCEL: {
6135 if (mTouchMode == TOUCH_DRAG_MODE) {
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00006136 mScroller.springBack(getScrollX(), getScrollY(), 0,
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006137 computeMaxScrollX(), 0, computeMaxScrollY());
6138 invalidate();
6139 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006140 cancelTouch();
6141 break;
6142 }
6143 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006144 }
6145
George Mount557748d2012-04-04 13:56:49 -07006146 /**
6147 * Returns the text scroll speed in content pixels per millisecond based on
6148 * the touch location.
6149 * @param coordinate The x or y touch coordinate in content space
6150 * @param min The minimum coordinate (x or y) of the edit content bounds
6151 * @param max The maximum coordinate (x or y) of the edit content bounds
6152 */
6153 private static float getTextScrollSpeed(int coordinate, int min, int max) {
6154 if (coordinate < min) {
6155 return (coordinate - min) * TEXT_SCROLL_RATE;
6156 } else if (coordinate >= max) {
6157 return (coordinate - max + 1) * TEXT_SCROLL_RATE;
6158 } else {
6159 return 0.0f;
6160 }
6161 }
6162
6163 private void beginScrollEdit() {
6164 if (mLastEditScroll == 0) {
6165 mLastEditScroll = SystemClock.uptimeMillis() -
6166 TEXT_SCROLL_FIRST_SCROLL_MS;
6167 scrollEditWithCursor();
6168 }
6169 }
6170
6171 private void endScrollEdit() {
6172 mLastEditScroll = 0;
6173 }
6174
6175 private static int getTextScrollDelta(float speed, long deltaT) {
6176 float distance = speed * deltaT;
6177 int intDistance = (int)Math.floor(distance);
6178 float probability = distance - intDistance;
6179 if (Math.random() < probability) {
6180 intDistance++;
6181 }
6182 return intDistance;
6183 }
6184 /**
6185 * Scrolls edit text a distance based on the last touch point,
6186 * the last scroll time, and the edit text content bounds.
6187 */
6188 private void scrollEditWithCursor() {
6189 if (mLastEditScroll != 0) {
6190 int x = viewToContentX(mLastTouchX + getScrollX() + mSelectDraggingOffset.x);
6191 float scrollSpeedX = getTextScrollSpeed(x, mEditTextContentBounds.left,
6192 mEditTextContentBounds.right);
6193 int y = viewToContentY(mLastTouchY + getScrollY() + mSelectDraggingOffset.y);
6194 float scrollSpeedY = getTextScrollSpeed(y, mEditTextContentBounds.top,
6195 mEditTextContentBounds.bottom);
6196 if (scrollSpeedX == 0.0f && scrollSpeedY == 0.0f) {
6197 endScrollEdit();
6198 } else {
6199 long currentTime = SystemClock.uptimeMillis();
6200 long timeSinceLastUpdate = currentTime - mLastEditScroll;
6201 int deltaX = getTextScrollDelta(scrollSpeedX, timeSinceLastUpdate);
6202 int deltaY = getTextScrollDelta(scrollSpeedY, timeSinceLastUpdate);
6203 mLastEditScroll = currentTime;
6204 if (deltaX == 0 && deltaY == 0) {
6205 // By probability no text scroll this time. Try again later.
6206 mPrivateHandler.sendEmptyMessageDelayed(SCROLL_EDIT_TEXT,
6207 TEXT_SCROLL_FIRST_SCROLL_MS);
6208 } else {
6209 int scrollX = getTextScrollX() + deltaX;
6210 scrollX = Math.min(getMaxTextScrollX(), scrollX);
6211 scrollX = Math.max(0, scrollX);
6212 int scrollY = getTextScrollY() + deltaY;
6213 scrollY = Math.min(getMaxTextScrollY(), scrollY);
6214 scrollY = Math.max(0, scrollY);
6215 scrollEditText(scrollX, scrollY);
6216 int cursorX = mSelectDraggingCursor.x;
6217 int cursorY = mSelectDraggingCursor.y;
6218 mSelectDraggingCursor.set(x - deltaX, y - deltaY);
6219 updateWebkitSelection();
6220 mSelectDraggingCursor.set(cursorX, cursorY);
6221 }
6222 }
6223 }
6224 }
6225
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006226 private void startTouch(float x, float y, long eventTime) {
6227 // Remember where the motion event started
6228 mStartTouchX = mLastTouchX = Math.round(x);
6229 mStartTouchY = mLastTouchY = Math.round(y);
6230 mLastTouchTime = eventTime;
6231 mVelocityTracker = VelocityTracker.obtain();
6232 mSnapScrollMode = SNAP_NONE;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006233 }
6234
6235 private void startDrag() {
6236 WebViewCore.reducePriority();
6237 // to get better performance, pause updating the picture
6238 WebViewCore.pauseUpdatePicture(mWebViewCore);
6239 nativeSetIsScrolling(true);
6240
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006241 if (mHorizontalScrollBarMode != SCROLLBAR_ALWAYSOFF
6242 || mVerticalScrollBarMode != SCROLLBAR_ALWAYSOFF) {
6243 mZoomManager.invokeZoomPicker();
6244 }
6245 }
6246
George Mountfcff68f2012-03-20 10:21:57 -07006247 private boolean doDrag(int deltaX, int deltaY) {
6248 boolean allDrag = true;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006249 if ((deltaX | deltaY) != 0) {
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00006250 int oldX = getScrollX();
6251 int oldY = getScrollY();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006252 int rangeX = computeMaxScrollX();
6253 int rangeY = computeMaxScrollY();
George Mountfcff68f2012-03-20 10:21:57 -07006254 final int contentX = (int)Math.floor(deltaX * mZoomManager.getInvScale());
6255 final int contentY = (int)Math.floor(deltaY * mZoomManager.getInvScale());
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006256
George Mountfcff68f2012-03-20 10:21:57 -07006257 // Assume page scrolling and change below if we're wrong
6258 mTouchMode = TOUCH_DRAG_MODE;
6259
6260 // Check special scrolling before going to main page scrolling.
6261 if (mIsEditingText && mTouchInEditText && canTextScroll(deltaX, deltaY)) {
6262 // Edit text scrolling
6263 oldX = getTextScrollX();
6264 rangeX = getMaxTextScrollX();
6265 deltaX = contentX;
6266 oldY = getTextScrollY();
6267 rangeY = getMaxTextScrollY();
6268 deltaY = contentY;
6269 mTouchMode = TOUCH_DRAG_TEXT_MODE;
6270 allDrag = false;
6271 } else if (mCurrentScrollingLayerId != 0) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006272 // Check the scrolling bounds to see if we will actually do any
6273 // scrolling. The rectangle is in document coordinates.
6274 final int maxX = mScrollingLayerRect.right;
6275 final int maxY = mScrollingLayerRect.bottom;
6276 final int resultX = Math.max(0,
6277 Math.min(mScrollingLayerRect.left + contentX, maxX));
6278 final int resultY = Math.max(0,
6279 Math.min(mScrollingLayerRect.top + contentY, maxY));
6280
6281 if (resultX != mScrollingLayerRect.left ||
6282 resultY != mScrollingLayerRect.top) {
6283 // In case we switched to dragging the page.
6284 mTouchMode = TOUCH_DRAG_LAYER_MODE;
6285 deltaX = contentX;
6286 deltaY = contentY;
6287 oldX = mScrollingLayerRect.left;
6288 oldY = mScrollingLayerRect.top;
6289 rangeX = maxX;
6290 rangeY = maxY;
George Mountfcff68f2012-03-20 10:21:57 -07006291 allDrag = false;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006292 }
6293 }
6294
6295 if (mOverScrollGlow != null) {
6296 mOverScrollGlow.setOverScrollDeltas(deltaX, deltaY);
6297 }
6298
Jonathan Dixon3c909522012-02-28 18:45:06 +00006299 mWebViewPrivate.overScrollBy(deltaX, deltaY, oldX, oldY,
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006300 rangeX, rangeY,
6301 mOverscrollDistance, mOverscrollDistance, true);
6302 if (mOverScrollGlow != null && mOverScrollGlow.isAnimating()) {
6303 invalidate();
6304 }
6305 }
6306 mZoomManager.keepZoomPickerVisible();
George Mountfcff68f2012-03-20 10:21:57 -07006307 return allDrag;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006308 }
6309
6310 private void stopTouch() {
6311 if (mScroller.isFinished() && !mSelectingText
George Mountfcff68f2012-03-20 10:21:57 -07006312 && (mTouchMode == TOUCH_DRAG_MODE
6313 || mTouchMode == TOUCH_DRAG_LAYER_MODE)) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006314 WebViewCore.resumePriority();
6315 WebViewCore.resumeUpdatePicture(mWebViewCore);
6316 nativeSetIsScrolling(false);
6317 }
6318
6319 // we also use mVelocityTracker == null to tell us that we are
6320 // not "moving around", so we can take the slower/prettier
6321 // mode in the drawing code
6322 if (mVelocityTracker != null) {
6323 mVelocityTracker.recycle();
6324 mVelocityTracker = null;
6325 }
6326
6327 // Release any pulled glows
6328 if (mOverScrollGlow != null) {
6329 mOverScrollGlow.releaseAll();
6330 }
6331
6332 if (mSelectingText) {
6333 mSelectionStarted = false;
6334 syncSelectionCursors();
6335 if (mIsCaretSelection) {
6336 resetCaretTimer();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006337 }
6338 invalidate();
6339 }
6340 }
6341
6342 private void cancelTouch() {
6343 // we also use mVelocityTracker == null to tell us that we are
6344 // not "moving around", so we can take the slower/prettier
6345 // mode in the drawing code
6346 if (mVelocityTracker != null) {
6347 mVelocityTracker.recycle();
6348 mVelocityTracker = null;
6349 }
6350
6351 if ((mTouchMode == TOUCH_DRAG_MODE
6352 || mTouchMode == TOUCH_DRAG_LAYER_MODE) && !mSelectingText) {
6353 WebViewCore.resumePriority();
6354 WebViewCore.resumeUpdatePicture(mWebViewCore);
6355 nativeSetIsScrolling(false);
6356 }
6357 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
6358 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
6359 mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
6360 mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
John Reckb2676f72012-03-02 14:13:06 -08006361 removeTouchHighlight();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006362 mHeldMotionless = MOTIONLESS_TRUE;
6363 mTouchMode = TOUCH_DONE_MODE;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006364 }
6365
George Mount7102eb22012-04-10 13:41:51 -07006366 private void snapDraggingCursor() {
6367 float scale = scaleAlongSegment(
6368 mSelectDraggingCursor.x, mSelectDraggingCursor.y,
6369 mSelectDraggingTextQuad.p4, mSelectDraggingTextQuad.p3);
6370 // clamp scale to ensure point is on the bottom segment
6371 scale = Math.max(0.0f, scale);
6372 scale = Math.min(scale, 1.0f);
6373 float newX = scaleCoordinate(scale,
6374 mSelectDraggingTextQuad.p4.x, mSelectDraggingTextQuad.p3.x);
6375 float newY = scaleCoordinate(scale,
6376 mSelectDraggingTextQuad.p4.y, mSelectDraggingTextQuad.p3.y);
6377 int x = Math.max(mEditTextContentBounds.left,
6378 Math.min(mEditTextContentBounds.right, Math.round(newX)));
6379 int y = Math.max(mEditTextContentBounds.top,
6380 Math.min(mEditTextContentBounds.bottom, Math.round(newY)));
6381 mSelectDraggingCursor.set(x, y);
George Mountf9c1f992012-03-21 16:06:10 -07006382 }
6383
6384 private static float scaleCoordinate(float scale, float coord1, float coord2) {
6385 float diff = coord2 - coord1;
6386 return coord1 + (scale * diff);
6387 }
6388
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006389 @Override
6390 public boolean onGenericMotionEvent(MotionEvent event) {
6391 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
6392 switch (event.getAction()) {
6393 case MotionEvent.ACTION_SCROLL: {
6394 final float vscroll;
6395 final float hscroll;
6396 if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
6397 vscroll = 0;
6398 hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
6399 } else {
6400 vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
6401 hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
6402 }
6403 if (hscroll != 0 || vscroll != 0) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00006404 final int vdelta = (int) (vscroll *
6405 mWebViewPrivate.getVerticalScrollFactor());
6406 final int hdelta = (int) (hscroll *
6407 mWebViewPrivate.getHorizontalScrollFactor());
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006408 if (pinScrollBy(hdelta, vdelta, false, 0)) {
6409 return true;
6410 }
6411 }
6412 }
6413 }
6414 }
Jonathan Dixon3c909522012-02-28 18:45:06 +00006415 return mWebViewPrivate.super_onGenericMotionEvent(event);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006416 }
6417
6418 private long mTrackballFirstTime = 0;
6419 private long mTrackballLastTime = 0;
6420 private float mTrackballRemainsX = 0.0f;
6421 private float mTrackballRemainsY = 0.0f;
6422 private int mTrackballXMove = 0;
6423 private int mTrackballYMove = 0;
6424 private boolean mSelectingText = false;
6425 private boolean mSelectionStarted = false;
6426 private static final int TRACKBALL_KEY_TIMEOUT = 1000;
6427 private static final int TRACKBALL_TIMEOUT = 200;
6428 private static final int TRACKBALL_WAIT = 100;
6429 private static final int TRACKBALL_SCALE = 400;
6430 private static final int TRACKBALL_SCROLL_COUNT = 5;
6431 private static final int TRACKBALL_MOVE_COUNT = 10;
6432 private static final int TRACKBALL_MULTIPLIER = 3;
6433 private static final int SELECT_CURSOR_OFFSET = 16;
6434 private static final int SELECT_SCROLL = 5;
6435 private int mSelectX = 0;
6436 private int mSelectY = 0;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006437 private boolean mTrackballDown = false;
6438 private long mTrackballUpTime = 0;
6439 private long mLastCursorTime = 0;
6440 private Rect mLastCursorBounds;
George Mount30d773f2012-04-20 08:34:44 -07006441 private SelectionHandleAlpha mHandleAlpha = new SelectionHandleAlpha();
6442 private ObjectAnimator mHandleAlphaAnimator =
6443 ObjectAnimator.ofInt(mHandleAlpha, "alpha", 0);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006444
6445 // Set by default; BrowserActivity clears to interpret trackball data
6446 // directly for movement. Currently, the framework only passes
6447 // arrow key events, not trackball events, from one child to the next
6448 private boolean mMapTrackballToArrowKeys = true;
6449
6450 private DrawData mDelaySetPicture;
6451 private DrawData mLoadedPicture;
6452
6453 public void setMapTrackballToArrowKeys(boolean setMap) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006454 mMapTrackballToArrowKeys = setMap;
6455 }
6456
6457 void resetTrackballTime() {
6458 mTrackballLastTime = 0;
6459 }
6460
6461 @Override
6462 public boolean onTrackballEvent(MotionEvent ev) {
6463 long time = ev.getEventTime();
6464 if ((ev.getMetaState() & KeyEvent.META_ALT_ON) != 0) {
6465 if (ev.getY() > 0) pageDown(true);
6466 if (ev.getY() < 0) pageUp(true);
6467 return true;
6468 }
6469 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
6470 if (mSelectingText) {
6471 return true; // discard press if copy in progress
6472 }
6473 mTrackballDown = true;
6474 if (mNativeClass == 0) {
6475 return false;
6476 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006477 if (DebugFlags.WEB_VIEW) {
6478 Log.v(LOGTAG, "onTrackballEvent down ev=" + ev
6479 + " time=" + time
6480 + " mLastCursorTime=" + mLastCursorTime);
6481 }
Jonathan Dixon3c909522012-02-28 18:45:06 +00006482 if (mWebView.isInTouchMode()) mWebView.requestFocusFromTouch();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006483 return false; // let common code in onKeyDown at it
6484 }
6485 if (ev.getAction() == MotionEvent.ACTION_UP) {
6486 // LONG_PRESS_CENTER is set in common onKeyDown
6487 mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
6488 mTrackballDown = false;
6489 mTrackballUpTime = time;
6490 if (mSelectingText) {
6491 copySelection();
6492 selectionDone();
6493 return true; // discard press if copy in progress
6494 }
6495 if (DebugFlags.WEB_VIEW) {
6496 Log.v(LOGTAG, "onTrackballEvent up ev=" + ev
6497 + " time=" + time
6498 );
6499 }
6500 return false; // let common code in onKeyUp at it
6501 }
6502 if ((mMapTrackballToArrowKeys && (ev.getMetaState() & KeyEvent.META_SHIFT_ON) == 0) ||
6503 AccessibilityManager.getInstance(mContext).isEnabled()) {
6504 if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent gmail quit");
6505 return false;
6506 }
6507 if (mTrackballDown) {
6508 if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent down quit");
6509 return true; // discard move if trackball is down
6510 }
6511 if (time - mTrackballUpTime < TRACKBALL_TIMEOUT) {
6512 if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent up timeout quit");
6513 return true;
6514 }
6515 // TODO: alternatively we can do panning as touch does
6516 switchOutDrawHistory();
6517 if (time - mTrackballLastTime > TRACKBALL_TIMEOUT) {
6518 if (DebugFlags.WEB_VIEW) {
6519 Log.v(LOGTAG, "onTrackballEvent time="
6520 + time + " last=" + mTrackballLastTime);
6521 }
6522 mTrackballFirstTime = time;
6523 mTrackballXMove = mTrackballYMove = 0;
6524 }
6525 mTrackballLastTime = time;
6526 if (DebugFlags.WEB_VIEW) {
6527 Log.v(LOGTAG, "onTrackballEvent ev=" + ev + " time=" + time);
6528 }
6529 mTrackballRemainsX += ev.getX();
6530 mTrackballRemainsY += ev.getY();
6531 doTrackball(time, ev.getMetaState());
6532 return true;
6533 }
6534
6535 private int scaleTrackballX(float xRate, int width) {
6536 int xMove = (int) (xRate / TRACKBALL_SCALE * width);
6537 int nextXMove = xMove;
6538 if (xMove > 0) {
6539 if (xMove > mTrackballXMove) {
6540 xMove -= mTrackballXMove;
6541 }
6542 } else if (xMove < mTrackballXMove) {
6543 xMove -= mTrackballXMove;
6544 }
6545 mTrackballXMove = nextXMove;
6546 return xMove;
6547 }
6548
6549 private int scaleTrackballY(float yRate, int height) {
6550 int yMove = (int) (yRate / TRACKBALL_SCALE * height);
6551 int nextYMove = yMove;
6552 if (yMove > 0) {
6553 if (yMove > mTrackballYMove) {
6554 yMove -= mTrackballYMove;
6555 }
6556 } else if (yMove < mTrackballYMove) {
6557 yMove -= mTrackballYMove;
6558 }
6559 mTrackballYMove = nextYMove;
6560 return yMove;
6561 }
6562
6563 private int keyCodeToSoundsEffect(int keyCode) {
6564 switch(keyCode) {
6565 case KeyEvent.KEYCODE_DPAD_UP:
6566 return SoundEffectConstants.NAVIGATION_UP;
6567 case KeyEvent.KEYCODE_DPAD_RIGHT:
6568 return SoundEffectConstants.NAVIGATION_RIGHT;
6569 case KeyEvent.KEYCODE_DPAD_DOWN:
6570 return SoundEffectConstants.NAVIGATION_DOWN;
6571 case KeyEvent.KEYCODE_DPAD_LEFT:
6572 return SoundEffectConstants.NAVIGATION_LEFT;
6573 }
John Reck4fa40372012-03-06 14:31:45 -08006574 return 0;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006575 }
6576
6577 private void doTrackball(long time, int metaState) {
6578 int elapsed = (int) (mTrackballLastTime - mTrackballFirstTime);
6579 if (elapsed == 0) {
6580 elapsed = TRACKBALL_TIMEOUT;
6581 }
6582 float xRate = mTrackballRemainsX * 1000 / elapsed;
6583 float yRate = mTrackballRemainsY * 1000 / elapsed;
6584 int viewWidth = getViewWidth();
6585 int viewHeight = getViewHeight();
6586 float ax = Math.abs(xRate);
6587 float ay = Math.abs(yRate);
6588 float maxA = Math.max(ax, ay);
6589 if (DebugFlags.WEB_VIEW) {
6590 Log.v(LOGTAG, "doTrackball elapsed=" + elapsed
6591 + " xRate=" + xRate
6592 + " yRate=" + yRate
6593 + " mTrackballRemainsX=" + mTrackballRemainsX
6594 + " mTrackballRemainsY=" + mTrackballRemainsY);
6595 }
6596 int width = mContentWidth - viewWidth;
6597 int height = mContentHeight - viewHeight;
6598 if (width < 0) width = 0;
6599 if (height < 0) height = 0;
6600 ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER);
6601 ay = Math.abs(mTrackballRemainsY * TRACKBALL_MULTIPLIER);
6602 maxA = Math.max(ax, ay);
6603 int count = Math.max(0, (int) maxA);
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00006604 int oldScrollX = getScrollX();
6605 int oldScrollY = getScrollY();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006606 if (count > 0) {
6607 int selectKeyCode = ax < ay ? mTrackballRemainsY < 0 ?
6608 KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN :
6609 mTrackballRemainsX < 0 ? KeyEvent.KEYCODE_DPAD_LEFT :
6610 KeyEvent.KEYCODE_DPAD_RIGHT;
6611 count = Math.min(count, TRACKBALL_MOVE_COUNT);
6612 if (DebugFlags.WEB_VIEW) {
6613 Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode
6614 + " count=" + count
6615 + " mTrackballRemainsX=" + mTrackballRemainsX
6616 + " mTrackballRemainsY=" + mTrackballRemainsY);
6617 }
John Reckb2676f72012-03-02 14:13:06 -08006618 if (mNativeClass != 0) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006619 for (int i = 0; i < count; i++) {
6620 letPageHandleNavKey(selectKeyCode, time, true, metaState);
6621 }
6622 letPageHandleNavKey(selectKeyCode, time, false, metaState);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006623 }
6624 mTrackballRemainsX = mTrackballRemainsY = 0;
6625 }
6626 if (count >= TRACKBALL_SCROLL_COUNT) {
6627 int xMove = scaleTrackballX(xRate, width);
6628 int yMove = scaleTrackballY(yRate, height);
6629 if (DebugFlags.WEB_VIEW) {
6630 Log.v(LOGTAG, "doTrackball pinScrollBy"
6631 + " count=" + count
6632 + " xMove=" + xMove + " yMove=" + yMove
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00006633 + " mScrollX-oldScrollX=" + (getScrollX()-oldScrollX)
6634 + " mScrollY-oldScrollY=" + (getScrollY()-oldScrollY)
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006635 );
6636 }
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00006637 if (Math.abs(getScrollX() - oldScrollX) > Math.abs(xMove)) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006638 xMove = 0;
6639 }
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00006640 if (Math.abs(getScrollY() - oldScrollY) > Math.abs(yMove)) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006641 yMove = 0;
6642 }
6643 if (xMove != 0 || yMove != 0) {
6644 pinScrollBy(xMove, yMove, true, 0);
6645 }
6646 }
6647 }
6648
6649 /**
6650 * Compute the maximum horizontal scroll position. Used by {@link OverScrollGlow}.
6651 * @return Maximum horizontal scroll position within real content
6652 */
6653 int computeMaxScrollX() {
6654 return Math.max(computeRealHorizontalScrollRange() - getViewWidth(), 0);
6655 }
6656
6657 /**
6658 * Compute the maximum vertical scroll position. Used by {@link OverScrollGlow}.
6659 * @return Maximum vertical scroll position within real content
6660 */
6661 int computeMaxScrollY() {
6662 return Math.max(computeRealVerticalScrollRange() + getTitleHeight()
6663 - getViewHeightWithTitle(), 0);
6664 }
6665
6666 boolean updateScrollCoordinates(int x, int y) {
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00006667 int oldX = getScrollX();
6668 int oldY = getScrollY();
6669 setScrollXRaw(x);
6670 setScrollYRaw(y);
6671 if (oldX != getScrollX() || oldY != getScrollY()) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00006672 mWebViewPrivate.onScrollChanged(getScrollX(), getScrollY(), oldX, oldY);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006673 return true;
6674 } else {
6675 return false;
6676 }
6677 }
6678
6679 public void flingScroll(int vx, int vy) {
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00006680 mScroller.fling(getScrollX(), getScrollY(), vx, vy, 0, computeMaxScrollX(), 0,
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006681 computeMaxScrollY(), mOverflingDistance, mOverflingDistance);
6682 invalidate();
6683 }
6684
6685 private void doFling() {
6686 if (mVelocityTracker == null) {
6687 return;
6688 }
6689 int maxX = computeMaxScrollX();
6690 int maxY = computeMaxScrollY();
6691
6692 mVelocityTracker.computeCurrentVelocity(1000, mMaximumFling);
6693 int vx = (int) mVelocityTracker.getXVelocity();
6694 int vy = (int) mVelocityTracker.getYVelocity();
6695
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00006696 int scrollX = getScrollX();
6697 int scrollY = getScrollY();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006698 int overscrollDistance = mOverscrollDistance;
6699 int overflingDistance = mOverflingDistance;
6700
6701 // Use the layer's scroll data if applicable.
6702 if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
6703 scrollX = mScrollingLayerRect.left;
6704 scrollY = mScrollingLayerRect.top;
6705 maxX = mScrollingLayerRect.right;
6706 maxY = mScrollingLayerRect.bottom;
6707 // No overscrolling for layers.
6708 overscrollDistance = overflingDistance = 0;
George Mountfcff68f2012-03-20 10:21:57 -07006709 } else if (mTouchMode == TOUCH_DRAG_TEXT_MODE) {
6710 scrollX = getTextScrollX();
6711 scrollY = getTextScrollY();
6712 maxX = getMaxTextScrollX();
6713 maxY = getMaxTextScrollY();
6714 // No overscrolling for edit text.
6715 overscrollDistance = overflingDistance = 0;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006716 }
6717
6718 if (mSnapScrollMode != SNAP_NONE) {
6719 if ((mSnapScrollMode & SNAP_X) == SNAP_X) {
6720 vy = 0;
6721 } else {
6722 vx = 0;
6723 }
6724 }
6725 if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) {
6726 WebViewCore.resumePriority();
6727 if (!mSelectingText) {
6728 WebViewCore.resumeUpdatePicture(mWebViewCore);
6729 }
6730 if (mScroller.springBack(scrollX, scrollY, 0, maxX, 0, maxY)) {
6731 invalidate();
6732 }
6733 return;
6734 }
6735 float currentVelocity = mScroller.getCurrVelocity();
6736 float velocity = (float) Math.hypot(vx, vy);
6737 if (mLastVelocity > 0 && currentVelocity > 0 && velocity
6738 > mLastVelocity * MINIMUM_VELOCITY_RATIO_FOR_ACCELERATION) {
6739 float deltaR = (float) (Math.abs(Math.atan2(mLastVelY, mLastVelX)
6740 - Math.atan2(vy, vx)));
6741 final float circle = (float) (Math.PI) * 2.0f;
6742 if (deltaR > circle * 0.9f || deltaR < circle * 0.1f) {
6743 vx += currentVelocity * mLastVelX / mLastVelocity;
6744 vy += currentVelocity * mLastVelY / mLastVelocity;
6745 velocity = (float) Math.hypot(vx, vy);
6746 if (DebugFlags.WEB_VIEW) {
6747 Log.v(LOGTAG, "doFling vx= " + vx + " vy=" + vy);
6748 }
6749 } else if (DebugFlags.WEB_VIEW) {
6750 Log.v(LOGTAG, "doFling missed " + deltaR / circle);
6751 }
6752 } else if (DebugFlags.WEB_VIEW) {
6753 Log.v(LOGTAG, "doFling start last=" + mLastVelocity
6754 + " current=" + currentVelocity
6755 + " vx=" + vx + " vy=" + vy
6756 + " maxX=" + maxX + " maxY=" + maxY
6757 + " scrollX=" + scrollX + " scrollY=" + scrollY
6758 + " layer=" + mCurrentScrollingLayerId);
6759 }
6760
6761 // Allow sloppy flings without overscrolling at the edges.
6762 if ((scrollX == 0 || scrollX == maxX) && Math.abs(vx) < Math.abs(vy)) {
6763 vx = 0;
6764 }
6765 if ((scrollY == 0 || scrollY == maxY) && Math.abs(vy) < Math.abs(vx)) {
6766 vy = 0;
6767 }
6768
6769 if (overscrollDistance < overflingDistance) {
6770 if ((vx > 0 && scrollX == -overscrollDistance) ||
6771 (vx < 0 && scrollX == maxX + overscrollDistance)) {
6772 vx = 0;
6773 }
6774 if ((vy > 0 && scrollY == -overscrollDistance) ||
6775 (vy < 0 && scrollY == maxY + overscrollDistance)) {
6776 vy = 0;
6777 }
6778 }
6779
6780 mLastVelX = vx;
6781 mLastVelY = vy;
6782 mLastVelocity = velocity;
6783
6784 // no horizontal overscroll if the content just fits
6785 mScroller.fling(scrollX, scrollY, -vx, -vy, 0, maxX, 0, maxY,
6786 maxX == 0 ? 0 : overflingDistance, overflingDistance);
6787 // Duration is calculated based on velocity. With range boundaries and overscroll
6788 // we may not know how long the final animation will take. (Hence the deprecation
6789 // warning on the call below.) It's not a big deal for scroll bars but if webcore
6790 // resumes during this effect we will take a performance hit. See computeScroll;
6791 // we resume webcore there when the animation is finished.
6792 final int time = mScroller.getDuration();
6793
6794 // Suppress scrollbars for layer scrolling.
George Mountfcff68f2012-03-20 10:21:57 -07006795 if (mTouchMode != TOUCH_DRAG_LAYER_MODE && mTouchMode != TOUCH_DRAG_TEXT_MODE) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00006796 mWebViewPrivate.awakenScrollBars(time);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006797 }
6798
6799 invalidate();
6800 }
6801
6802 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00006803 * See {@link WebView#getZoomControls()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006804 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00006805 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006806 @Deprecated
6807 public View getZoomControls() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006808 if (!getSettings().supportZoom()) {
6809 Log.w(LOGTAG, "This WebView doesn't support zoom.");
6810 return null;
6811 }
6812 return mZoomManager.getExternalZoomPicker();
6813 }
6814
6815 void dismissZoomControl() {
6816 mZoomManager.dismissZoomPicker();
6817 }
6818
6819 float getDefaultZoomScale() {
6820 return mZoomManager.getDefaultScale();
6821 }
6822
6823 /**
6824 * Return the overview scale of the WebView
6825 * @return The overview scale.
6826 */
6827 float getZoomOverviewScale() {
6828 return mZoomManager.getZoomOverviewScale();
6829 }
6830
6831 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00006832 * See {@link WebView#canZoomIn()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006833 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00006834 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006835 public boolean canZoomIn() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006836 return mZoomManager.canZoomIn();
6837 }
6838
6839 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00006840 * See {@link WebView#canZoomOut()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006841 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00006842 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006843 public boolean canZoomOut() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006844 return mZoomManager.canZoomOut();
6845 }
6846
6847 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00006848 * See {@link WebView#zoomIn()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006849 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00006850 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006851 public boolean zoomIn() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006852 return mZoomManager.zoomIn();
6853 }
6854
6855 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00006856 * See {@link WebView#zoomOut()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006857 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00006858 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006859 public boolean zoomOut() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006860 return mZoomManager.zoomOut();
6861 }
6862
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006863 /*
6864 * Return true if the rect (e.g. plugin) is fully visible and maximized
6865 * inside the WebView.
6866 */
6867 boolean isRectFitOnScreen(Rect rect) {
6868 final int rectWidth = rect.width();
6869 final int rectHeight = rect.height();
6870 final int viewWidth = getViewWidth();
6871 final int viewHeight = getViewHeightWithTitle();
6872 float scale = Math.min((float) viewWidth / rectWidth, (float) viewHeight / rectHeight);
6873 scale = mZoomManager.computeScaleWithLimits(scale);
6874 return !mZoomManager.willScaleTriggerZoom(scale)
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00006875 && contentToViewX(rect.left) >= getScrollX()
6876 && contentToViewX(rect.right) <= getScrollX() + viewWidth
6877 && contentToViewY(rect.top) >= getScrollY()
6878 && contentToViewY(rect.bottom) <= getScrollY() + viewHeight;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006879 }
6880
6881 /*
6882 * Maximize and center the rectangle, specified in the document coordinate
6883 * space, inside the WebView. If the zoom doesn't need to be changed, do an
6884 * animated scroll to center it. If the zoom needs to be changed, find the
6885 * zoom center and do a smooth zoom transition. The rect is in document
6886 * coordinates
6887 */
6888 void centerFitRect(Rect rect) {
6889 final int rectWidth = rect.width();
6890 final int rectHeight = rect.height();
6891 final int viewWidth = getViewWidth();
6892 final int viewHeight = getViewHeightWithTitle();
6893 float scale = Math.min((float) viewWidth / rectWidth, (float) viewHeight
6894 / rectHeight);
6895 scale = mZoomManager.computeScaleWithLimits(scale);
6896 if (!mZoomManager.willScaleTriggerZoom(scale)) {
6897 pinScrollTo(contentToViewX(rect.left + rectWidth / 2) - viewWidth / 2,
6898 contentToViewY(rect.top + rectHeight / 2) - viewHeight / 2,
6899 true, 0);
6900 } else {
6901 float actualScale = mZoomManager.getScale();
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00006902 float oldScreenX = rect.left * actualScale - getScrollX();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006903 float rectViewX = rect.left * scale;
6904 float rectViewWidth = rectWidth * scale;
6905 float newMaxWidth = mContentWidth * scale;
6906 float newScreenX = (viewWidth - rectViewWidth) / 2;
6907 // pin the newX to the WebView
6908 if (newScreenX > rectViewX) {
6909 newScreenX = rectViewX;
6910 } else if (newScreenX > (newMaxWidth - rectViewX - rectViewWidth)) {
6911 newScreenX = viewWidth - (newMaxWidth - rectViewX);
6912 }
6913 float zoomCenterX = (oldScreenX * scale - newScreenX * actualScale)
6914 / (scale - actualScale);
6915 float oldScreenY = rect.top * actualScale + getTitleHeight()
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00006916 - getScrollY();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006917 float rectViewY = rect.top * scale + getTitleHeight();
6918 float rectViewHeight = rectHeight * scale;
6919 float newMaxHeight = mContentHeight * scale + getTitleHeight();
6920 float newScreenY = (viewHeight - rectViewHeight) / 2;
6921 // pin the newY to the WebView
6922 if (newScreenY > rectViewY) {
6923 newScreenY = rectViewY;
6924 } else if (newScreenY > (newMaxHeight - rectViewY - rectViewHeight)) {
6925 newScreenY = viewHeight - (newMaxHeight - rectViewY);
6926 }
6927 float zoomCenterY = (oldScreenY * scale - newScreenY * actualScale)
6928 / (scale - actualScale);
6929 mZoomManager.setZoomCenter(zoomCenterX, zoomCenterY);
6930 mZoomManager.startZoomAnimation(scale, false);
6931 }
6932 }
6933
6934 // Called by JNI to handle a touch on a node representing an email address,
6935 // address, or phone number
6936 private void overrideLoading(String url) {
6937 mCallbackProxy.uiOverrideUrlLoading(url);
6938 }
6939
6940 @Override
6941 public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
6942 // FIXME: If a subwindow is showing find, and the user touches the
6943 // background window, it can steal focus.
6944 if (mFindIsUp) return false;
6945 boolean result = false;
John Reckb2676f72012-03-02 14:13:06 -08006946 result = mWebViewPrivate.super_requestFocus(direction, previouslyFocusedRect);
John Recka4eddb52012-04-13 12:42:32 -07006947 if (mWebViewCore.getSettings().getNeedInitialFocus()
6948 && !mWebView.isInTouchMode()) {
John Reckb2676f72012-03-02 14:13:06 -08006949 // For cases such as GMail, where we gain focus from a direction,
6950 // we want to move to the first available link.
6951 // FIXME: If there are no visible links, we may not want to
6952 int fakeKeyDirection = 0;
6953 switch(direction) {
6954 case View.FOCUS_UP:
6955 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_UP;
6956 break;
6957 case View.FOCUS_DOWN:
6958 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_DOWN;
6959 break;
6960 case View.FOCUS_LEFT:
6961 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_LEFT;
6962 break;
6963 case View.FOCUS_RIGHT:
6964 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_RIGHT;
6965 break;
6966 default:
6967 return result;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006968 }
John Recka4eddb52012-04-13 12:42:32 -07006969 mWebViewCore.sendMessage(EventHub.SET_INITIAL_FOCUS, fakeKeyDirection);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006970 }
6971 return result;
6972 }
6973
6974 @Override
Jonathan Dixon3c909522012-02-28 18:45:06 +00006975 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08006976 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
6977 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
6978 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
6979 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
6980
6981 int measuredHeight = heightSize;
6982 int measuredWidth = widthSize;
6983
6984 // Grab the content size from WebViewCore.
6985 int contentHeight = contentToViewDimension(mContentHeight);
6986 int contentWidth = contentToViewDimension(mContentWidth);
6987
6988// Log.d(LOGTAG, "------- measure " + heightMode);
6989
6990 if (heightMode != MeasureSpec.EXACTLY) {
6991 mHeightCanMeasure = true;
6992 measuredHeight = contentHeight;
6993 if (heightMode == MeasureSpec.AT_MOST) {
6994 // If we are larger than the AT_MOST height, then our height can
6995 // no longer be measured and we should scroll internally.
6996 if (measuredHeight > heightSize) {
6997 measuredHeight = heightSize;
6998 mHeightCanMeasure = false;
Jonathan Dixon3c909522012-02-28 18:45:06 +00006999 measuredHeight |= View.MEASURED_STATE_TOO_SMALL;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007000 }
7001 }
7002 } else {
7003 mHeightCanMeasure = false;
7004 }
7005 if (mNativeClass != 0) {
7006 nativeSetHeightCanMeasure(mHeightCanMeasure);
7007 }
7008 // For the width, always use the given size unless unspecified.
7009 if (widthMode == MeasureSpec.UNSPECIFIED) {
7010 mWidthCanMeasure = true;
7011 measuredWidth = contentWidth;
7012 } else {
7013 if (measuredWidth < contentWidth) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00007014 measuredWidth |= View.MEASURED_STATE_TOO_SMALL;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007015 }
7016 mWidthCanMeasure = false;
7017 }
7018
7019 synchronized (this) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00007020 mWebViewPrivate.setMeasuredDimension(measuredWidth, measuredHeight);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007021 }
7022 }
7023
7024 @Override
7025 public boolean requestChildRectangleOnScreen(View child,
7026 Rect rect,
7027 boolean immediate) {
7028 if (mNativeClass == 0) {
7029 return false;
7030 }
7031 // don't scroll while in zoom animation. When it is done, we will adjust
John Reckb2676f72012-03-02 14:13:06 -08007032 // the necessary components
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007033 if (mZoomManager.isFixedLengthAnimationInProgress()) {
7034 return false;
7035 }
7036
7037 rect.offset(child.getLeft() - child.getScrollX(),
7038 child.getTop() - child.getScrollY());
7039
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00007040 Rect content = new Rect(viewToContentX(getScrollX()),
7041 viewToContentY(getScrollY()),
7042 viewToContentX(getScrollX() + getWidth()
Jonathan Dixon3c909522012-02-28 18:45:06 +00007043 - mWebView.getVerticalScrollbarWidth()),
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00007044 viewToContentY(getScrollY() + getViewHeightWithTitle()));
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007045 int screenTop = contentToViewY(content.top);
7046 int screenBottom = contentToViewY(content.bottom);
7047 int height = screenBottom - screenTop;
7048 int scrollYDelta = 0;
7049
7050 if (rect.bottom > screenBottom) {
7051 int oneThirdOfScreenHeight = height / 3;
7052 if (rect.height() > 2 * oneThirdOfScreenHeight) {
7053 // If the rectangle is too tall to fit in the bottom two thirds
7054 // of the screen, place it at the top.
7055 scrollYDelta = rect.top - screenTop;
7056 } else {
7057 // If the rectangle will still fit on screen, we want its
7058 // top to be in the top third of the screen.
7059 scrollYDelta = rect.top - (screenTop + oneThirdOfScreenHeight);
7060 }
7061 } else if (rect.top < screenTop) {
7062 scrollYDelta = rect.top - screenTop;
7063 }
7064
7065 int screenLeft = contentToViewX(content.left);
7066 int screenRight = contentToViewX(content.right);
7067 int width = screenRight - screenLeft;
7068 int scrollXDelta = 0;
7069
7070 if (rect.right > screenRight && rect.left > screenLeft) {
7071 if (rect.width() > width) {
7072 scrollXDelta += (rect.left - screenLeft);
7073 } else {
7074 scrollXDelta += (rect.right - screenRight);
7075 }
7076 } else if (rect.left < screenLeft) {
7077 scrollXDelta -= (screenLeft - rect.left);
7078 }
7079
7080 if ((scrollYDelta | scrollXDelta) != 0) {
7081 return pinScrollBy(scrollXDelta, scrollYDelta, !immediate, 0);
7082 }
7083
7084 return false;
7085 }
7086
7087 /* package */ void replaceTextfieldText(int oldStart, int oldEnd,
7088 String replace, int newStart, int newEnd) {
7089 WebViewCore.ReplaceTextData arg = new WebViewCore.ReplaceTextData();
7090 arg.mReplace = replace;
7091 arg.mNewStart = newStart;
7092 arg.mNewEnd = newEnd;
7093 mTextGeneration++;
7094 arg.mTextGeneration = mTextGeneration;
George Mountdbef1c52012-03-28 14:17:13 -07007095 sendBatchableInputMessage(EventHub.REPLACE_TEXT, oldStart, oldEnd, arg);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007096 }
7097
7098 /* package */ void passToJavaScript(String currentText, KeyEvent event) {
7099 // check if mWebViewCore has been destroyed
7100 if (mWebViewCore == null) {
7101 return;
7102 }
7103 WebViewCore.JSKeyData arg = new WebViewCore.JSKeyData();
7104 arg.mEvent = event;
7105 arg.mCurrentText = currentText;
7106 // Increase our text generation number, and pass it to webcore thread
7107 mTextGeneration++;
7108 mWebViewCore.sendMessage(EventHub.PASS_TO_JS, mTextGeneration, 0, arg);
7109 // WebKit's document state is not saved until about to leave the page.
7110 // To make sure the host application, like Browser, has the up to date
7111 // document state when it goes to background, we force to save the
7112 // document state.
7113 mWebViewCore.removeMessages(EventHub.SAVE_DOCUMENT_STATE);
John Reckb2676f72012-03-02 14:13:06 -08007114 mWebViewCore.sendMessageDelayed(EventHub.SAVE_DOCUMENT_STATE, null, 1000);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007115 }
7116
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007117 public synchronized WebViewCore getWebViewCore() {
7118 return mWebViewCore;
7119 }
7120
George Mountfcff68f2012-03-20 10:21:57 -07007121 private boolean canTextScroll(int directionX, int directionY) {
7122 int scrollX = getTextScrollX();
7123 int scrollY = getTextScrollY();
7124 int maxScrollX = getMaxTextScrollX();
7125 int maxScrollY = getMaxTextScrollY();
7126 boolean canScrollX = (directionX > 0)
7127 ? (scrollX < maxScrollX)
7128 : (scrollX > 0);
7129 boolean canScrollY = (directionY > 0)
7130 ? (scrollY < maxScrollY)
7131 : (scrollY > 0);
7132 return canScrollX || canScrollY;
7133 }
7134
7135 private int getTextScrollX() {
7136 return -mEditTextContent.left;
7137 }
7138
7139 private int getTextScrollY() {
7140 return -mEditTextContent.top;
7141 }
7142
7143 private int getMaxTextScrollX() {
George Mount7102eb22012-04-10 13:41:51 -07007144 return Math.max(0, mEditTextContent.width() - mEditTextContentBounds.width());
George Mountfcff68f2012-03-20 10:21:57 -07007145 }
7146
7147 private int getMaxTextScrollY() {
George Mount7102eb22012-04-10 13:41:51 -07007148 return Math.max(0, mEditTextContent.height() - mEditTextContentBounds.height());
George Mountfcff68f2012-03-20 10:21:57 -07007149 }
7150
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007151 //-------------------------------------------------------------------------
7152 // Methods can be called from a separate thread, like WebViewCore
7153 // If it needs to call the View system, it has to send message.
7154 //-------------------------------------------------------------------------
7155
7156 /**
7157 * General handler to receive message coming from webkit thread
7158 */
Jeff Brown9d3bdbd2012-03-21 11:50:06 -07007159 class PrivateHandler extends Handler implements WebViewInputDispatcher.UiCallbacks {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007160 @Override
7161 public void handleMessage(Message msg) {
7162 // exclude INVAL_RECT_MSG_ID since it is frequently output
7163 if (DebugFlags.WEB_VIEW && msg.what != INVAL_RECT_MSG_ID) {
7164 if (msg.what >= FIRST_PRIVATE_MSG_ID
7165 && msg.what <= LAST_PRIVATE_MSG_ID) {
7166 Log.v(LOGTAG, HandlerPrivateDebugString[msg.what
7167 - FIRST_PRIVATE_MSG_ID]);
7168 } else if (msg.what >= FIRST_PACKAGE_MSG_ID
7169 && msg.what <= LAST_PACKAGE_MSG_ID) {
7170 Log.v(LOGTAG, HandlerPackageDebugString[msg.what
7171 - FIRST_PACKAGE_MSG_ID]);
7172 } else {
7173 Log.v(LOGTAG, Integer.toString(msg.what));
7174 }
7175 }
7176 if (mWebViewCore == null) {
7177 // after WebView's destroy() is called, skip handling messages.
7178 return;
7179 }
7180 if (mBlockWebkitViewMessages
7181 && msg.what != WEBCORE_INITIALIZED_MSG_ID) {
7182 // Blocking messages from webkit
7183 return;
7184 }
7185 switch (msg.what) {
7186 case REMEMBER_PASSWORD: {
7187 mDatabase.setUsernamePassword(
7188 msg.getData().getString("host"),
7189 msg.getData().getString("username"),
7190 msg.getData().getString("password"));
7191 ((Message) msg.obj).sendToTarget();
7192 break;
7193 }
7194 case NEVER_REMEMBER_PASSWORD: {
7195 mDatabase.setUsernamePassword(
7196 msg.getData().getString("host"), null, null);
7197 ((Message) msg.obj).sendToTarget();
7198 break;
7199 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007200 case SCROLL_SELECT_TEXT: {
7201 if (mAutoScrollX == 0 && mAutoScrollY == 0) {
7202 mSentAutoScrollMessage = false;
7203 break;
7204 }
7205 if (mCurrentScrollingLayerId == 0) {
7206 pinScrollBy(mAutoScrollX, mAutoScrollY, true, 0);
7207 } else {
7208 scrollLayerTo(mScrollingLayerRect.left + mAutoScrollX,
7209 mScrollingLayerRect.top + mAutoScrollY);
7210 }
7211 sendEmptyMessageDelayed(
7212 SCROLL_SELECT_TEXT, SELECT_SCROLL_INTERVAL);
7213 break;
7214 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007215 case SCROLL_TO_MSG_ID: {
7216 // arg1 = animate, arg2 = onlyIfImeIsShowing
7217 // obj = Point(x, y)
7218 if (msg.arg2 == 1) {
7219 // This scroll is intended to bring the textfield into
7220 // view, but is only necessary if the IME is showing
7221 InputMethodManager imm = InputMethodManager.peekInstance();
7222 if (imm == null || !imm.isAcceptingText()
John Reckb2676f72012-03-02 14:13:06 -08007223 || !imm.isActive(mWebView)) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007224 break;
7225 }
7226 }
7227 final Point p = (Point) msg.obj;
John Recka60a1892012-04-19 10:48:20 -07007228 contentScrollTo(p.x, p.y, msg.arg1 == 1);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007229 break;
7230 }
7231 case UPDATE_ZOOM_RANGE: {
7232 WebViewCore.ViewState viewState = (WebViewCore.ViewState) msg.obj;
7233 // mScrollX contains the new minPrefWidth
7234 mZoomManager.updateZoomRange(viewState, getViewWidth(), viewState.mScrollX);
7235 break;
7236 }
7237 case UPDATE_ZOOM_DENSITY: {
7238 final float density = (Float) msg.obj;
7239 mZoomManager.updateDefaultZoomDensity(density);
7240 break;
7241 }
7242 case REPLACE_BASE_CONTENT: {
7243 nativeReplaceBaseContent(msg.arg1);
7244 break;
7245 }
7246 case NEW_PICTURE_MSG_ID: {
7247 // called for new content
7248 final WebViewCore.DrawData draw = (WebViewCore.DrawData) msg.obj;
7249 setNewPicture(draw, true);
7250 break;
7251 }
7252 case WEBCORE_INITIALIZED_MSG_ID:
7253 // nativeCreate sets mNativeClass to a non-zero value
7254 String drawableDir = BrowserFrame.getRawResFilename(
7255 BrowserFrame.DRAWABLEDIR, mContext);
7256 WindowManager windowManager =
7257 (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
7258 Display display = windowManager.getDefaultDisplay();
7259 nativeCreate(msg.arg1, drawableDir,
7260 ActivityManager.isHighEndGfx(display));
7261 if (mDelaySetPicture != null) {
7262 setNewPicture(mDelaySetPicture, true);
7263 mDelaySetPicture = null;
7264 }
7265 if (mIsPaused) {
7266 nativeSetPauseDrawing(mNativeClass, true);
7267 }
Jeff Brown9d3bdbd2012-03-21 11:50:06 -07007268 mInputDispatcher = new WebViewInputDispatcher(this,
7269 mWebViewCore.getInputDispatcherCallbacks());
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007270 break;
7271 case UPDATE_TEXTFIELD_TEXT_MSG_ID:
7272 // Make sure that the textfield is currently focused
7273 // and representing the same node as the pointer.
7274 if (msg.arg2 == mTextGeneration) {
7275 String text = (String) msg.obj;
7276 if (null == text) {
7277 text = "";
7278 }
John Reckb2676f72012-03-02 14:13:06 -08007279 if (mInputConnection != null &&
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007280 mFieldPointer == msg.arg1) {
7281 mInputConnection.setTextAndKeepSelection(text);
7282 }
7283 }
7284 break;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007285 case UPDATE_TEXT_SELECTION_MSG_ID:
7286 updateTextSelectionFromMessage(msg.arg1, msg.arg2,
7287 (WebViewCore.TextSelectionData) msg.obj);
7288 break;
John Reck4fa40372012-03-06 14:31:45 -08007289 case TAKE_FOCUS:
7290 int direction = msg.arg1;
7291 View focusSearch = mWebView.focusSearch(direction);
7292 if (focusSearch != null && focusSearch != mWebView) {
7293 focusSearch.requestFocus();
7294 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007295 break;
7296 case CLEAR_TEXT_ENTRY:
John Reckb2676f72012-03-02 14:13:06 -08007297 hideSoftKeyboard();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007298 break;
7299 case INVAL_RECT_MSG_ID: {
7300 Rect r = (Rect)msg.obj;
7301 if (r == null) {
7302 invalidate();
7303 } else {
7304 // we need to scale r from content into view coords,
7305 // which viewInvalidate() does for us
7306 viewInvalidate(r.left, r.top, r.right, r.bottom);
7307 }
7308 break;
7309 }
George Mountbcd5dd72012-03-01 08:39:03 -08007310 case REQUEST_FORM_DATA:
7311 if (mFieldPointer == msg.arg1) {
7312 ArrayAdapter<String> adapter = (ArrayAdapter<String>)msg.obj;
7313 mAutoCompletePopup.setAdapter(adapter);
7314 }
7315 break;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007316
7317 case LONG_PRESS_CENTER:
7318 // as this is shared by keydown and trackballdown, reset all
7319 // the states
7320 mGotCenterDown = false;
7321 mTrackballDown = false;
7322 performLongClick();
7323 break;
7324
7325 case WEBCORE_NEED_TOUCH_EVENTS:
Jeff Brown9d3bdbd2012-03-21 11:50:06 -07007326 mInputDispatcher.setWebKitWantsTouchEvents(msg.arg1 != 0);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007327 break;
7328
7329 case REQUEST_KEYBOARD:
7330 if (msg.arg1 == 0) {
7331 hideSoftKeyboard();
7332 } else {
7333 displaySoftKeyboard(false);
7334 }
7335 break;
7336
7337 case DRAG_HELD_MOTIONLESS:
7338 mHeldMotionless = MOTIONLESS_TRUE;
7339 invalidate();
7340 // fall through to keep scrollbars awake
7341
7342 case AWAKEN_SCROLL_BARS:
7343 if (mTouchMode == TOUCH_DRAG_MODE
7344 && mHeldMotionless == MOTIONLESS_TRUE) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00007345 mWebViewPrivate.awakenScrollBars(ViewConfiguration
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007346 .getScrollDefaultDelay(), false);
7347 mPrivateHandler.sendMessageDelayed(mPrivateHandler
7348 .obtainMessage(AWAKEN_SCROLL_BARS),
7349 ViewConfiguration.getScrollDefaultDelay());
7350 }
7351 break;
7352
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007353 case SCREEN_ON:
Jonathan Dixon3c909522012-02-28 18:45:06 +00007354 mWebView.setKeepScreenOn(msg.arg1 == 1);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007355 break;
7356
7357 case ENTER_FULLSCREEN_VIDEO:
7358 int layerId = msg.arg1;
7359
7360 String url = (String) msg.obj;
7361 if (mHTML5VideoViewProxy != null) {
7362 mHTML5VideoViewProxy.enterFullScreenVideo(layerId, url);
7363 }
7364 break;
7365
7366 case EXIT_FULLSCREEN_VIDEO:
7367 if (mHTML5VideoViewProxy != null) {
7368 mHTML5VideoViewProxy.exitFullScreenVideo();
7369 }
7370 break;
7371
7372 case SHOW_FULLSCREEN: {
7373 View view = (View) msg.obj;
7374 int orientation = msg.arg1;
7375 int npp = msg.arg2;
7376
7377 if (inFullScreenMode()) {
7378 Log.w(LOGTAG, "Should not have another full screen.");
7379 dismissFullScreenMode();
7380 }
Jonathan Dixon3c909522012-02-28 18:45:06 +00007381 mFullScreenHolder = new PluginFullScreenHolder(WebViewClassic.this, orientation, npp);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007382 mFullScreenHolder.setContentView(view);
7383 mFullScreenHolder.show();
7384 invalidate();
7385
7386 break;
7387 }
7388 case HIDE_FULLSCREEN:
7389 dismissFullScreenMode();
7390 break;
7391
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007392 case SHOW_RECT_MSG_ID: {
7393 WebViewCore.ShowRectData data = (WebViewCore.ShowRectData) msg.obj;
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00007394 int x = getScrollX();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007395 int left = contentToViewX(data.mLeft);
7396 int width = contentToViewDimension(data.mWidth);
7397 int maxWidth = contentToViewDimension(data.mContentWidth);
7398 int viewWidth = getViewWidth();
7399 if (width < viewWidth) {
7400 // center align
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00007401 x += left + width / 2 - getScrollX() - viewWidth / 2;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007402 } else {
7403 x += (int) (left + data.mXPercentInDoc * width
Jonathan Dixon0dc0da62012-02-23 18:08:01 +00007404 - getScrollX() - data.mXPercentInView * viewWidth);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007405 }
7406 if (DebugFlags.WEB_VIEW) {
7407 Log.v(LOGTAG, "showRectMsg=(left=" + left + ",width=" +
7408 width + ",maxWidth=" + maxWidth +
7409 ",viewWidth=" + viewWidth + ",x="
7410 + x + ",xPercentInDoc=" + data.mXPercentInDoc +
7411 ",xPercentInView=" + data.mXPercentInView+ ")");
7412 }
7413 // use the passing content width to cap x as the current
7414 // mContentWidth may not be updated yet
7415 x = Math.max(0,
7416 (Math.min(maxWidth, x + viewWidth)) - viewWidth);
7417 int top = contentToViewY(data.mTop);
7418 int height = contentToViewDimension(data.mHeight);
7419 int maxHeight = contentToViewDimension(data.mContentHeight);
7420 int viewHeight = getViewHeight();
7421 int y = (int) (top + data.mYPercentInDoc * height -
7422 data.mYPercentInView * viewHeight);
7423 if (DebugFlags.WEB_VIEW) {
7424 Log.v(LOGTAG, "showRectMsg=(top=" + top + ",height=" +
7425 height + ",maxHeight=" + maxHeight +
7426 ",viewHeight=" + viewHeight + ",y="
7427 + y + ",yPercentInDoc=" + data.mYPercentInDoc +
7428 ",yPercentInView=" + data.mYPercentInView+ ")");
7429 }
7430 // use the passing content height to cap y as the current
7431 // mContentHeight may not be updated yet
7432 y = Math.max(0,
7433 (Math.min(maxHeight, y + viewHeight) - viewHeight));
7434 // We need to take into account the visible title height
7435 // when scrolling since y is an absolute view position.
7436 y = Math.max(0, y - getVisibleTitleHeightImpl());
Jonathan Dixon3c909522012-02-28 18:45:06 +00007437 mWebView.scrollTo(x, y);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007438 }
7439 break;
7440
7441 case CENTER_FIT_RECT:
7442 centerFitRect((Rect)msg.obj);
7443 break;
7444
7445 case SET_SCROLLBAR_MODES:
7446 mHorizontalScrollBarMode = msg.arg1;
7447 mVerticalScrollBarMode = msg.arg2;
7448 break;
7449
7450 case SELECTION_STRING_CHANGED:
7451 if (mAccessibilityInjector != null) {
7452 String selectionString = (String) msg.obj;
7453 mAccessibilityInjector.onSelectionStringChange(selectionString);
7454 }
7455 break;
7456
George Mountbcd5dd72012-03-01 08:39:03 -08007457 case FOCUS_NODE_CHANGED:
George Mountf70276a2012-03-12 14:22:10 -07007458 mIsEditingText = (msg.arg1 == mFieldPointer);
7459 if (mAutoCompletePopup != null && !mIsEditingText) {
7460 mAutoCompletePopup.clearAdapter();
George Mountbcd5dd72012-03-01 08:39:03 -08007461 }
7462 // fall through to HIT_TEST_RESULT
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007463 case HIT_TEST_RESULT:
7464 WebKitHitTest hit = (WebKitHitTest) msg.obj;
7465 mFocusedNode = hit;
7466 setTouchHighlightRects(hit);
7467 setHitTestResult(hit);
7468 break;
7469
7470 case SAVE_WEBARCHIVE_FINISHED:
7471 SaveWebArchiveMessage saveMessage = (SaveWebArchiveMessage)msg.obj;
7472 if (saveMessage.mCallback != null) {
7473 saveMessage.mCallback.onReceiveValue(saveMessage.mResultFile);
7474 }
7475 break;
7476
7477 case SET_AUTOFILLABLE:
7478 mAutoFillData = (WebViewCore.AutoFillData) msg.obj;
George Mountbcd5dd72012-03-01 08:39:03 -08007479 if (mInputConnection != null) {
7480 mInputConnection.setAutoFillable(mAutoFillData.getQueryId());
7481 mAutoCompletePopup.setAutoFillQueryId(mAutoFillData.getQueryId());
7482 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007483 break;
7484
7485 case AUTOFILL_COMPLETE:
George Mountbcd5dd72012-03-01 08:39:03 -08007486 if (mAutoCompletePopup != null) {
7487 ArrayList<String> pastEntries = new ArrayList<String>();
7488 mAutoCompletePopup.setAdapter(new ArrayAdapter<String>(
7489 mContext,
7490 com.android.internal.R.layout.web_text_view_dropdown,
7491 pastEntries));
7492 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007493 break;
7494
7495 case COPY_TO_CLIPBOARD:
7496 copyToClipboard((String) msg.obj);
7497 break;
7498
7499 case INIT_EDIT_FIELD:
7500 if (mInputConnection != null) {
7501 TextFieldInitData initData = (TextFieldInitData) msg.obj;
7502 mTextGeneration = 0;
7503 mFieldPointer = initData.mFieldPointer;
7504 mInputConnection.initEditorInfo(initData);
7505 mInputConnection.setTextAndKeepSelection(initData.mText);
George Mount7102eb22012-04-10 13:41:51 -07007506 mEditTextContentBounds.set(initData.mContentBounds);
George Mountf70276a2012-03-12 14:22:10 -07007507 mEditTextLayerId = initData.mNodeLayerId;
7508 nativeMapLayerRect(mNativeClass, mEditTextLayerId,
George Mount7102eb22012-04-10 13:41:51 -07007509 mEditTextContentBounds);
George Mountf70276a2012-03-12 14:22:10 -07007510 mEditTextContent.set(initData.mContentRect);
7511 relocateAutoCompletePopup();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007512 }
7513 break;
7514
7515 case REPLACE_TEXT:{
7516 String text = (String)msg.obj;
7517 int start = msg.arg1;
7518 int end = msg.arg2;
7519 int cursorPosition = start + text.length();
7520 replaceTextfieldText(start, end, text,
7521 cursorPosition, cursorPosition);
7522 break;
7523 }
7524
7525 case UPDATE_MATCH_COUNT: {
Victoria Lease0b8413b2012-03-26 13:04:10 -07007526 WebViewCore.FindAllRequest request = (WebViewCore.FindAllRequest)msg.obj;
7527 if (request == null) {
7528 if (mFindCallback != null) {
7529 mFindCallback.updateMatchCount(0, 0, true);
7530 }
7531 } else if (request == mFindRequest) {
7532 int matchCount, matchIndex;
7533 synchronized (mFindRequest) {
7534 matchCount = request.mMatchCount;
7535 matchIndex = request.mMatchIndex;
7536 }
7537 if (mFindCallback != null) {
7538 mFindCallback.updateMatchCount(matchIndex, matchCount, false);
7539 }
7540 if (mFindListener != null) {
7541 mFindListener.onFindResultReceived(matchIndex, matchCount, true);
7542 }
7543 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007544 break;
7545 }
Victoria Lease0b8413b2012-03-26 13:04:10 -07007546
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007547 case CLEAR_CARET_HANDLE:
7548 selectionDone();
7549 break;
7550
7551 case KEY_PRESS:
George Mountdbef1c52012-03-28 14:17:13 -07007552 sendBatchableInputMessage(EventHub.KEY_PRESS, msg.arg1, 0, null);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007553 break;
7554
George Mountbcd5dd72012-03-01 08:39:03 -08007555 case RELOCATE_AUTO_COMPLETE_POPUP:
7556 relocateAutoCompletePopup();
7557 break;
7558
7559 case AUTOFILL_FORM:
7560 mWebViewCore.sendMessage(EventHub.AUTOFILL_FORM,
7561 msg.arg1, /* unused */0);
7562 break;
7563
George Mountc6c549d2012-03-15 16:39:16 -07007564 case EDIT_TEXT_SIZE_CHANGED:
7565 if (msg.arg1 == mFieldPointer) {
7566 mEditTextContent.set((Rect)msg.obj);
7567 }
7568 break;
7569
George Mountf796fdf2012-03-19 14:51:55 -07007570 case SHOW_CARET_HANDLE:
7571 if (!mSelectingText && mIsEditingText && mIsCaretSelection) {
7572 setupWebkitSelect();
7573 resetCaretTimer();
7574 showPasteWindow();
7575 }
7576 break;
7577
George Mount7102eb22012-04-10 13:41:51 -07007578 case UPDATE_CONTENT_BOUNDS:
7579 mEditTextContentBounds.set((Rect) msg.obj);
7580 break;
7581
George Mount557748d2012-04-04 13:56:49 -07007582 case SCROLL_EDIT_TEXT:
7583 scrollEditWithCursor();
7584 break;
7585
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007586 default:
7587 super.handleMessage(msg);
7588 break;
7589 }
7590 }
Jeff Brown9d3bdbd2012-03-21 11:50:06 -07007591
7592 @Override
7593 public Looper getUiLooper() {
7594 return getLooper();
7595 }
7596
7597 @Override
7598 public void dispatchUiEvent(MotionEvent event, int eventType, int flags) {
7599 onHandleUiEvent(event, eventType, flags);
7600 }
7601
7602 @Override
7603 public Context getContext() {
7604 return WebViewClassic.this.getContext();
7605 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007606 }
7607
7608 private void setHitTestTypeFromUrl(String url) {
7609 String substr = null;
7610 if (url.startsWith(SCHEME_GEO)) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00007611 mInitialHitTestResult.setType(HitTestResult.GEO_TYPE);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007612 substr = url.substring(SCHEME_GEO.length());
7613 } else if (url.startsWith(SCHEME_TEL)) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00007614 mInitialHitTestResult.setType(HitTestResult.PHONE_TYPE);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007615 substr = url.substring(SCHEME_TEL.length());
7616 } else if (url.startsWith(SCHEME_MAILTO)) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00007617 mInitialHitTestResult.setType(HitTestResult.EMAIL_TYPE);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007618 substr = url.substring(SCHEME_MAILTO.length());
7619 } else {
Jonathan Dixon3c909522012-02-28 18:45:06 +00007620 mInitialHitTestResult.setType(HitTestResult.SRC_ANCHOR_TYPE);
7621 mInitialHitTestResult.setExtra(url);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007622 return;
7623 }
7624 try {
Jonathan Dixon3c909522012-02-28 18:45:06 +00007625 mInitialHitTestResult.setExtra(URLDecoder.decode(substr, "UTF-8"));
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007626 } catch (Throwable e) {
7627 Log.w(LOGTAG, "Failed to decode URL! " + substr, e);
Jonathan Dixon3c909522012-02-28 18:45:06 +00007628 mInitialHitTestResult.setType(HitTestResult.UNKNOWN_TYPE);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007629 }
7630 }
7631
7632 private void setHitTestResult(WebKitHitTest hit) {
7633 if (hit == null) {
7634 mInitialHitTestResult = null;
7635 return;
7636 }
7637 mInitialHitTestResult = new HitTestResult();
7638 if (hit.mLinkUrl != null) {
7639 setHitTestTypeFromUrl(hit.mLinkUrl);
7640 if (hit.mImageUrl != null
Jonathan Dixon3c909522012-02-28 18:45:06 +00007641 && mInitialHitTestResult.getType() == HitTestResult.SRC_ANCHOR_TYPE) {
7642 mInitialHitTestResult.setType(HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
7643 mInitialHitTestResult.setExtra(hit.mImageUrl);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007644 }
7645 } else if (hit.mImageUrl != null) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00007646 mInitialHitTestResult.setType(HitTestResult.IMAGE_TYPE);
7647 mInitialHitTestResult.setExtra(hit.mImageUrl);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007648 } else if (hit.mEditable) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00007649 mInitialHitTestResult.setType(HitTestResult.EDIT_TEXT_TYPE);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007650 } else if (hit.mIntentUrl != null) {
7651 setHitTestTypeFromUrl(hit.mIntentUrl);
7652 }
7653 }
7654
7655 private boolean shouldDrawHighlightRect() {
7656 if (mFocusedNode == null || mInitialHitTestResult == null) {
7657 return false;
7658 }
7659 if (mTouchHighlightRegion.isEmpty()) {
7660 return false;
7661 }
Jonathan Dixon3c909522012-02-28 18:45:06 +00007662 if (mFocusedNode.mHasFocus && !mWebView.isInTouchMode()) {
John Reck413fab32012-03-07 11:02:15 -08007663 return mDrawCursorRing && !mFocusedNode.mEditable;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007664 }
John Reckef109b02012-03-06 18:02:11 -08007665 if (mFocusedNode.mHasFocus && mFocusedNode.mEditable) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007666 return false;
7667 }
John Reck2d8c13b2012-04-12 14:15:54 -07007668 long delay = SystemClock.uptimeMillis() - mTouchHighlightRequested;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007669 if (delay < ViewConfiguration.getTapTimeout()) {
7670 Rect r = mTouchHighlightRegion.getBounds();
Jonathan Dixon3c909522012-02-28 18:45:06 +00007671 mWebView.postInvalidateDelayed(delay, r.left, r.top, r.right, r.bottom);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007672 return false;
7673 }
John Reck2d8c13b2012-04-12 14:15:54 -07007674 if (mInputDispatcher == null) {
7675 return false;
7676 }
7677 return mInputDispatcher.shouldShowTapHighlight();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007678 }
7679
7680
7681 private FocusTransitionDrawable mFocusTransition = null;
7682 static class FocusTransitionDrawable extends Drawable {
7683 Region mPreviousRegion;
7684 Region mNewRegion;
7685 float mProgress = 0;
Jonathan Dixon3c909522012-02-28 18:45:06 +00007686 WebViewClassic mWebView;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007687 Paint mPaint;
7688 int mMaxAlpha;
7689 Point mTranslate;
7690
Jonathan Dixon3c909522012-02-28 18:45:06 +00007691 public FocusTransitionDrawable(WebViewClassic view) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007692 mWebView = view;
7693 mPaint = new Paint(mWebView.mTouchHightlightPaint);
7694 mMaxAlpha = mPaint.getAlpha();
7695 }
7696
7697 @Override
7698 public void setColorFilter(ColorFilter cf) {
7699 }
7700
7701 @Override
7702 public void setAlpha(int alpha) {
7703 }
7704
7705 @Override
7706 public int getOpacity() {
7707 return 0;
7708 }
7709
7710 public void setProgress(float p) {
7711 mProgress = p;
7712 if (mWebView.mFocusTransition == this) {
7713 if (mProgress == 1f)
7714 mWebView.mFocusTransition = null;
7715 mWebView.invalidate();
7716 }
7717 }
7718
7719 public float getProgress() {
7720 return mProgress;
7721 }
7722
7723 @Override
7724 public void draw(Canvas canvas) {
7725 if (mTranslate == null) {
7726 Rect bounds = mPreviousRegion.getBounds();
7727 Point from = new Point(bounds.centerX(), bounds.centerY());
7728 mNewRegion.getBounds(bounds);
7729 Point to = new Point(bounds.centerX(), bounds.centerY());
7730 mTranslate = new Point(from.x - to.x, from.y - to.y);
7731 }
7732 int alpha = (int) (mProgress * mMaxAlpha);
7733 RegionIterator iter = new RegionIterator(mPreviousRegion);
7734 Rect r = new Rect();
7735 mPaint.setAlpha(mMaxAlpha - alpha);
7736 float tx = mTranslate.x * mProgress;
7737 float ty = mTranslate.y * mProgress;
7738 int save = canvas.save(Canvas.MATRIX_SAVE_FLAG);
7739 canvas.translate(-tx, -ty);
7740 while (iter.next(r)) {
7741 canvas.drawRect(r, mPaint);
7742 }
7743 canvas.restoreToCount(save);
7744 iter = new RegionIterator(mNewRegion);
7745 r = new Rect();
7746 mPaint.setAlpha(alpha);
7747 save = canvas.save(Canvas.MATRIX_SAVE_FLAG);
7748 tx = mTranslate.x - tx;
7749 ty = mTranslate.y - ty;
7750 canvas.translate(tx, ty);
7751 while (iter.next(r)) {
7752 canvas.drawRect(r, mPaint);
7753 }
7754 canvas.restoreToCount(save);
7755 }
7756 };
7757
7758 private boolean shouldAnimateTo(WebKitHitTest hit) {
7759 // TODO: Don't be annoying or throw out the animation entirely
7760 return false;
7761 }
7762
7763 private void setTouchHighlightRects(WebKitHitTest hit) {
7764 FocusTransitionDrawable transition = null;
7765 if (shouldAnimateTo(hit)) {
7766 transition = new FocusTransitionDrawable(this);
7767 }
7768 Rect[] rects = hit != null ? hit.mTouchRects : null;
7769 if (!mTouchHighlightRegion.isEmpty()) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00007770 mWebView.invalidate(mTouchHighlightRegion.getBounds());
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007771 if (transition != null) {
7772 transition.mPreviousRegion = new Region(mTouchHighlightRegion);
7773 }
7774 mTouchHighlightRegion.setEmpty();
7775 }
7776 if (rects != null) {
7777 mTouchHightlightPaint.setColor(hit.mTapHighlightColor);
7778 for (Rect rect : rects) {
7779 Rect viewRect = contentToViewRect(rect);
7780 // some sites, like stories in nytimes.com, set
7781 // mouse event handler in the top div. It is not
7782 // user friendly to highlight the div if it covers
7783 // more than half of the screen.
7784 if (viewRect.width() < getWidth() >> 1
7785 || viewRect.height() < getHeight() >> 1) {
7786 mTouchHighlightRegion.union(viewRect);
John Reckd855ffd2012-04-12 14:45:49 -07007787 } else if (DebugFlags.WEB_VIEW) {
7788 Log.d(LOGTAG, "Skip the huge selection rect:"
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007789 + viewRect);
7790 }
7791 }
Jonathan Dixon3c909522012-02-28 18:45:06 +00007792 mWebView.invalidate(mTouchHighlightRegion.getBounds());
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007793 if (transition != null && transition.mPreviousRegion != null) {
7794 transition.mNewRegion = new Region(mTouchHighlightRegion);
7795 mFocusTransition = transition;
7796 ObjectAnimator animator = ObjectAnimator.ofFloat(
7797 mFocusTransition, "progress", 1f);
7798 animator.start();
7799 }
7800 }
7801 }
7802
Jonathan Dixon3c909522012-02-28 18:45:06 +00007803 // Interface to allow the profiled WebView to hook the page swap notifications.
7804 public interface PageSwapDelegate {
7805 void onPageSwapOccurred(boolean notifyAnimationStarted);
7806 }
7807
John Reck834f66b2012-03-27 12:52:57 -07007808 long mLastSwapTime;
7809 double mAverageSwapFps;
7810
Jonathan Dixoncd93e152012-03-02 19:19:44 +00007811 /** Called by JNI when pages are swapped (only occurs with hardware
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007812 * acceleration) */
7813 protected void pageSwapCallback(boolean notifyAnimationStarted) {
John Reck834f66b2012-03-27 12:52:57 -07007814 if (DebugFlags.MEASURE_PAGE_SWAP_FPS) {
7815 long now = System.currentTimeMillis();
7816 long diff = now - mLastSwapTime;
7817 mAverageSwapFps = ((1000.0 / diff) + mAverageSwapFps) / 2;
7818 Log.d(LOGTAG, "page swap fps: " + mAverageSwapFps);
7819 mLastSwapTime = now;
7820 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007821 mWebViewCore.resumeWebKitDraw();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007822 if (notifyAnimationStarted) {
7823 mWebViewCore.sendMessage(EventHub.NOTIFY_ANIMATION_STARTED);
7824 }
Jonathan Dixon3c909522012-02-28 18:45:06 +00007825 if (mWebView instanceof PageSwapDelegate) {
7826 // This provides a hook for ProfiledWebView to observe the tile page swaps.
7827 ((PageSwapDelegate) mWebView).onPageSwapOccurred(notifyAnimationStarted);
7828 }
Chris Craik9e5936b2012-04-11 16:45:41 -07007829
7830 if (mPictureListener != null) {
7831 // trigger picture listener for hardware layers. Software layers are
7832 // triggered in setNewPicture
7833 mPictureListener.onNewPicture(getWebView(), capturePicture());
7834 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007835 }
7836
7837 void setNewPicture(final WebViewCore.DrawData draw, boolean updateBaseLayer) {
7838 if (mNativeClass == 0) {
7839 if (mDelaySetPicture != null) {
7840 throw new IllegalStateException("Tried to setNewPicture with"
7841 + " a delay picture already set! (memory leak)");
7842 }
7843 // Not initialized yet, delay set
7844 mDelaySetPicture = draw;
7845 return;
7846 }
7847 WebViewCore.ViewState viewState = draw.mViewState;
7848 boolean isPictureAfterFirstLayout = viewState != null;
7849
7850 if (updateBaseLayer) {
Chris Craikc2c95432012-04-25 15:13:52 -07007851 setBaseLayer(draw.mBaseLayer,
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007852 getSettings().getShowVisualIndicator(),
7853 isPictureAfterFirstLayout);
7854 }
7855 final Point viewSize = draw.mViewSize;
7856 // We update the layout (i.e. request a layout from the
7857 // view system) if the last view size that we sent to
7858 // WebCore matches the view size of the picture we just
7859 // received in the fixed dimension.
7860 final boolean updateLayout = viewSize.x == mLastWidthSent
7861 && viewSize.y == mLastHeightSent;
7862 // Don't send scroll event for picture coming from webkit,
7863 // since the new picture may cause a scroll event to override
7864 // the saved history scroll position.
7865 mSendScrollEvent = false;
7866 recordNewContentSize(draw.mContentSize.x,
7867 draw.mContentSize.y, updateLayout);
7868 if (isPictureAfterFirstLayout) {
7869 // Reset the last sent data here since dealing with new page.
7870 mLastWidthSent = 0;
7871 mZoomManager.onFirstLayout(draw);
7872 int scrollX = viewState.mShouldStartScrolledRight
7873 ? getContentWidth() : viewState.mScrollX;
7874 int scrollY = viewState.mScrollY;
John Recka60a1892012-04-19 10:48:20 -07007875 contentScrollTo(scrollX, scrollY, false);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007876 if (!mDrawHistory) {
John Reckb2676f72012-03-02 14:13:06 -08007877 // As we are on a new page, hide the keyboard
7878 hideSoftKeyboard();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007879 }
7880 }
7881 mSendScrollEvent = true;
7882
Chris Craikc2c95432012-04-25 15:13:52 -07007883 int functor = 0;
7884 if (mWebView.isHardwareAccelerated()
7885 || mWebView.getLayerType() != View.LAYER_TYPE_HARDWARE) {
7886 functor = nativeGetDrawGLFunction(mNativeClass);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007887 }
Chris Craikc2c95432012-04-25 15:13:52 -07007888
7889 if (functor != 0) {
7890 mWebView.getViewRootImpl().attachFunctor(functor);
John Reckca835db2012-03-26 17:18:43 -07007891 } else {
Chris Craikc2c95432012-04-25 15:13:52 -07007892 // invalidate the screen so that the next repaint will show new content
7893 // TODO: partial invalidate
John Reckca835db2012-03-26 17:18:43 -07007894 mWebView.invalidate();
7895 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007896
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007897 // update the zoom information based on the new picture
7898 mZoomManager.onNewPicture(draw);
7899
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007900 if (isPictureAfterFirstLayout) {
7901 mViewManager.postReadyToDrawAll();
7902 }
George Mount557748d2012-04-04 13:56:49 -07007903 scrollEditWithCursor();
George Mount8b0d90e2012-04-10 12:29:59 -07007904
7905 if (mPictureListener != null) {
Chris Craik9e5936b2012-04-11 16:45:41 -07007906 if (!mWebView.isHardwareAccelerated()
7907 || mWebView.getLayerType() == View.LAYER_TYPE_SOFTWARE) {
7908 // trigger picture listener for software layers. Hardware layers are
7909 // triggered in pageSwapCallback
7910 mPictureListener.onNewPicture(getWebView(), capturePicture());
7911 }
George Mount8b0d90e2012-04-10 12:29:59 -07007912 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007913 }
7914
7915 /**
7916 * Used when receiving messages for REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID
John Reckb2676f72012-03-02 14:13:06 -08007917 * and UPDATE_TEXT_SELECTION_MSG_ID.
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007918 */
7919 private void updateTextSelectionFromMessage(int nodePointer,
7920 int textGeneration, WebViewCore.TextSelectionData data) {
7921 if (textGeneration == mTextGeneration) {
John Reckb2676f72012-03-02 14:13:06 -08007922 if (mInputConnection != null && mFieldPointer == nodePointer) {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007923 mInputConnection.setSelection(data.mStart, data.mEnd);
7924 }
7925 }
7926 nativeSetTextSelection(mNativeClass, data.mSelectTextPtr);
7927
7928 if (data.mSelectTextPtr != 0 &&
7929 (data.mStart != data.mEnd ||
7930 (mFieldPointer == nodePointer && mFieldPointer != 0))) {
7931 mIsCaretSelection = (data.mStart == data.mEnd);
George Mountf796fdf2012-03-19 14:51:55 -07007932 if (mIsCaretSelection &&
7933 (mInputConnection == null ||
7934 mInputConnection.getEditable().length() == 0)) {
7935 // There's no text, don't show caret handle.
7936 selectionDone();
7937 } else {
7938 if (!mSelectingText) {
7939 setupWebkitSelect();
7940 } else if (!mSelectionStarted) {
7941 syncSelectionCursors();
George Mountf9c1f992012-03-21 16:06:10 -07007942 } else {
7943 adjustSelectionCursors();
George Mountf796fdf2012-03-19 14:51:55 -07007944 }
7945 if (mIsCaretSelection) {
7946 resetCaretTimer();
7947 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007948 }
7949 } else {
7950 selectionDone();
7951 }
7952 invalidate();
7953 }
7954
George Mountf70276a2012-03-12 14:22:10 -07007955 private void scrollEditText(int scrollX, int scrollY) {
7956 // Scrollable edit text. Scroll it.
George Mountfcff68f2012-03-20 10:21:57 -07007957 float maxScrollX = getMaxTextScrollX();
George Mountf70276a2012-03-12 14:22:10 -07007958 float scrollPercentX = ((float)scrollX)/maxScrollX;
7959 mEditTextContent.offsetTo(-scrollX, -scrollY);
7960 mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SCROLL_TEXT_INPUT, 0,
7961 scrollY, (Float)scrollPercentX);
George Mountf70276a2012-03-12 14:22:10 -07007962 }
7963
George Mountdbef1c52012-03-28 14:17:13 -07007964 private void beginTextBatch() {
7965 mIsBatchingTextChanges = true;
7966 }
7967
7968 private void commitTextBatch() {
7969 if (mWebViewCore != null) {
7970 mWebViewCore.sendMessages(mBatchedTextChanges);
7971 }
7972 mBatchedTextChanges.clear();
7973 mIsBatchingTextChanges = false;
7974 }
7975
7976 private void sendBatchableInputMessage(int what, int arg1, int arg2,
7977 Object obj) {
7978 if (mWebViewCore == null) {
7979 return;
7980 }
7981 Message message = Message.obtain(null, what, arg1, arg2, obj);
7982 if (mIsBatchingTextChanges) {
7983 mBatchedTextChanges.add(message);
7984 } else {
7985 mWebViewCore.sendMessage(message);
7986 }
7987 }
7988
Jonathan Dixonded37ed92012-02-13 17:26:46 -08007989 // Class used to use a dropdown for a <select> element
7990 private class InvokeListBox implements Runnable {
7991 // Whether the listbox allows multiple selection.
7992 private boolean mMultiple;
7993 // Passed in to a list with multiple selection to tell
7994 // which items are selected.
7995 private int[] mSelectedArray;
7996 // Passed in to a list with single selection to tell
7997 // where the initial selection is.
7998 private int mSelection;
7999
8000 private Container[] mContainers;
8001
8002 // Need these to provide stable ids to my ArrayAdapter,
8003 // which normally does not have stable ids. (Bug 1250098)
8004 private class Container extends Object {
8005 /**
8006 * Possible values for mEnabled. Keep in sync with OptionStatus in
8007 * WebViewCore.cpp
8008 */
8009 final static int OPTGROUP = -1;
8010 final static int OPTION_DISABLED = 0;
8011 final static int OPTION_ENABLED = 1;
8012
8013 String mString;
8014 int mEnabled;
8015 int mId;
8016
8017 @Override
8018 public String toString() {
8019 return mString;
8020 }
8021 }
8022
8023 /**
8024 * Subclass ArrayAdapter so we can disable OptionGroupLabels,
8025 * and allow filtering.
8026 */
8027 private class MyArrayListAdapter extends ArrayAdapter<Container> {
8028 public MyArrayListAdapter() {
Jonathan Dixon3c909522012-02-28 18:45:06 +00008029 super(WebViewClassic.this.mContext,
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008030 mMultiple ? com.android.internal.R.layout.select_dialog_multichoice :
8031 com.android.internal.R.layout.webview_select_singlechoice,
8032 mContainers);
8033 }
8034
8035 @Override
8036 public View getView(int position, View convertView,
8037 ViewGroup parent) {
8038 // Always pass in null so that we will get a new CheckedTextView
8039 // Otherwise, an item which was previously used as an <optgroup>
8040 // element (i.e. has no check), could get used as an <option>
8041 // element, which needs a checkbox/radio, but it would not have
8042 // one.
8043 convertView = super.getView(position, null, parent);
8044 Container c = item(position);
8045 if (c != null && Container.OPTION_ENABLED != c.mEnabled) {
8046 // ListView does not draw dividers between disabled and
8047 // enabled elements. Use a LinearLayout to provide dividers
8048 LinearLayout layout = new LinearLayout(mContext);
8049 layout.setOrientation(LinearLayout.VERTICAL);
8050 if (position > 0) {
8051 View dividerTop = new View(mContext);
8052 dividerTop.setBackgroundResource(
8053 android.R.drawable.divider_horizontal_bright);
8054 layout.addView(dividerTop);
8055 }
8056
8057 if (Container.OPTGROUP == c.mEnabled) {
8058 // Currently select_dialog_multichoice uses CheckedTextViews.
8059 // If that changes, the class cast will no longer be valid.
8060 if (mMultiple) {
8061 Assert.assertTrue(convertView instanceof CheckedTextView);
8062 ((CheckedTextView) convertView).setCheckMarkDrawable(null);
8063 }
8064 } else {
8065 // c.mEnabled == Container.OPTION_DISABLED
8066 // Draw the disabled element in a disabled state.
8067 convertView.setEnabled(false);
8068 }
8069
8070 layout.addView(convertView);
8071 if (position < getCount() - 1) {
8072 View dividerBottom = new View(mContext);
8073 dividerBottom.setBackgroundResource(
8074 android.R.drawable.divider_horizontal_bright);
8075 layout.addView(dividerBottom);
8076 }
8077 return layout;
8078 }
8079 return convertView;
8080 }
8081
8082 @Override
8083 public boolean hasStableIds() {
8084 // AdapterView's onChanged method uses this to determine whether
8085 // to restore the old state. Return false so that the old (out
8086 // of date) state does not replace the new, valid state.
8087 return false;
8088 }
8089
8090 private Container item(int position) {
8091 if (position < 0 || position >= getCount()) {
8092 return null;
8093 }
8094 return getItem(position);
8095 }
8096
8097 @Override
8098 public long getItemId(int position) {
8099 Container item = item(position);
8100 if (item == null) {
8101 return -1;
8102 }
8103 return item.mId;
8104 }
8105
8106 @Override
8107 public boolean areAllItemsEnabled() {
8108 return false;
8109 }
8110
8111 @Override
8112 public boolean isEnabled(int position) {
8113 Container item = item(position);
8114 if (item == null) {
8115 return false;
8116 }
8117 return Container.OPTION_ENABLED == item.mEnabled;
8118 }
8119 }
8120
8121 private InvokeListBox(String[] array, int[] enabled, int[] selected) {
8122 mMultiple = true;
8123 mSelectedArray = selected;
8124
8125 int length = array.length;
8126 mContainers = new Container[length];
8127 for (int i = 0; i < length; i++) {
8128 mContainers[i] = new Container();
8129 mContainers[i].mString = array[i];
8130 mContainers[i].mEnabled = enabled[i];
8131 mContainers[i].mId = i;
8132 }
8133 }
8134
8135 private InvokeListBox(String[] array, int[] enabled, int selection) {
8136 mSelection = selection;
8137 mMultiple = false;
8138
8139 int length = array.length;
8140 mContainers = new Container[length];
8141 for (int i = 0; i < length; i++) {
8142 mContainers[i] = new Container();
8143 mContainers[i].mString = array[i];
8144 mContainers[i].mEnabled = enabled[i];
8145 mContainers[i].mId = i;
8146 }
8147 }
8148
8149 /*
8150 * Whenever the data set changes due to filtering, this class ensures
8151 * that the checked item remains checked.
8152 */
8153 private class SingleDataSetObserver extends DataSetObserver {
8154 private long mCheckedId;
8155 private ListView mListView;
8156 private Adapter mAdapter;
8157
8158 /*
8159 * Create a new observer.
8160 * @param id The ID of the item to keep checked.
8161 * @param l ListView for getting and clearing the checked states
8162 * @param a Adapter for getting the IDs
8163 */
8164 public SingleDataSetObserver(long id, ListView l, Adapter a) {
8165 mCheckedId = id;
8166 mListView = l;
8167 mAdapter = a;
8168 }
8169
8170 @Override
8171 public void onChanged() {
8172 // The filter may have changed which item is checked. Find the
8173 // item that the ListView thinks is checked.
8174 int position = mListView.getCheckedItemPosition();
8175 long id = mAdapter.getItemId(position);
8176 if (mCheckedId != id) {
8177 // Clear the ListView's idea of the checked item, since
8178 // it is incorrect
8179 mListView.clearChoices();
8180 // Search for mCheckedId. If it is in the filtered list,
8181 // mark it as checked
8182 int count = mAdapter.getCount();
8183 for (int i = 0; i < count; i++) {
8184 if (mAdapter.getItemId(i) == mCheckedId) {
8185 mListView.setItemChecked(i, true);
8186 break;
8187 }
8188 }
8189 }
8190 }
8191 }
8192
8193 @Override
8194 public void run() {
8195 final ListView listView = (ListView) LayoutInflater.from(mContext)
8196 .inflate(com.android.internal.R.layout.select_dialog, null);
8197 final MyArrayListAdapter adapter = new MyArrayListAdapter();
8198 AlertDialog.Builder b = new AlertDialog.Builder(mContext)
8199 .setView(listView).setCancelable(true)
8200 .setInverseBackgroundForced(true);
8201
8202 if (mMultiple) {
8203 b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
8204 @Override
8205 public void onClick(DialogInterface dialog, int which) {
8206 mWebViewCore.sendMessage(
8207 EventHub.LISTBOX_CHOICES,
8208 adapter.getCount(), 0,
8209 listView.getCheckedItemPositions());
8210 }});
8211 b.setNegativeButton(android.R.string.cancel,
8212 new DialogInterface.OnClickListener() {
8213 @Override
8214 public void onClick(DialogInterface dialog, int which) {
8215 mWebViewCore.sendMessage(
8216 EventHub.SINGLE_LISTBOX_CHOICE, -2, 0);
8217 }});
8218 }
8219 mListBoxDialog = b.create();
8220 listView.setAdapter(adapter);
8221 listView.setFocusableInTouchMode(true);
8222 // There is a bug (1250103) where the checks in a ListView with
8223 // multiple items selected are associated with the positions, not
8224 // the ids, so the items do not properly retain their checks when
8225 // filtered. Do not allow filtering on multiple lists until
8226 // that bug is fixed.
8227
8228 listView.setTextFilterEnabled(!mMultiple);
8229 if (mMultiple) {
8230 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
8231 int length = mSelectedArray.length;
8232 for (int i = 0; i < length; i++) {
8233 listView.setItemChecked(mSelectedArray[i], true);
8234 }
8235 } else {
8236 listView.setOnItemClickListener(new OnItemClickListener() {
8237 @Override
8238 public void onItemClick(AdapterView<?> parent, View v,
8239 int position, long id) {
8240 // Rather than sending the message right away, send it
8241 // after the page regains focus.
8242 mListBoxMessage = Message.obtain(null,
8243 EventHub.SINGLE_LISTBOX_CHOICE, (int) id, 0);
Michael Kolb87a435a2012-03-22 10:33:50 -07008244 if (mListBoxDialog != null) {
8245 mListBoxDialog.dismiss();
8246 mListBoxDialog = null;
8247 }
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008248 }
8249 });
8250 if (mSelection != -1) {
8251 listView.setSelection(mSelection);
8252 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
8253 listView.setItemChecked(mSelection, true);
8254 DataSetObserver observer = new SingleDataSetObserver(
8255 adapter.getItemId(mSelection), listView, adapter);
8256 adapter.registerDataSetObserver(observer);
8257 }
8258 }
8259 mListBoxDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
8260 @Override
8261 public void onCancel(DialogInterface dialog) {
8262 mWebViewCore.sendMessage(
8263 EventHub.SINGLE_LISTBOX_CHOICE, -2, 0);
8264 mListBoxDialog = null;
8265 }
8266 });
8267 mListBoxDialog.show();
8268 }
8269 }
8270
8271 private Message mListBoxMessage;
8272
8273 /*
8274 * Request a dropdown menu for a listbox with multiple selection.
8275 *
8276 * @param array Labels for the listbox.
8277 * @param enabledArray State for each element in the list. See static
8278 * integers in Container class.
8279 * @param selectedArray Which positions are initally selected.
8280 */
8281 void requestListBox(String[] array, int[] enabledArray, int[]
8282 selectedArray) {
8283 mPrivateHandler.post(
8284 new InvokeListBox(array, enabledArray, selectedArray));
8285 }
8286
8287 /*
8288 * Request a dropdown menu for a listbox with single selection or a single
8289 * <select> element.
8290 *
8291 * @param array Labels for the listbox.
8292 * @param enabledArray State for each element in the list. See static
8293 * integers in Container class.
8294 * @param selection Which position is initally selected.
8295 */
8296 void requestListBox(String[] array, int[] enabledArray, int selection) {
8297 mPrivateHandler.post(
8298 new InvokeListBox(array, enabledArray, selection));
8299 }
8300
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008301 private int getScaledMaxXScroll() {
8302 int width;
8303 if (mHeightCanMeasure == false) {
8304 width = getViewWidth() / 4;
8305 } else {
8306 Rect visRect = new Rect();
8307 calcOurVisibleRect(visRect);
8308 width = visRect.width() / 2;
8309 }
8310 // FIXME the divisor should be retrieved from somewhere
8311 return viewToContentX(width);
8312 }
8313
8314 private int getScaledMaxYScroll() {
8315 int height;
8316 if (mHeightCanMeasure == false) {
8317 height = getViewHeight() / 4;
8318 } else {
8319 Rect visRect = new Rect();
8320 calcOurVisibleRect(visRect);
8321 height = visRect.height() / 2;
8322 }
8323 // FIXME the divisor should be retrieved from somewhere
8324 // the closest thing today is hard-coded into ScrollView.java
8325 // (from ScrollView.java, line 363) int maxJump = height/2;
8326 return Math.round(height * mZoomManager.getInvScale());
8327 }
8328
8329 /**
8330 * Called by JNI to invalidate view
8331 */
8332 private void viewInvalidate() {
8333 invalidate();
8334 }
8335
8336 /**
8337 * Pass the key directly to the page. This assumes that
8338 * nativePageShouldHandleShiftAndArrows() returned true.
8339 */
8340 private void letPageHandleNavKey(int keyCode, long time, boolean down, int metaState) {
8341 int keyEventAction;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008342 if (down) {
8343 keyEventAction = KeyEvent.ACTION_DOWN;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008344 } else {
8345 keyEventAction = KeyEvent.ACTION_UP;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008346 }
8347
8348 KeyEvent event = new KeyEvent(time, time, keyEventAction, keyCode,
8349 1, (metaState & KeyEvent.META_SHIFT_ON)
8350 | (metaState & KeyEvent.META_ALT_ON)
8351 | (metaState & KeyEvent.META_SYM_ON)
8352 , KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0);
John Reck4fa40372012-03-06 14:31:45 -08008353 sendKeyEvent(event);
8354 }
8355
8356 private void sendKeyEvent(KeyEvent event) {
8357 int direction = 0;
8358 switch (event.getKeyCode()) {
8359 case KeyEvent.KEYCODE_DPAD_DOWN:
8360 direction = View.FOCUS_DOWN;
8361 break;
8362 case KeyEvent.KEYCODE_DPAD_UP:
8363 direction = View.FOCUS_UP;
8364 break;
8365 case KeyEvent.KEYCODE_DPAD_LEFT:
8366 direction = View.FOCUS_LEFT;
8367 break;
8368 case KeyEvent.KEYCODE_DPAD_RIGHT:
8369 direction = View.FOCUS_RIGHT;
8370 break;
8371 case KeyEvent.KEYCODE_TAB:
8372 direction = event.isShiftPressed() ? View.FOCUS_BACKWARD : View.FOCUS_FORWARD;
8373 break;
8374 }
8375 if (direction != 0 && mWebView.focusSearch(direction) == null) {
8376 // Can't take focus in that direction
8377 direction = 0;
8378 }
8379 int eventHubAction = EventHub.KEY_UP;
8380 if (event.getAction() == KeyEvent.ACTION_DOWN) {
8381 eventHubAction = EventHub.KEY_DOWN;
8382 int sound = keyCodeToSoundsEffect(event.getKeyCode());
8383 if (sound != 0) {
8384 mWebView.playSoundEffect(sound);
8385 }
8386 }
George Mountdbef1c52012-03-28 14:17:13 -07008387 sendBatchableInputMessage(eventHubAction, direction, 0, event);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008388 }
8389
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008390 /**
8391 * @return Whether accessibility script has been injected.
8392 */
8393 private boolean accessibilityScriptInjected() {
8394 // TODO: Maybe the injected script should announce its presence in
8395 // the page meta-tag so the nativePageShouldHandleShiftAndArrows
8396 // will check that as one of the conditions it looks for
8397 return mAccessibilityScriptInjected;
8398 }
8399
8400 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00008401 * See {@link WebView#setBackgroundColor(int)}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008402 */
8403 @Override
8404 public void setBackgroundColor(int color) {
8405 mBackgroundColor = color;
8406 mWebViewCore.sendMessage(EventHub.SET_BACKGROUND_COLOR, color);
8407 }
8408
8409 /**
Jonathan Dixon19b80112012-03-02 18:18:00 +00008410 * See {@link WebView#debugDump()}
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008411 */
Jonathan Dixon19b80112012-03-02 18:18:00 +00008412 @Override
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008413 @Deprecated
8414 public void debugDump() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008415 }
8416
8417 /**
8418 * Draw the HTML page into the specified canvas. This call ignores any
8419 * view-specific zoom, scroll offset, or other changes. It does not draw
8420 * any view-specific chrome, such as progress or URL bars.
8421 *
Jonathan Dixoncd93e152012-03-02 19:19:44 +00008422 * only needs to be accessible to Browser and testing
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008423 */
8424 public void drawPage(Canvas canvas) {
8425 calcOurContentVisibleRectF(mVisibleContentRect);
8426 nativeDraw(canvas, mVisibleContentRect, 0, 0, false);
8427 }
8428
8429 /**
8430 * Enable the communication b/t the webView and VideoViewProxy
8431 *
Jonathan Dixoncd93e152012-03-02 19:19:44 +00008432 * only used by the Browser
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008433 */
8434 public void setHTML5VideoViewProxy(HTML5VideoViewProxy proxy) {
8435 mHTML5VideoViewProxy = proxy;
8436 }
8437
8438 /**
8439 * Set the time to wait between passing touches to WebCore. See also the
8440 * TOUCH_SENT_INTERVAL member for further discussion.
8441 *
Jonathan Dixoncd93e152012-03-02 19:19:44 +00008442 * This is only used by the DRT test application.
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008443 */
8444 public void setTouchInterval(int interval) {
8445 mCurrentTouchInterval = interval;
8446 }
8447
8448 /**
8449 * Copy text into the clipboard. This is called indirectly from
8450 * WebViewCore.
8451 * @param text The text to put into the clipboard.
8452 */
8453 private void copyToClipboard(String text) {
Jonathan Dixon3c909522012-02-28 18:45:06 +00008454 ClipboardManager cm = (ClipboardManager)mContext
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008455 .getSystemService(Context.CLIPBOARD_SERVICE);
8456 ClipData clip = ClipData.newPlainText(getTitle(), text);
8457 cm.setPrimaryClip(clip);
8458 }
8459
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008460 /*package*/ void autoFillForm(int autoFillQueryId) {
George Mountbcd5dd72012-03-01 08:39:03 -08008461 mPrivateHandler.obtainMessage(AUTOFILL_FORM, autoFillQueryId, 0)
8462 .sendToTarget();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008463 }
8464
8465 /* package */ ViewManager getViewManager() {
8466 return mViewManager;
8467 }
8468
Jonathan Dixoncd93e152012-03-02 19:19:44 +00008469 /** send content invalidate */
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008470 protected void contentInvalidateAll() {
8471 if (mWebViewCore != null && !mBlockWebkitViewMessages) {
8472 mWebViewCore.sendMessage(EventHub.CONTENT_INVALIDATE_ALL);
8473 }
8474 }
8475
Jonathan Dixoncd93e152012-03-02 19:19:44 +00008476 /** discard all textures from tiles. Used in Profiled WebView */
Jonathan Dixon3c909522012-02-28 18:45:06 +00008477 public void discardAllTextures() {
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008478 nativeDiscardAllTextures();
8479 }
8480
John Recka5408e62012-03-16 14:18:44 -07008481 @Override
8482 public void setLayerType(int layerType, Paint paint) {
8483 updateHwAccelerated();
8484 }
8485
8486 private void updateHwAccelerated() {
8487 if (mNativeClass == 0) {
8488 return;
8489 }
8490 boolean hwAccelerated = false;
8491 if (mWebView.isHardwareAccelerated()
8492 && mWebView.getLayerType() != View.LAYER_TYPE_SOFTWARE) {
8493 hwAccelerated = true;
8494 }
Chris Craik00ed0fd2012-04-10 13:10:24 -07008495
8496 // result is of type LayerAndroid::InvalidateFlags, non zero means invalidate/redraw
John Recka5408e62012-03-16 14:18:44 -07008497 int result = nativeSetHwAccelerated(mNativeClass, hwAccelerated);
Chris Craik00ed0fd2012-04-10 13:10:24 -07008498 if (mWebViewCore != null && !mBlockWebkitViewMessages && result != 0) {
8499 mWebViewCore.contentDraw();
John Recka5408e62012-03-16 14:18:44 -07008500 }
8501 }
8502
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008503 /**
8504 * Begin collecting per-tile profiling data
8505 *
Jonathan Dixoncd93e152012-03-02 19:19:44 +00008506 * only used by profiling tests
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008507 */
8508 public void tileProfilingStart() {
8509 nativeTileProfilingStart();
8510 }
8511 /**
8512 * Return per-tile profiling data
8513 *
Jonathan Dixoncd93e152012-03-02 19:19:44 +00008514 * only used by profiling tests
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008515 */
8516 public float tileProfilingStop() {
8517 return nativeTileProfilingStop();
8518 }
8519
Jonathan Dixoncd93e152012-03-02 19:19:44 +00008520 /** only used by profiling tests */
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008521 public void tileProfilingClear() {
8522 nativeTileProfilingClear();
8523 }
Jonathan Dixoncd93e152012-03-02 19:19:44 +00008524 /** only used by profiling tests */
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008525 public int tileProfilingNumFrames() {
8526 return nativeTileProfilingNumFrames();
8527 }
Jonathan Dixoncd93e152012-03-02 19:19:44 +00008528 /** only used by profiling tests */
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008529 public int tileProfilingNumTilesInFrame(int frame) {
8530 return nativeTileProfilingNumTilesInFrame(frame);
8531 }
Jonathan Dixoncd93e152012-03-02 19:19:44 +00008532 /** only used by profiling tests */
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008533 public int tileProfilingGetInt(int frame, int tile, String key) {
8534 return nativeTileProfilingGetInt(frame, tile, key);
8535 }
Jonathan Dixoncd93e152012-03-02 19:19:44 +00008536 /** only used by profiling tests */
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008537 public float tileProfilingGetFloat(int frame, int tile, String key) {
8538 return nativeTileProfilingGetFloat(frame, tile, key);
8539 }
8540
8541 /**
8542 * Checks the focused content for an editable text field. This can be
8543 * text input or ContentEditable.
8544 * @return true if the focused item is an editable text field.
8545 */
8546 boolean focusCandidateIsEditableText() {
John Reckb2676f72012-03-02 14:13:06 -08008547 if (mFocusedNode != null) {
8548 return mFocusedNode.mEditable;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008549 }
John Reckb2676f72012-03-02 14:13:06 -08008550 return false;
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008551 }
8552
John Reckf72a4462012-03-02 14:21:18 -08008553 // Called via JNI
8554 private void postInvalidate() {
8555 mWebView.postInvalidate();
8556 }
8557
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008558 private native void nativeCreate(int ptr, String drawableDir, boolean isHighEndGfx);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008559 private native void nativeDebugDump();
8560 private native void nativeDestroy();
8561
8562 /**
8563 * Draw the picture set with a background color and extra. If
8564 * "splitIfNeeded" is true and the return value is not 0, the return value
8565 * MUST be passed to WebViewCore with SPLIT_PICTURE_SET message so that the
8566 * native allocation can be freed.
8567 */
8568 private native int nativeDraw(Canvas canvas, RectF visibleRect,
8569 int color, int extra, boolean splitIfNeeded);
8570 private native void nativeDumpDisplayTree(String urlOrNull);
8571 private native boolean nativeEvaluateLayersAnimations(int nativeInstance);
Romain Guyd4caee02012-04-23 20:44:04 -07008572 private native int nativeCreateDrawGLFunction(int nativeInstance, Rect rect,
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008573 Rect viewRect, RectF visibleRect, float scale, int extras);
Romain Guyd4caee02012-04-23 20:44:04 -07008574 private native int nativeGetDrawGLFunction(int nativeInstance);
8575 private native void nativeUpdateDrawGLFunction(int nativeInstance, Rect rect, Rect viewRect,
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008576 RectF visibleRect, float scale);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008577 private native String nativeGetSelection();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008578 private native Rect nativeLayerBounds(int layer);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008579 private native void nativeSetHeightCanMeasure(boolean measure);
8580 private native boolean nativeSetBaseLayer(int nativeInstance,
Chris Craikc2c95432012-04-25 15:13:52 -07008581 int layer, boolean showVisualIndicator, boolean isPictureAfterFirstLayout);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008582 private native int nativeGetBaseLayer();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008583 private native void nativeReplaceBaseContent(int content);
8584 private native void nativeCopyBaseContentToPicture(Picture pict);
8585 private native boolean nativeHasContent();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008586 private native void nativeStopGL();
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008587 private native void nativeDiscardAllTextures();
8588 private native void nativeTileProfilingStart();
8589 private native float nativeTileProfilingStop();
8590 private native void nativeTileProfilingClear();
8591 private native int nativeTileProfilingNumFrames();
8592 private native int nativeTileProfilingNumTilesInFrame(int frame);
8593 private native int nativeTileProfilingGetInt(int frame, int tile, String key);
8594 private native float nativeTileProfilingGetFloat(int frame, int tile, String key);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008595
8596 private native void nativeUseHardwareAccelSkia(boolean enabled);
8597
8598 // Returns a pointer to the scrollable LayerAndroid at the given point.
8599 private native int nativeScrollableLayer(int x, int y, Rect scrollRect,
8600 Rect scrollBounds);
8601 /**
8602 * Scroll the specified layer.
8603 * @param layer Id of the layer to scroll, as determined by nativeScrollableLayer.
8604 * @param newX Destination x position to which to scroll.
8605 * @param newY Destination y position to which to scroll.
8606 * @return True if the layer is successfully scrolled.
8607 */
8608 private native boolean nativeScrollLayer(int layer, int newX, int newY);
8609 private native void nativeSetIsScrolling(boolean isScrolling);
8610 private native int nativeGetBackgroundColor();
8611 native boolean nativeSetProperty(String key, String value);
8612 native String nativeGetProperty(String key);
8613 /**
8614 * See {@link ComponentCallbacks2} for the trim levels and descriptions
8615 */
8616 private static native void nativeOnTrimMemory(int level);
8617 private static native void nativeSetPauseDrawing(int instance, boolean pause);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008618 private static native void nativeSetTextSelection(int instance, int selection);
8619 private static native int nativeGetHandleLayerId(int instance, int handle,
George Mountf9c1f992012-03-21 16:06:10 -07008620 Point cursorLocation, QuadF textQuad);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008621 private static native boolean nativeIsBaseFirst(int instance);
George Mountbcd5dd72012-03-01 08:39:03 -08008622 private static native void nativeMapLayerRect(int instance, int layerId,
8623 Rect rect);
John Recka5408e62012-03-16 14:18:44 -07008624 // Returns 1 if a layer sync is needed, else 0
8625 private static native int nativeSetHwAccelerated(int instance, boolean hwAccelerated);
Jonathan Dixonded37ed92012-02-13 17:26:46 -08008626}