2 * Copyright (C) 2012 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.webkit;
19 import android.animation.ObjectAnimator;
20 import android.annotation.Widget;
21 import android.app.ActivityManager;
22 import android.app.AlertDialog;
23 import android.content.BroadcastReceiver;
24 import android.content.ClipData;
25 import android.content.ClipboardManager;
26 import android.content.ComponentCallbacks2;
27 import android.content.Context;
28 import android.content.DialogInterface;
29 import android.content.DialogInterface.OnCancelListener;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.content.pm.PackageManager;
33 import android.content.res.Configuration;
34 import android.database.DataSetObserver;
35 import android.graphics.Bitmap;
36 import android.graphics.BitmapFactory;
37 import android.graphics.BitmapShader;
38 import android.graphics.Canvas;
39 import android.graphics.Color;
40 import android.graphics.ColorFilter;
41 import android.graphics.DrawFilter;
42 import android.graphics.Paint;
43 import android.graphics.PaintFlagsDrawFilter;
44 import android.graphics.Picture;
45 import android.graphics.Point;
46 import android.graphics.PointF;
47 import android.graphics.Rect;
48 import android.graphics.RectF;
49 import android.graphics.Region;
50 import android.graphics.RegionIterator;
51 import android.graphics.Shader;
52 import android.graphics.drawable.Drawable;
53 import android.net.Proxy;
54 import android.net.ProxyProperties;
55 import android.net.Uri;
56 import android.net.http.SslCertificate;
57 import android.os.AsyncTask;
58 import android.os.Bundle;
59 import android.os.Handler;
60 import android.os.Looper;
61 import android.os.Message;
62 import android.os.SystemClock;
63 import android.provider.Settings;
64 import android.security.KeyChain;
65 import android.speech.tts.TextToSpeech;
66 import android.text.Editable;
67 import android.text.InputType;
68 import android.text.Selection;
69 import android.text.TextUtils;
70 import android.util.DisplayMetrics;
71 import android.util.EventLog;
72 import android.util.Log;
73 import android.view.Display;
74 import android.view.Gravity;
75 import android.view.HapticFeedbackConstants;
76 import android.view.HardwareCanvas;
77 import android.view.InputDevice;
78 import android.view.KeyCharacterMap;
79 import android.view.KeyEvent;
80 import android.view.LayoutInflater;
81 import android.view.MotionEvent;
82 import android.view.ScaleGestureDetector;
83 import android.view.SoundEffectConstants;
84 import android.view.VelocityTracker;
85 import android.view.View;
86 import android.view.View.MeasureSpec;
87 import android.view.ViewConfiguration;
88 import android.view.ViewGroup;
89 import android.view.ViewParent;
90 import android.view.ViewTreeObserver;
91 import android.view.WindowManager;
92 import android.view.accessibility.AccessibilityEvent;
93 import android.view.accessibility.AccessibilityManager;
94 import android.view.accessibility.AccessibilityNodeInfo;
95 import android.view.inputmethod.BaseInputConnection;
96 import android.view.inputmethod.EditorInfo;
97 import android.view.inputmethod.InputConnection;
98 import android.view.inputmethod.InputMethodManager;
99 import android.webkit.WebView.HitTestResult;
100 import android.webkit.WebView.PictureListener;
101 import android.webkit.WebViewCore.DrawData;
102 import android.webkit.WebViewCore.EventHub;
103 import android.webkit.WebViewCore.TextFieldInitData;
104 import android.webkit.WebViewCore.TouchHighlightData;
105 import android.webkit.WebViewCore.WebKitHitTest;
106 import android.widget.AbsoluteLayout;
107 import android.widget.Adapter;
108 import android.widget.AdapterView;
109 import android.widget.AdapterView.OnItemClickListener;
110 import android.widget.ArrayAdapter;
111 import android.widget.CheckedTextView;
112 import android.widget.LinearLayout;
113 import android.widget.ListView;
114 import android.widget.OverScroller;
115 import android.widget.PopupWindow;
116 import android.widget.Scroller;
117 import android.widget.TextView;
118 import android.widget.Toast;
120 import junit.framework.Assert;
123 import java.io.FileInputStream;
124 import java.io.FileNotFoundException;
125 import java.io.FileOutputStream;
126 import java.io.IOException;
127 import java.io.InputStream;
128 import java.io.OutputStream;
129 import java.net.URLDecoder;
130 import java.util.ArrayList;
131 import java.util.HashMap;
132 import java.util.HashSet;
133 import java.util.List;
134 import java.util.Map;
135 import java.util.Set;
136 import java.util.Vector;
137 import java.util.regex.Matcher;
138 import java.util.regex.Pattern;
141 * <p>A View that displays web pages. This class is the basis upon which you
142 * can roll your own web browser or simply display some online content within your Activity.
143 * It uses the WebKit rendering engine to display
144 * web pages and includes methods to navigate forward and backward
145 * through a history, zoom in and out, perform text searches and more.</p>
146 * <p>To enable the built-in zoom, set
147 * {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)}
148 * (introduced in API version 3).
149 * <p>Note that, in order for your Activity to access the Internet and load web pages
150 * in a WebView, you must add the {@code INTERNET} permissions to your
151 * Android Manifest file:</p>
152 * <pre><uses-permission android:name="android.permission.INTERNET" /></pre>
154 * <p>This must be a child of the <a
155 * href="{@docRoot}guide/topics/manifest/manifest-element.html">{@code <manifest>}</a>
158 * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-webview.html">Web View
161 * <h3>Basic usage</h3>
163 * <p>By default, a WebView provides no browser-like widgets, does not
164 * enable JavaScript and web page errors are ignored. If your goal is only
165 * to display some HTML as a part of your UI, this is probably fine;
166 * the user won't need to interact with the web page beyond reading
167 * it, and the web page won't need to interact with the user. If you
168 * actually want a full-blown web browser, then you probably want to
169 * invoke the Browser application with a URL Intent rather than show it
170 * with a WebView. For example:
172 * Uri uri = Uri.parse("http://www.example.com");
173 * Intent intent = new Intent(Intent.ACTION_VIEW, uri);
174 * startActivity(intent);
176 * <p>See {@link android.content.Intent} for more information.</p>
178 * <p>To provide a WebView in your own Activity, include a {@code <WebView>} in your layout,
179 * or set the entire Activity window as a WebView during {@link
180 * android.app.Activity#onCreate(Bundle) onCreate()}:</p>
181 * <pre class="prettyprint">
182 * WebView webview = new WebView(this);
183 * setContentView(webview);
186 * <p>Then load the desired web page:</p>
188 * // Simplest usage: note that an exception will NOT be thrown
189 * // if there is an error loading this page (see below).
190 * webview.loadUrl("http://slashdot.org/");
192 * // OR, you can also load from an HTML string:
193 * String summary = "<html><body>You scored <b>192</b> points.</body></html>";
194 * webview.loadData(summary, "text/html", null);
195 * // ... although note that there are restrictions on what this HTML can do.
196 * // See the JavaDocs for {@link #loadData(String,String,String) loadData()} and {@link
197 * #loadDataWithBaseURL(String,String,String,String,String) loadDataWithBaseURL()} for more info.
200 * <p>A WebView has several customization points where you can add your
201 * own behavior. These are:</p>
204 * <li>Creating and setting a {@link android.webkit.WebChromeClient} subclass.
205 * This class is called when something that might impact a
206 * browser UI happens, for instance, progress updates and
207 * JavaScript alerts are sent here (see <a
208 * href="{@docRoot}guide/developing/debug-tasks.html#DebuggingWebPages">Debugging Tasks</a>).
210 * <li>Creating and setting a {@link android.webkit.WebViewClient} subclass.
211 * It will be called when things happen that impact the
212 * rendering of the content, eg, errors or form submissions. You
213 * can also intercept URL loading here (via {@link
214 * android.webkit.WebViewClient#shouldOverrideUrlLoading(WebView,String)
215 * shouldOverrideUrlLoading()}).</li>
216 * <li>Modifying the {@link android.webkit.WebSettings}, such as
217 * enabling JavaScript with {@link android.webkit.WebSettings#setJavaScriptEnabled(boolean)
218 * setJavaScriptEnabled()}. </li>
219 * <li>Injecting Java objects into the WebView using the
220 * {@link android.webkit.WebView#addJavascriptInterface} method. This
221 * method allows you to inject Java objects into a page's JavaScript
222 * context, so that they can be accessed by JavaScript in the page.</li>
225 * <p>Here's a more complicated example, showing error handling,
226 * settings, and progress notification:</p>
228 * <pre class="prettyprint">
229 * // Let's display the progress in the activity title bar, like the
230 * // browser app does.
231 * getWindow().requestFeature(Window.FEATURE_PROGRESS);
233 * webview.getSettings().setJavaScriptEnabled(true);
235 * final Activity activity = this;
236 * webview.setWebChromeClient(new WebChromeClient() {
237 * public void onProgressChanged(WebView view, int progress) {
238 * // Activities and WebViews measure progress with different scales.
239 * // The progress meter will automatically disappear when we reach 100%
240 * activity.setProgress(progress * 1000);
243 * webview.setWebViewClient(new WebViewClient() {
244 * public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
245 * Toast.makeText(activity, "Oh no! " + description, Toast.LENGTH_SHORT).show();
249 * webview.loadUrl("http://slashdot.org/");
252 * <h3>Cookie and window management</h3>
254 * <p>For obvious security reasons, your application has its own
255 * cache, cookie store etc.—it does not share the Browser
256 * application's data. Cookies are managed on a separate thread, so
257 * operations like index building don't block the UI
258 * thread. Follow the instructions in {@link android.webkit.CookieSyncManager}
259 * if you want to use cookies in your application.
262 * <p>By default, requests by the HTML to open new windows are
263 * ignored. This is true whether they be opened by JavaScript or by
264 * the target attribute on a link. You can customize your
265 * {@link WebChromeClient} to provide your own behaviour for opening multiple windows,
266 * and render them in whatever manner you want.</p>
268 * <p>The standard behavior for an Activity is to be destroyed and
269 * recreated when the device orientation or any other configuration changes. This will cause
270 * the WebView to reload the current page. If you don't want that, you
271 * can set your Activity to handle the {@code orientation} and {@code keyboardHidden}
272 * changes, and then just leave the WebView alone. It'll automatically
273 * re-orient itself as appropriate. Read <a
274 * href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling Runtime Changes</a> for
275 * more information about how to handle configuration changes during runtime.</p>
278 * <h3>Building web pages to support different screen densities</h3>
280 * <p>The screen density of a device is based on the screen resolution. A screen with low density
281 * has fewer available pixels per inch, where a screen with high density
282 * has more — sometimes significantly more — pixels per inch. The density of a
283 * screen is important because, other things being equal, a UI element (such as a button) whose
284 * height and width are defined in terms of screen pixels will appear larger on the lower density
285 * screen and smaller on the higher density screen.
286 * For simplicity, Android collapses all actual screen densities into three generalized densities:
287 * high, medium, and low.</p>
288 * <p>By default, WebView scales a web page so that it is drawn at a size that matches the default
289 * appearance on a medium density screen. So, it applies 1.5x scaling on a high density screen
290 * (because its pixels are smaller) and 0.75x scaling on a low density screen (because its pixels
292 * Starting with API Level 5 (Android 2.0), WebView supports DOM, CSS, and meta tag features to help
293 * you (as a web developer) target screens with different screen densities.</p>
294 * <p>Here's a summary of the features you can use to handle different screen densities:</p>
296 * <li>The {@code window.devicePixelRatio} DOM property. The value of this property specifies the
297 * default scaling factor used for the current device. For example, if the value of {@code
298 * window.devicePixelRatio} is "1.0", then the device is considered a medium density (mdpi) device
299 * and default scaling is not applied to the web page; if the value is "1.5", then the device is
300 * considered a high density device (hdpi) and the page content is scaled 1.5x; if the
301 * value is "0.75", then the device is considered a low density device (ldpi) and the content is
302 * scaled 0.75x. However, if you specify the {@code "target-densitydpi"} meta property
303 * (discussed below), then you can stop this default scaling behavior.</li>
304 * <li>The {@code -webkit-device-pixel-ratio} CSS media query. Use this to specify the screen
305 * densities for which this style sheet is to be used. The corresponding value should be either
306 * "0.75", "1", or "1.5", to indicate that the styles are for devices with low density, medium
307 * density, or high density screens, respectively. For example:
309 * <link rel="stylesheet" media="screen and (-webkit-device-pixel-ratio:1.5)" href="hdpi.css" /></pre>
310 * <p>The {@code hdpi.css} stylesheet is only used for devices with a screen pixel ration of 1.5,
311 * which is the high density pixel ratio.</p>
313 * <li>The {@code target-densitydpi} property for the {@code viewport} meta tag. You can use
314 * this to specify the target density for which the web page is designed, using the following
317 * <li>{@code device-dpi} - Use the device's native dpi as the target dpi. Default scaling never
319 * <li>{@code high-dpi} - Use hdpi as the target dpi. Medium and low density screens scale down
320 * as appropriate.</li>
321 * <li>{@code medium-dpi} - Use mdpi as the target dpi. High density screens scale up and
322 * low density screens scale down. This is also the default behavior.</li>
323 * <li>{@code low-dpi} - Use ldpi as the target dpi. Medium and high density screens scale up
324 * as appropriate.</li>
325 * <li><em>{@code <value>}</em> - Specify a dpi value to use as the target dpi (accepted
326 * values are 70-400).</li>
328 * <p>Here's an example meta tag to specify the target density:</p>
329 * <pre><meta name="viewport" content="target-densitydpi=device-dpi" /></pre></li>
331 * <p>If you want to modify your web page for different densities, by using the {@code
332 * -webkit-device-pixel-ratio} CSS media query and/or the {@code
333 * window.devicePixelRatio} DOM property, then you should set the {@code target-densitydpi} meta
334 * property to {@code device-dpi}. This stops Android from performing scaling in your web page and
335 * allows you to make the necessary adjustments for each density via CSS and JavaScript.</p>
337 * <h3>HTML5 Video support</h3>
339 * <p>In order to support inline HTML5 video in your application, you need to have hardware
340 * acceleration turned on, and set a {@link android.webkit.WebChromeClient}. For full screen support,
341 * implementations of {@link WebChromeClient#onShowCustomView(View, WebChromeClient.CustomViewCallback)}
342 * and {@link WebChromeClient#onHideCustomView()} are required,
343 * {@link WebChromeClient#getVideoLoadingProgressView()} is optional.
348 // TODO: Check if any WebView published API methods are called from within here, and if so
349 // we should bounce the call out via the proxy to enable any sub-class to override it.
351 @SuppressWarnings("deprecation")
352 public final class WebViewClassic implements WebViewProvider, WebViewProvider.ScrollDelegate,
353 WebViewProvider.ViewDelegate {
354 private class InnerGlobalLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener {
356 public void onGlobalLayout() {
357 if (mWebView.isShown()) {
363 private class InnerScrollChangedListener implements ViewTreeObserver.OnScrollChangedListener {
365 public void onScrollChanged() {
366 if (mWebView.isShown()) {
373 * InputConnection used for ContentEditable. This captures changes
374 * to the text and sends them either as key strokes or text changes.
376 class WebViewInputConnection extends BaseInputConnection {
377 // Used for mapping characters to keys typed.
378 private KeyCharacterMap mKeyCharacterMap;
379 private boolean mIsKeySentByMe;
380 private int mInputType;
381 private int mImeOptions;
382 private String mHint;
383 private int mMaxLength;
384 private boolean mIsAutoFillable;
385 private boolean mIsAutoCompleteEnabled;
386 private String mName;
387 private int mBatchLevel;
389 public WebViewInputConnection() {
390 super(mWebView, true);
393 public void setAutoFillable(int queryId) {
394 mIsAutoFillable = getSettings().getAutoFillEnabled()
395 && (queryId != WebTextView.FORM_NOT_AUTOFILLABLE);
396 int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
397 if (variation != EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD
398 && (mIsAutoFillable || mIsAutoCompleteEnabled)) {
399 if (mName != null && mName.length() > 0) {
400 requestFormData(mName, mFieldPointer, mIsAutoFillable,
401 mIsAutoCompleteEnabled);
407 public boolean beginBatchEdit() {
408 if (mBatchLevel == 0) {
416 public boolean endBatchEdit() {
418 if (mBatchLevel == 0) {
424 public boolean getIsAutoFillable() {
425 return mIsAutoFillable;
429 public boolean sendKeyEvent(KeyEvent event) {
430 // Some IMEs send key events directly using sendKeyEvents.
431 // WebViewInputConnection should treat these as text changes.
432 if (!mIsKeySentByMe) {
433 if (event.getAction() == KeyEvent.ACTION_UP) {
434 if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
435 return deleteSurroundingText(1, 0);
436 } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) {
437 return deleteSurroundingText(0, 1);
438 } else if (event.getUnicodeChar() != 0){
439 String newComposingText =
440 Character.toString((char)event.getUnicodeChar());
441 return commitText(newComposingText, 1);
443 } else if (event.getAction() == KeyEvent.ACTION_DOWN &&
444 (event.getKeyCode() == KeyEvent.KEYCODE_DEL
445 || event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL
446 || event.getUnicodeChar() != 0)) {
447 return true; // only act on action_down
450 return super.sendKeyEvent(event);
453 public void setTextAndKeepSelection(CharSequence text) {
454 Editable editable = getEditable();
455 int selectionStart = Selection.getSelectionStart(editable);
456 int selectionEnd = Selection.getSelectionEnd(editable);
457 text = limitReplaceTextByMaxLength(text, editable.length());
458 editable.replace(0, editable.length(), text);
460 // Keep the previous selection.
461 selectionStart = Math.min(selectionStart, editable.length());
462 selectionEnd = Math.min(selectionEnd, editable.length());
463 setSelection(selectionStart, selectionEnd);
464 finishComposingText();
467 public void replaceSelection(CharSequence text) {
468 Editable editable = getEditable();
469 int selectionStart = Selection.getSelectionStart(editable);
470 int selectionEnd = Selection.getSelectionEnd(editable);
471 text = limitReplaceTextByMaxLength(text, selectionEnd - selectionStart);
472 setNewText(selectionStart, selectionEnd, text);
473 editable.replace(selectionStart, selectionEnd, text);
475 // Move caret to the end of the new text
476 int newCaret = selectionStart + text.length();
477 setSelection(newCaret, newCaret);
481 public boolean setComposingText(CharSequence text, int newCursorPosition) {
482 Editable editable = getEditable();
483 int start = getComposingSpanStart(editable);
484 int end = getComposingSpanEnd(editable);
485 if (start < 0 || end < 0) {
486 start = Selection.getSelectionStart(editable);
487 end = Selection.getSelectionEnd(editable);
494 CharSequence limitedText = limitReplaceTextByMaxLength(text, end - start);
495 setNewText(start, end, limitedText);
496 if (limitedText != text) {
497 newCursorPosition -= text.length() - limitedText.length();
499 super.setComposingText(limitedText, newCursorPosition);
500 if (limitedText != text) {
502 int lastCaret = start + limitedText.length();
503 finishComposingText();
504 setSelection(lastCaret, lastCaret);
510 public boolean commitText(CharSequence text, int newCursorPosition) {
511 setComposingText(text, newCursorPosition);
512 int cursorPosition = Selection.getSelectionEnd(getEditable());
513 setComposingRegion(cursorPosition, cursorPosition);
518 public boolean deleteSurroundingText(int leftLength, int rightLength) {
519 Editable editable = getEditable();
520 int cursorPosition = Selection.getSelectionEnd(editable);
521 int startDelete = Math.max(0, cursorPosition - leftLength);
522 int endDelete = Math.min(editable.length(),
523 cursorPosition + rightLength);
524 setNewText(startDelete, endDelete, "");
525 return super.deleteSurroundingText(leftLength, rightLength);
529 public boolean performEditorAction(int editorAction) {
531 boolean handled = true;
532 switch (editorAction) {
533 case EditorInfo.IME_ACTION_NEXT:
534 mWebView.requestFocus(View.FOCUS_FORWARD);
536 case EditorInfo.IME_ACTION_PREVIOUS:
537 mWebView.requestFocus(View.FOCUS_BACKWARD);
539 case EditorInfo.IME_ACTION_DONE:
540 WebViewClassic.this.hideSoftKeyboard();
542 case EditorInfo.IME_ACTION_GO:
543 case EditorInfo.IME_ACTION_SEARCH:
544 WebViewClassic.this.hideSoftKeyboard();
545 String text = getEditable().toString();
546 passToJavaScript(text, new KeyEvent(KeyEvent.ACTION_DOWN,
547 KeyEvent.KEYCODE_ENTER));
548 passToJavaScript(text, new KeyEvent(KeyEvent.ACTION_UP,
549 KeyEvent.KEYCODE_ENTER));
553 handled = super.performEditorAction(editorAction);
560 public void initEditorInfo(WebViewCore.TextFieldInitData initData) {
561 int type = initData.mType;
562 int inputType = InputType.TYPE_CLASS_TEXT
563 | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
564 int imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
565 | EditorInfo.IME_FLAG_NO_FULLSCREEN;
566 if (!initData.mIsSpellCheckEnabled) {
567 inputType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
569 if (WebTextView.TEXT_AREA != type) {
570 if (initData.mIsTextFieldNext) {
571 imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
573 if (initData.mIsTextFieldPrev) {
574 imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
578 case WebTextView.NORMAL_TEXT_FIELD:
579 imeOptions |= EditorInfo.IME_ACTION_GO;
581 case WebTextView.TEXT_AREA:
582 inputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE
583 | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
584 | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
585 imeOptions |= EditorInfo.IME_ACTION_NONE;
587 case WebTextView.PASSWORD:
588 inputType |= EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD;
589 imeOptions |= EditorInfo.IME_ACTION_GO;
591 case WebTextView.SEARCH:
592 imeOptions |= EditorInfo.IME_ACTION_SEARCH;
594 case WebTextView.EMAIL:
595 // inputType needs to be overwritten because of the different text variation.
596 inputType = InputType.TYPE_CLASS_TEXT
597 | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
598 imeOptions |= EditorInfo.IME_ACTION_GO;
600 case WebTextView.NUMBER:
601 // inputType needs to be overwritten because of the different class.
602 inputType = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL
603 | InputType.TYPE_NUMBER_FLAG_SIGNED | InputType.TYPE_NUMBER_FLAG_DECIMAL;
604 // Number and telephone do not have both a Tab key and an
605 // action, so set the action to NEXT
606 imeOptions |= EditorInfo.IME_ACTION_NEXT;
608 case WebTextView.TELEPHONE:
609 // inputType needs to be overwritten because of the different class.
610 inputType = InputType.TYPE_CLASS_PHONE;
611 imeOptions |= EditorInfo.IME_ACTION_NEXT;
613 case WebTextView.URL:
614 // TYPE_TEXT_VARIATION_URI prevents Tab key from showing, so
615 // exclude it for now.
616 imeOptions |= EditorInfo.IME_ACTION_GO;
617 inputType |= InputType.TYPE_TEXT_VARIATION_URI;
620 imeOptions |= EditorInfo.IME_ACTION_GO;
623 mHint = initData.mLabel;
624 mInputType = inputType;
625 mImeOptions = imeOptions;
626 mMaxLength = initData.mMaxLength;
627 mIsAutoCompleteEnabled = initData.mIsAutoCompleteEnabled;
628 mName = initData.mName;
629 mAutoCompletePopup.clearAdapter();
632 public void setupEditorInfo(EditorInfo outAttrs) {
633 outAttrs.inputType = mInputType;
634 outAttrs.imeOptions = mImeOptions;
635 outAttrs.hintText = mHint;
636 outAttrs.initialCapsMode = getCursorCapsMode(InputType.TYPE_CLASS_TEXT);
640 * Sends a text change to webkit indirectly. If it is a single-
641 * character add or delete, it sends it as a key stroke. If it cannot
642 * be represented as a key stroke, it sends it as a field change.
643 * @param start The start offset (inclusive) of the text being changed.
644 * @param end The end offset (exclusive) of the text being changed.
645 * @param text The new text to replace the changed text.
647 private void setNewText(int start, int end, CharSequence text) {
648 mIsKeySentByMe = true;
649 Editable editable = getEditable();
650 CharSequence original = editable.subSequence(start, end);
651 boolean isCharacterAdd = false;
652 boolean isCharacterDelete = false;
653 int textLength = text.length();
654 int originalLength = original.length();
655 if (textLength > originalLength) {
656 isCharacterAdd = (textLength == originalLength + 1)
657 && TextUtils.regionMatches(text, 0, original, 0,
659 } else if (originalLength > textLength) {
660 isCharacterDelete = (textLength == originalLength - 1)
661 && TextUtils.regionMatches(text, 0, original, 0,
664 if (isCharacterAdd) {
665 sendCharacter(text.charAt(textLength - 1));
666 } else if (isCharacterDelete) {
667 sendKey(KeyEvent.KEYCODE_DEL);
668 } else if ((textLength != originalLength) ||
669 !TextUtils.regionMatches(text, 0, original, 0,
671 // Send a message so that key strokes and text replacement
672 // do not come out of order.
673 Message replaceMessage = mPrivateHandler.obtainMessage(
674 REPLACE_TEXT, start, end, text.toString());
675 mPrivateHandler.sendMessage(replaceMessage);
677 if (mAutoCompletePopup != null) {
678 StringBuilder newText = new StringBuilder();
679 newText.append(editable.subSequence(0, start));
680 newText.append(text);
681 newText.append(editable.subSequence(end, editable.length()));
682 mAutoCompletePopup.setText(newText.toString());
684 mIsKeySentByMe = false;
688 * Send a single character to the WebView as a key down and up event.
689 * @param c The character to be sent.
691 private void sendCharacter(char c) {
692 if (mKeyCharacterMap == null) {
693 mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
695 char[] chars = new char[1];
697 KeyEvent[] events = mKeyCharacterMap.getEvents(chars);
698 if (events != null) {
699 for (KeyEvent event : events) {
703 Message msg = mPrivateHandler.obtainMessage(KEY_PRESS, (int) c, 0);
704 mPrivateHandler.sendMessage(msg);
709 * Send a key event for a specific key code, not a standard
711 * @param keyCode The key code to send.
713 private void sendKey(int keyCode) {
714 long eventTime = SystemClock.uptimeMillis();
715 sendKeyEvent(new KeyEvent(eventTime, eventTime,
716 KeyEvent.ACTION_DOWN, keyCode, 0, 0,
717 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
718 KeyEvent.FLAG_SOFT_KEYBOARD));
719 sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
720 KeyEvent.ACTION_UP, keyCode, 0, 0,
721 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
722 KeyEvent.FLAG_SOFT_KEYBOARD));
725 private CharSequence limitReplaceTextByMaxLength(CharSequence text,
727 if (mMaxLength > 0) {
728 Editable editable = getEditable();
729 int maxReplace = mMaxLength - editable.length() + numReplaced;
730 if (maxReplace < text.length()) {
731 maxReplace = Math.max(maxReplace, 0);
732 // New length is greater than the maximum. trim it down.
733 text = text.subSequence(0, maxReplace);
739 private void restartInput() {
740 InputMethodManager imm = InputMethodManager.peekInstance();
742 // Since the text has changed, do not allow the IME to replace the
743 // existing text as though it were a completion.
744 imm.restartInput(mWebView);
749 private class PastePopupWindow extends PopupWindow implements View.OnClickListener {
750 private ViewGroup mContentView;
751 private TextView mPasteTextView;
753 public PastePopupWindow() {
754 super(mContext, null,
755 com.android.internal.R.attr.textSelectHandleWindowStyle);
756 setClippingEnabled(true);
757 LinearLayout linearLayout = new LinearLayout(mContext);
758 linearLayout.setOrientation(LinearLayout.HORIZONTAL);
759 mContentView = linearLayout;
760 mContentView.setBackgroundResource(
761 com.android.internal.R.drawable.text_edit_paste_window);
763 LayoutInflater inflater = (LayoutInflater)mContext.
764 getSystemService(Context.LAYOUT_INFLATER_SERVICE);
766 ViewGroup.LayoutParams wrapContent = new ViewGroup.LayoutParams(
767 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
769 mPasteTextView = (TextView) inflater.inflate(
770 com.android.internal.R.layout.text_edit_action_popup_text, null);
771 mPasteTextView.setLayoutParams(wrapContent);
772 mContentView.addView(mPasteTextView);
773 mPasteTextView.setText(com.android.internal.R.string.paste);
774 mPasteTextView.setOnClickListener(this);
775 this.setContentView(mContentView);
778 public void show(Point cursorBottom, Point cursorTop,
779 int windowLeft, int windowTop) {
782 int width = mContentView.getMeasuredWidth();
783 int height = mContentView.getMeasuredHeight();
784 int y = cursorTop.y - height;
785 int x = cursorTop.x - (width / 2);
787 // There's not enough room vertically, move it below the
789 ensureSelectionHandles();
790 y = cursorBottom.y + mSelectHandleCenter.getIntrinsicHeight();
791 x = cursorBottom.x - (width / 2);
793 if (x < windowLeft) {
797 showAtLocation(mWebView, Gravity.NO_GRAVITY, x, y);
799 update(x, y, width, height);
807 public void onClick(View view) {
808 pasteFromClipboard();
812 protected void measureContent() {
813 final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
814 mContentView.measure(
815 View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels,
816 View.MeasureSpec.AT_MOST),
817 View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels,
818 View.MeasureSpec.AT_MOST));
822 // The listener to capture global layout change event.
823 private InnerGlobalLayoutListener mGlobalLayoutListener = null;
825 // The listener to capture scroll event.
826 private InnerScrollChangedListener mScrollChangedListener = null;
828 // if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing
829 // the screen all-the-time. Good for profiling our drawing code
830 static private final boolean AUTO_REDRAW_HACK = false;
832 // The rate at which edit text is scrolled in content pixels per millisecond
833 static private final float TEXT_SCROLL_RATE = 0.01f;
835 // The presumed scroll rate for the first scroll of edit text
836 static private final long TEXT_SCROLL_FIRST_SCROLL_MS = 16;
838 // true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK
839 private boolean mAutoRedraw;
841 // Reference to the AlertDialog displayed by InvokeListBox.
842 // It's used to dismiss the dialog in destroy if not done before.
843 private AlertDialog mListBoxDialog = null;
845 static final String LOGTAG = "webview";
847 private ZoomManager mZoomManager;
849 private final Rect mGLRectViewport = new Rect();
850 private final Rect mViewRectViewport = new Rect();
851 private final RectF mVisibleContentRect = new RectF();
852 private boolean mGLViewportEmpty = false;
853 WebViewInputConnection mInputConnection = null;
854 private int mFieldPointer;
855 private PastePopupWindow mPasteWindow;
856 private AutoCompletePopup mAutoCompletePopup;
857 Rect mEditTextContentBounds = new Rect();
858 Rect mEditTextContent = new Rect();
859 int mEditTextLayerId;
860 boolean mIsEditingText = false;
861 ArrayList<Message> mBatchedTextChanges = new ArrayList<Message>();
862 boolean mIsBatchingTextChanges = false;
863 private long mLastEditScroll = 0;
865 private static class OnTrimMemoryListener implements ComponentCallbacks2 {
866 private static OnTrimMemoryListener sInstance = null;
868 static void init(Context c) {
869 if (sInstance == null) {
870 sInstance = new OnTrimMemoryListener(c.getApplicationContext());
874 private OnTrimMemoryListener(Context c) {
875 c.registerComponentCallbacks(this);
879 public void onConfigurationChanged(Configuration newConfig) {
884 public void onLowMemory() {
889 public void onTrimMemory(int level) {
890 if (DebugFlags.WEB_VIEW) {
891 Log.d("WebView", "onTrimMemory: " + level);
893 // When framework reset EGL context during high memory pressure, all
894 // the existing GL resources for the html5 video will be destroyed
896 // Here we just need to clean up the Surface Texture which is static.
897 if (level >= TRIM_MEMORY_UI_HIDDEN) {
898 HTML5VideoInline.cleanupSurfaceTexture();
900 WebViewClassic.nativeOnTrimMemory(level);
905 // A final CallbackProxy shared by WebViewCore and BrowserFrame.
906 private CallbackProxy mCallbackProxy;
908 private WebViewDatabase mDatabase;
910 // SSL certificate for the main top-level page (if secure)
911 private SslCertificate mCertificate;
913 // Native WebView pointer that is 0 until the native object has been
915 private int mNativeClass;
916 // This would be final but it needs to be set to null when the WebView is
918 private WebViewCore mWebViewCore;
919 // Handler for dispatching UI messages.
920 /* package */ final Handler mPrivateHandler = new PrivateHandler();
921 // Used to ignore changes to webkit text that arrives to the UI side after
923 private int mTextGeneration;
925 /* package */ void incrementTextGeneration() { mTextGeneration++; }
927 // Used by WebViewCore to create child views.
928 /* package */ ViewManager mViewManager;
930 // Used to display in full screen mode
931 PluginFullScreenHolder mFullScreenHolder;
934 * Position of the last touch event in pixels.
935 * Use integer to prevent loss of dragging delta calculation accuracy;
936 * which was done in float and converted to integer, and resulted in gradual
937 * and compounding touch position and view dragging mismatch.
939 private int mLastTouchX;
940 private int mLastTouchY;
941 private int mStartTouchX;
942 private int mStartTouchY;
943 private float mAverageAngle;
946 * Time of the last touch event.
948 private long mLastTouchTime;
951 * Time of the last time sending touch event to WebViewCore
953 private long mLastSentTouchTime;
956 * The minimum elapsed time before sending another ACTION_MOVE event to
957 * WebViewCore. This really should be tuned for each type of the devices.
958 * For example in Google Map api test case, it takes Dream device at least
959 * 150ms to do a full cycle in the WebViewCore by processing a touch event,
960 * triggering the layout and drawing the picture. While the same process
961 * takes 60+ms on the current high speed device. If we make
962 * TOUCH_SENT_INTERVAL too small, there will be multiple touch events sent
963 * to WebViewCore queue and the real layout and draw events will be pushed
964 * to further, which slows down the refresh rate. Choose 50 to favor the
965 * current high speed devices. For Dream like devices, 100 is a better
966 * choice. Maybe make this in the buildspec later.
967 * (Update 12/14/2010: changed to 0 since current device should be able to
968 * handle the raw events and Map team voted to have the raw events too.
970 private static final int TOUCH_SENT_INTERVAL = 0;
971 private int mCurrentTouchInterval = TOUCH_SENT_INTERVAL;
974 * Helper class to get velocity for fling
976 VelocityTracker mVelocityTracker;
977 private int mMaximumFling;
978 private float mLastVelocity;
979 private float mLastVelX;
980 private float mLastVelY;
982 // The id of the native layer being scrolled.
983 private int mCurrentScrollingLayerId;
984 private Rect mScrollingLayerRect = new Rect();
986 // only trigger accelerated fling if the new velocity is at least
987 // MINIMUM_VELOCITY_RATIO_FOR_ACCELERATION times of the previous velocity
988 private static final float MINIMUM_VELOCITY_RATIO_FOR_ACCELERATION = 0.2f;
992 * TODO: Some of this is now unnecessary as it is handled by
993 * WebInputTouchDispatcher (such as click, long press, and double tap).
995 private int mTouchMode = TOUCH_DONE_MODE;
996 private static final int TOUCH_INIT_MODE = 1;
997 private static final int TOUCH_DRAG_START_MODE = 2;
998 private static final int TOUCH_DRAG_MODE = 3;
999 private static final int TOUCH_SHORTPRESS_START_MODE = 4;
1000 private static final int TOUCH_SHORTPRESS_MODE = 5;
1001 private static final int TOUCH_DOUBLE_TAP_MODE = 6;
1002 private static final int TOUCH_DONE_MODE = 7;
1003 private static final int TOUCH_PINCH_DRAG = 8;
1004 private static final int TOUCH_DRAG_LAYER_MODE = 9;
1005 private static final int TOUCH_DRAG_TEXT_MODE = 10;
1007 // true when the touch movement exceeds the slop
1008 private boolean mConfirmMove;
1009 private boolean mTouchInEditText;
1011 // Whether or not to draw the cursor ring.
1012 private boolean mDrawCursorRing = true;
1014 // true if onPause has been called (and not onResume)
1015 private boolean mIsPaused;
1017 private HitTestResult mInitialHitTestResult;
1018 private WebKitHitTest mFocusedNode;
1021 * Customizable constant
1023 // pre-computed square of ViewConfiguration.getScaledTouchSlop()
1024 private int mTouchSlopSquare;
1025 // pre-computed square of ViewConfiguration.getScaledDoubleTapSlop()
1026 private int mDoubleTapSlopSquare;
1027 // pre-computed density adjusted navigation slop
1028 private int mNavSlop;
1029 // This should be ViewConfiguration.getTapTimeout()
1030 // But system time out is 100ms, which is too short for the browser.
1031 // In the browser, if it switches out of tap too soon, jump tap won't work.
1032 // In addition, a double tap on a trackpad will always have a duration of
1033 // 300ms, so this value must be at least that (otherwise we will timeout the
1034 // first tap and convert it to a long press).
1035 private static final int TAP_TIMEOUT = 300;
1036 // This should be ViewConfiguration.getLongPressTimeout()
1037 // But system time out is 500ms, which is too short for the browser.
1038 // With a short timeout, it's difficult to treat trigger a short press.
1039 private static final int LONG_PRESS_TIMEOUT = 1000;
1040 // needed to avoid flinging after a pause of no movement
1041 private static final int MIN_FLING_TIME = 250;
1042 // draw unfiltered after drag is held without movement
1043 private static final int MOTIONLESS_TIME = 100;
1044 // The amount of content to overlap between two screens when going through
1045 // pages with the space bar, in pixels.
1046 private static final int PAGE_SCROLL_OVERLAP = 24;
1049 * These prevent calling requestLayout if either dimension is fixed. This
1050 * depends on the layout parameters and the measure specs.
1052 boolean mWidthCanMeasure;
1053 boolean mHeightCanMeasure;
1055 // Remember the last dimensions we sent to the native side so we can avoid
1056 // sending the same dimensions more than once.
1058 int mLastHeightSent;
1059 // Since view height sent to webkit could be fixed to avoid relayout, this
1060 // value records the last sent actual view height.
1061 int mLastActualHeightSent;
1063 private int mContentWidth; // cache of value from WebViewCore
1064 private int mContentHeight; // cache of value from WebViewCore
1066 // Need to have the separate control for horizontal and vertical scrollbar
1067 // style than the View's single scrollbar style
1068 private boolean mOverlayHorizontalScrollbar = true;
1069 private boolean mOverlayVerticalScrollbar = false;
1071 // our standard speed. this way small distances will be traversed in less
1072 // time than large distances, but we cap the duration, so that very large
1073 // distances won't take too long to get there.
1074 private static final int STD_SPEED = 480; // pixels per second
1075 // time for the longest scroll animation
1076 private static final int MAX_DURATION = 750; // milliseconds
1077 private static final int SLIDE_TITLE_DURATION = 500; // milliseconds
1079 // Used by OverScrollGlow
1080 OverScroller mScroller;
1081 Scroller mEditTextScroller;
1083 private boolean mInOverScrollMode = false;
1084 private static Paint mOverScrollBackground;
1085 private static Paint mOverScrollBorder;
1087 private boolean mWrapContent;
1088 private static final int MOTIONLESS_FALSE = 0;
1089 private static final int MOTIONLESS_PENDING = 1;
1090 private static final int MOTIONLESS_TRUE = 2;
1091 private static final int MOTIONLESS_IGNORE = 3;
1092 private int mHeldMotionless;
1094 // An instance for injecting accessibility in WebViews with disabled
1095 // JavaScript or ones for which no accessibility script exists
1096 private AccessibilityInjector mAccessibilityInjector;
1098 // flag indicating if accessibility script is injected so we
1099 // know to handle Shift and arrows natively first
1100 private boolean mAccessibilityScriptInjected;
1104 * How long the caret handle will last without being touched.
1106 private static final long CARET_HANDLE_STAMINA_MS = 3000;
1108 private Drawable mSelectHandleLeft;
1109 private Drawable mSelectHandleRight;
1110 private Drawable mSelectHandleCenter;
1111 private Point mSelectHandleLeftOffset;
1112 private Point mSelectHandleRightOffset;
1113 private Point mSelectHandleCenterOffset;
1114 private Point mSelectCursorBase = new Point();
1115 private int mSelectCursorBaseLayerId;
1116 private QuadF mSelectCursorBaseTextQuad = new QuadF();
1117 private Point mSelectCursorExtent = new Point();
1118 private int mSelectCursorExtentLayerId;
1119 private QuadF mSelectCursorExtentTextQuad = new QuadF();
1120 private Point mSelectDraggingCursor;
1121 private Point mSelectDraggingOffset;
1122 private QuadF mSelectDraggingTextQuad;
1123 private boolean mIsCaretSelection;
1124 static final int HANDLE_ID_START = 0;
1125 static final int HANDLE_ID_END = 1;
1126 static final int HANDLE_ID_BASE = 2;
1127 static final int HANDLE_ID_EXTENT = 3;
1129 // the color used to highlight the touch rectangles
1130 static final int HIGHLIGHT_COLOR = 0x6633b5e5;
1131 // the region indicating where the user touched on the screen
1132 private Region mTouchHighlightRegion = new Region();
1133 // the paint for the touch highlight
1134 private Paint mTouchHightlightPaint = new Paint();
1136 private static final boolean DEBUG_TOUCH_HIGHLIGHT = true;
1137 private static final int TOUCH_HIGHLIGHT_ELAPSE_TIME = 2000;
1138 private Paint mTouchCrossHairColor;
1139 private int mTouchHighlightX;
1140 private int mTouchHighlightY;
1141 private long mTouchHighlightRequested;
1143 // Basically this proxy is used to tell the Video to update layer tree at
1144 // SetBaseLayer time and to pause when WebView paused.
1145 private HTML5VideoViewProxy mHTML5VideoViewProxy;
1147 // If we are using a set picture, don't send view updates to webkit
1148 private boolean mBlockWebkitViewMessages = false;
1150 // cached value used to determine if we need to switch drawing models
1151 private boolean mHardwareAccelSkia = false;
1154 * Private message ids
1156 private static final int REMEMBER_PASSWORD = 1;
1157 private static final int NEVER_REMEMBER_PASSWORD = 2;
1158 private static final int SWITCH_TO_SHORTPRESS = 3;
1159 private static final int SWITCH_TO_LONGPRESS = 4;
1160 private static final int RELEASE_SINGLE_TAP = 5;
1161 private static final int REQUEST_FORM_DATA = 6;
1162 private static final int DRAG_HELD_MOTIONLESS = 8;
1163 private static final int AWAKEN_SCROLL_BARS = 9;
1164 private static final int PREVENT_DEFAULT_TIMEOUT = 10;
1165 private static final int SCROLL_SELECT_TEXT = 11;
1168 private static final int FIRST_PRIVATE_MSG_ID = REMEMBER_PASSWORD;
1169 private static final int LAST_PRIVATE_MSG_ID = SCROLL_SELECT_TEXT;
1172 * Package message ids
1174 static final int SCROLL_TO_MSG_ID = 101;
1175 static final int NEW_PICTURE_MSG_ID = 105;
1176 static final int WEBCORE_INITIALIZED_MSG_ID = 107;
1177 static final int UPDATE_TEXTFIELD_TEXT_MSG_ID = 108;
1178 static final int UPDATE_ZOOM_RANGE = 109;
1179 static final int TAKE_FOCUS = 110;
1180 static final int CLEAR_TEXT_ENTRY = 111;
1181 static final int UPDATE_TEXT_SELECTION_MSG_ID = 112;
1182 static final int SHOW_RECT_MSG_ID = 113;
1183 static final int LONG_PRESS_CENTER = 114;
1184 static final int PREVENT_TOUCH_ID = 115;
1185 static final int WEBCORE_NEED_TOUCH_EVENTS = 116;
1186 // obj=Rect in doc coordinates
1187 static final int INVAL_RECT_MSG_ID = 117;
1188 static final int REQUEST_KEYBOARD = 118;
1189 static final int SHOW_FULLSCREEN = 120;
1190 static final int HIDE_FULLSCREEN = 121;
1191 static final int REPLACE_BASE_CONTENT = 123;
1192 static final int UPDATE_MATCH_COUNT = 126;
1193 static final int CENTER_FIT_RECT = 127;
1194 static final int SET_SCROLLBAR_MODES = 129;
1195 static final int SELECTION_STRING_CHANGED = 130;
1196 static final int HIT_TEST_RESULT = 131;
1197 static final int SAVE_WEBARCHIVE_FINISHED = 132;
1199 static final int SET_AUTOFILLABLE = 133;
1200 static final int AUTOFILL_COMPLETE = 134;
1202 static final int SCREEN_ON = 136;
1203 static final int ENTER_FULLSCREEN_VIDEO = 137;
1204 static final int UPDATE_ZOOM_DENSITY = 139;
1205 static final int EXIT_FULLSCREEN_VIDEO = 140;
1207 static final int COPY_TO_CLIPBOARD = 141;
1208 static final int INIT_EDIT_FIELD = 142;
1209 static final int REPLACE_TEXT = 143;
1210 static final int CLEAR_CARET_HANDLE = 144;
1211 static final int KEY_PRESS = 145;
1212 static final int RELOCATE_AUTO_COMPLETE_POPUP = 146;
1213 static final int FOCUS_NODE_CHANGED = 147;
1214 static final int AUTOFILL_FORM = 148;
1215 static final int SCROLL_EDIT_TEXT = 149;
1216 static final int EDIT_TEXT_SIZE_CHANGED = 150;
1217 static final int SHOW_CARET_HANDLE = 151;
1218 static final int UPDATE_CONTENT_BOUNDS = 152;
1220 private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID;
1221 private static final int LAST_PACKAGE_MSG_ID = HIT_TEST_RESULT;
1223 static final String[] HandlerPrivateDebugString = {
1224 "REMEMBER_PASSWORD", // = 1;
1225 "NEVER_REMEMBER_PASSWORD", // = 2;
1226 "SWITCH_TO_SHORTPRESS", // = 3;
1227 "SWITCH_TO_LONGPRESS", // = 4;
1228 "RELEASE_SINGLE_TAP", // = 5;
1229 "REQUEST_FORM_DATA", // = 6;
1230 "RESUME_WEBCORE_PRIORITY", // = 7;
1231 "DRAG_HELD_MOTIONLESS", // = 8;
1232 "AWAKEN_SCROLL_BARS", // = 9;
1233 "PREVENT_DEFAULT_TIMEOUT", // = 10;
1234 "SCROLL_SELECT_TEXT" // = 11;
1237 static final String[] HandlerPackageDebugString = {
1238 "SCROLL_TO_MSG_ID", // = 101;
1242 "NEW_PICTURE_MSG_ID", // = 105;
1243 "UPDATE_TEXT_ENTRY_MSG_ID", // = 106;
1244 "WEBCORE_INITIALIZED_MSG_ID", // = 107;
1245 "UPDATE_TEXTFIELD_TEXT_MSG_ID", // = 108;
1246 "UPDATE_ZOOM_RANGE", // = 109;
1247 "UNHANDLED_NAV_KEY", // = 110;
1248 "CLEAR_TEXT_ENTRY", // = 111;
1249 "UPDATE_TEXT_SELECTION_MSG_ID", // = 112;
1250 "SHOW_RECT_MSG_ID", // = 113;
1251 "LONG_PRESS_CENTER", // = 114;
1252 "PREVENT_TOUCH_ID", // = 115;
1253 "WEBCORE_NEED_TOUCH_EVENTS", // = 116;
1254 "INVAL_RECT_MSG_ID", // = 117;
1255 "REQUEST_KEYBOARD", // = 118;
1256 "DO_MOTION_UP", // = 119;
1257 "SHOW_FULLSCREEN", // = 120;
1258 "HIDE_FULLSCREEN", // = 121;
1259 "DOM_FOCUS_CHANGED", // = 122;
1260 "REPLACE_BASE_CONTENT", // = 123;
1261 "RETURN_LABEL", // = 125;
1262 "UPDATE_MATCH_COUNT", // = 126;
1263 "CENTER_FIT_RECT", // = 127;
1264 "REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID", // = 128;
1265 "SET_SCROLLBAR_MODES", // = 129;
1266 "SELECTION_STRING_CHANGED", // = 130;
1267 "SET_TOUCH_HIGHLIGHT_RECTS", // = 131;
1268 "SAVE_WEBARCHIVE_FINISHED", // = 132;
1269 "SET_AUTOFILLABLE", // = 133;
1270 "AUTOFILL_COMPLETE", // = 134;
1271 "SELECT_AT", // = 135;
1272 "SCREEN_ON", // = 136;
1273 "ENTER_FULLSCREEN_VIDEO", // = 137;
1274 "UPDATE_SELECTION", // = 138;
1275 "UPDATE_ZOOM_DENSITY" // = 139;
1278 // If the site doesn't use the viewport meta tag to specify the viewport,
1279 // use DEFAULT_VIEWPORT_WIDTH as the default viewport width
1280 static final int DEFAULT_VIEWPORT_WIDTH = 980;
1282 // normally we try to fit the content to the minimum preferred width
1283 // calculated by the Webkit. To avoid the bad behavior when some site's
1284 // minimum preferred width keeps growing when changing the viewport width or
1285 // the minimum preferred width is huge, an upper limit is needed.
1286 static int sMaxViewportWidth = DEFAULT_VIEWPORT_WIDTH;
1288 // initial scale in percent. 0 means using default.
1289 private int mInitialScaleInPercent = 0;
1291 // Whether or not a scroll event should be sent to webkit. This is only set
1292 // to false when restoring the scroll position.
1293 private boolean mSendScrollEvent = true;
1295 private int mSnapScrollMode = SNAP_NONE;
1296 private static final int SNAP_NONE = 0;
1297 private static final int SNAP_LOCK = 1; // not a separate state
1298 private static final int SNAP_X = 2; // may be combined with SNAP_LOCK
1299 private static final int SNAP_Y = 4; // may be combined with SNAP_LOCK
1300 private boolean mSnapPositive;
1302 // keep these in sync with their counterparts in WebView.cpp
1303 private static final int DRAW_EXTRAS_NONE = 0;
1304 private static final int DRAW_EXTRAS_SELECTION = 1;
1305 private static final int DRAW_EXTRAS_CURSOR_RING = 2;
1307 // keep this in sync with WebCore:ScrollbarMode in WebKit
1308 private static final int SCROLLBAR_AUTO = 0;
1309 private static final int SCROLLBAR_ALWAYSOFF = 1;
1310 // as we auto fade scrollbar, this is ignored.
1311 private static final int SCROLLBAR_ALWAYSON = 2;
1312 private int mHorizontalScrollBarMode = SCROLLBAR_AUTO;
1313 private int mVerticalScrollBarMode = SCROLLBAR_AUTO;
1315 // constants for determining script injection strategy
1316 private static final int ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED = -1;
1317 private static final int ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT = 0;
1318 private static final int ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED = 1;
1320 // the alias via which accessibility JavaScript interface is exposed
1321 private static final String ALIAS_ACCESSIBILITY_JS_INTERFACE = "accessibility";
1323 // Template for JavaScript that injects a screen-reader.
1324 private static final String ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE =
1325 "javascript:(function() {" +
1326 " var chooser = document.createElement('script');" +
1327 " chooser.type = 'text/javascript';" +
1328 " chooser.src = '%1s';" +
1329 " document.getElementsByTagName('head')[0].appendChild(chooser);" +
1332 // Regular expression that matches the "axs" URL parameter.
1333 // The value of 0 means the accessibility script is opted out
1334 // The value of 1 means the accessibility script is already injected
1335 private static final String PATTERN_MATCH_AXS_URL_PARAMETER = "(\\?axs=(0|1))|(&axs=(0|1))";
1337 // TextToSpeech instance exposed to JavaScript to the injected screenreader.
1338 private TextToSpeech mTextToSpeech;
1340 // variable to cache the above pattern in case accessibility is enabled.
1341 private Pattern mMatchAxsUrlParameterPattern;
1344 * Max distance to overscroll by in pixels.
1345 * This how far content can be pulled beyond its normal bounds by the user.
1347 private int mOverscrollDistance;
1350 * Max distance to overfling by in pixels.
1351 * This is how far flinged content can move beyond the end of its normal bounds.
1353 private int mOverflingDistance;
1355 private OverScrollGlow mOverScrollGlow;
1357 // Used to match key downs and key ups
1358 private Vector<Integer> mKeysPressed;
1360 /* package */ static boolean mLogEvent = true;
1363 private long mLastTouchUpTime = 0;
1365 private WebViewCore.AutoFillData mAutoFillData;
1367 private static boolean sNotificationsEnabled = true;
1370 * URI scheme for telephone number
1372 public static final String SCHEME_TEL = "tel:";
1374 * URI scheme for email address
1376 public static final String SCHEME_MAILTO = "mailto:";
1378 * URI scheme for map address
1380 public static final String SCHEME_GEO = "geo:0,0?q=";
1382 private int mBackgroundColor = Color.WHITE;
1384 private static final long SELECT_SCROLL_INTERVAL = 1000 / 60; // 60 / second
1385 private int mAutoScrollX = 0;
1386 private int mAutoScrollY = 0;
1387 private int mMinAutoScrollX = 0;
1388 private int mMaxAutoScrollX = 0;
1389 private int mMinAutoScrollY = 0;
1390 private int mMaxAutoScrollY = 0;
1391 private Rect mScrollingLayerBounds = new Rect();
1392 private boolean mSentAutoScrollMessage = false;
1394 // used for serializing asynchronously handled touch events.
1395 private WebViewInputDispatcher mInputDispatcher;
1397 // Used to track whether picture updating was paused due to a window focus change.
1398 private boolean mPictureUpdatePausedForFocusChange = false;
1400 // Used to notify listeners of a new picture.
1401 private PictureListener mPictureListener;
1403 // Used to notify listeners about find-on-page results.
1404 private WebView.FindListener mFindListener;
1406 // Used to prevent resending save password message
1407 private Message mResumeMsg;
1410 * Refer to {@link WebView#requestFocusNodeHref(Message)} for more information
1412 static class FocusNodeHref {
1413 static final String TITLE = "title";
1414 static final String URL = "url";
1415 static final String SRC = "src";
1418 public WebViewClassic(WebView webView, WebView.PrivateAccess privateAccess) {
1420 mWebViewPrivate = privateAccess;
1421 mContext = webView.getContext();
1425 * See {@link WebViewProvider#init(Map, boolean)}
1428 public void init(Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
1429 Context context = mContext;
1431 // Used by the chrome stack to find application paths
1432 JniUtil.setContext(context);
1434 mCallbackProxy = new CallbackProxy(context, this);
1435 mViewManager = new ViewManager(this);
1436 L10nUtils.setApplicationContext(context.getApplicationContext());
1437 mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javaScriptInterfaces);
1438 mDatabase = WebViewDatabase.getInstance(context);
1439 mScroller = new OverScroller(context, null, 0, 0, false); //TODO Use OverScroller's flywheel
1440 mZoomManager = new ZoomManager(this, mCallbackProxy);
1442 /* The init method must follow the creation of certain member variables,
1443 * such as the mZoomManager.
1446 setupPackageListener(context);
1447 setupProxyListener(context);
1448 setupTrustStorageListener(context);
1449 updateMultiTouchSupport(context);
1451 if (privateBrowsing) {
1452 startPrivateBrowsing();
1455 mAutoFillData = new WebViewCore.AutoFillData();
1456 mEditTextScroller = new Scroller(context);
1459 // WebViewProvider bindings
1461 static class Factory implements WebViewFactoryProvider, WebViewFactoryProvider.Statics {
1463 public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {
1464 return new WebViewClassic(webView, privateAccess);
1468 public Statics getStatics() { return this; }
1471 public String findAddress(String addr) {
1472 return WebViewClassic.findAddress(addr);
1475 public void setPlatformNotificationsEnabled(boolean enable) {
1477 WebViewClassic.enablePlatformNotifications();
1479 WebViewClassic.disablePlatformNotifications();
1485 private void onHandleUiEvent(MotionEvent event, int eventType, int flags) {
1486 switch (eventType) {
1487 case WebViewInputDispatcher.EVENT_TYPE_LONG_PRESS:
1488 HitTestResult hitTest = getHitTestResult();
1490 && hitTest.getType() != HitTestResult.UNKNOWN_TYPE) {
1494 case WebViewInputDispatcher.EVENT_TYPE_DOUBLE_TAP:
1495 mZoomManager.handleDoubleTap(event.getX(), event.getY());
1497 case WebViewInputDispatcher.EVENT_TYPE_TOUCH:
1498 onHandleUiTouchEvent(event);
1503 private void onHandleUiTouchEvent(MotionEvent ev) {
1504 final ScaleGestureDetector detector =
1505 mZoomManager.getMultiTouchGestureDetector();
1507 float x = ev.getX();
1508 float y = ev.getY();
1510 if (detector != null) {
1511 detector.onTouchEvent(ev);
1512 if (detector.isInProgress()) {
1513 mLastTouchTime = ev.getEventTime();
1514 x = detector.getFocusX();
1515 y = detector.getFocusY();
1517 mWebView.cancelLongPress();
1518 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
1519 if (!mZoomManager.supportsPanDuringZoom()) {
1522 mTouchMode = TOUCH_DRAG_MODE;
1523 if (mVelocityTracker == null) {
1524 mVelocityTracker = VelocityTracker.obtain();
1529 int action = ev.getActionMasked();
1530 if (action == MotionEvent.ACTION_POINTER_DOWN) {
1532 action = MotionEvent.ACTION_DOWN;
1533 } else if (action == MotionEvent.ACTION_POINTER_UP && ev.getPointerCount() >= 2) {
1534 // set mLastTouchX/Y to the remaining points for multi-touch.
1535 mLastTouchX = Math.round(x);
1536 mLastTouchY = Math.round(y);
1537 } else if (action == MotionEvent.ACTION_MOVE) {
1538 // negative x or y indicate it is on the edge, skip it.
1539 if (x < 0 || y < 0) {
1544 handleTouchEventCommon(ev, action, Math.round(x), Math.round(y));
1547 // The webview that is bound to this WebViewClassic instance. Primarily needed for supplying
1548 // as the first param in the WebViewClient and WebChromeClient callbacks.
1549 final private WebView mWebView;
1550 // Callback interface, provides priviledged access into the WebView instance.
1551 final private WebView.PrivateAccess mWebViewPrivate;
1552 // Cached reference to mWebView.getContext(), for convenience.
1553 final private Context mContext;
1556 * @return The webview proxy that this classic webview is bound to.
1558 public WebView getWebView() {
1563 public ViewDelegate getViewDelegate() {
1568 public ScrollDelegate getScrollDelegate() {
1572 public static WebViewClassic fromWebView(WebView webView) {
1573 return webView == null ? null : (WebViewClassic) webView.getWebViewProvider();
1576 // Accessors, purely for convenience (and to reduce code churn during webview proxy migration).
1578 return mWebView.getScrollX();
1582 return mWebView.getScrollY();
1586 return mWebView.getWidth();
1590 return mWebView.getHeight();
1593 Context getContext() {
1598 mWebView.invalidate();
1601 // Setters for the Scroll X & Y, without invoking the onScrollChanged etc code paths.
1602 void setScrollXRaw(int mScrollX) {
1603 mWebViewPrivate.setScrollXRaw(mScrollX);
1606 void setScrollYRaw(int mScrollY) {
1607 mWebViewPrivate.setScrollYRaw(mScrollY);
1610 private static class TrustStorageListener extends BroadcastReceiver {
1612 public void onReceive(Context context, Intent intent) {
1613 if (intent.getAction().equals(KeyChain.ACTION_STORAGE_CHANGED)) {
1614 handleCertTrustChanged();
1618 private static TrustStorageListener sTrustStorageListener;
1621 * Handles update to the trust storage.
1623 private static void handleCertTrustChanged() {
1624 // send a message for indicating trust storage change
1625 WebViewCore.sendStaticMessage(EventHub.TRUST_STORAGE_UPDATED, null);
1629 * @param context This method expects this to be a valid context.
1631 private static void setupTrustStorageListener(Context context) {
1632 if (sTrustStorageListener != null ) {
1635 IntentFilter filter = new IntentFilter();
1636 filter.addAction(KeyChain.ACTION_STORAGE_CHANGED);
1637 sTrustStorageListener = new TrustStorageListener();
1639 context.getApplicationContext().registerReceiver(sTrustStorageListener, filter);
1640 if (current != null) {
1641 handleCertTrustChanged();
1645 private static class ProxyReceiver extends BroadcastReceiver {
1647 public void onReceive(Context context, Intent intent) {
1648 if (intent.getAction().equals(Proxy.PROXY_CHANGE_ACTION)) {
1649 handleProxyBroadcast(intent);
1655 * Receiver for PROXY_CHANGE_ACTION, will be null when it is not added handling broadcasts.
1657 private static ProxyReceiver sProxyReceiver;
1660 * @param context This method expects this to be a valid context
1662 private static synchronized void setupProxyListener(Context context) {
1663 if (sProxyReceiver != null || sNotificationsEnabled == false) {
1666 IntentFilter filter = new IntentFilter();
1667 filter.addAction(Proxy.PROXY_CHANGE_ACTION);
1668 sProxyReceiver = new ProxyReceiver();
1669 Intent currentProxy = context.getApplicationContext().registerReceiver(
1670 sProxyReceiver, filter);
1671 if (currentProxy != null) {
1672 handleProxyBroadcast(currentProxy);
1677 * @param context This method expects this to be a valid context
1679 private static synchronized void disableProxyListener(Context context) {
1680 if (sProxyReceiver == null)
1683 context.getApplicationContext().unregisterReceiver(sProxyReceiver);
1684 sProxyReceiver = null;
1687 private static void handleProxyBroadcast(Intent intent) {
1688 ProxyProperties proxyProperties = (ProxyProperties)intent.getExtra(Proxy.EXTRA_PROXY_INFO);
1689 if (proxyProperties == null || proxyProperties.getHost() == null) {
1690 WebViewCore.sendStaticMessage(EventHub.PROXY_CHANGED, null);
1693 WebViewCore.sendStaticMessage(EventHub.PROXY_CHANGED, proxyProperties);
1697 * A variable to track if there is a receiver added for ACTION_PACKAGE_ADDED
1698 * or ACTION_PACKAGE_REMOVED.
1700 private static boolean sPackageInstallationReceiverAdded = false;
1703 * A set of Google packages we monitor for the
1704 * navigator.isApplicationInstalled() API. Add additional packages as
1707 private static Set<String> sGoogleApps;
1709 sGoogleApps = new HashSet<String>();
1710 sGoogleApps.add("com.google.android.youtube");
1713 private static class PackageListener extends BroadcastReceiver {
1715 public void onReceive(Context context, Intent intent) {
1716 final String action = intent.getAction();
1717 final String packageName = intent.getData().getSchemeSpecificPart();
1718 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
1719 if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) {
1720 // if it is replacing, refreshPlugins() when adding
1724 if (sGoogleApps.contains(packageName)) {
1725 if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
1726 WebViewCore.sendStaticMessage(EventHub.ADD_PACKAGE_NAME, packageName);
1728 WebViewCore.sendStaticMessage(EventHub.REMOVE_PACKAGE_NAME, packageName);
1732 PluginManager pm = PluginManager.getInstance(context);
1733 if (pm.containsPluginPermissionAndSignatures(packageName)) {
1734 pm.refreshPlugins(Intent.ACTION_PACKAGE_ADDED.equals(action));
1739 private void setupPackageListener(Context context) {
1742 * we must synchronize the instance check and the creation of the
1743 * receiver to ensure that only ONE receiver exists for all WebView
1746 synchronized (WebViewClassic.class) {
1748 // if the receiver already exists then we do not need to register it
1750 if (sPackageInstallationReceiverAdded) {
1754 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
1755 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
1756 filter.addDataScheme("package");
1757 BroadcastReceiver packageListener = new PackageListener();
1758 context.getApplicationContext().registerReceiver(packageListener, filter);
1759 sPackageInstallationReceiverAdded = true;
1762 // check if any of the monitored apps are already installed
1763 AsyncTask<Void, Void, Set<String>> task = new AsyncTask<Void, Void, Set<String>>() {
1766 protected Set<String> doInBackground(Void... unused) {
1767 Set<String> installedPackages = new HashSet<String>();
1768 PackageManager pm = mContext.getPackageManager();
1769 for (String name : sGoogleApps) {
1771 pm.getPackageInfo(name,
1772 PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
1773 installedPackages.add(name);
1774 } catch (PackageManager.NameNotFoundException e) {
1775 // package not found
1778 return installedPackages;
1781 // Executes on the UI thread
1783 protected void onPostExecute(Set<String> installedPackages) {
1784 if (mWebViewCore != null) {
1785 mWebViewCore.sendMessage(EventHub.ADD_PACKAGE_NAMES, installedPackages);
1792 void updateMultiTouchSupport(Context context) {
1793 mZoomManager.updateMultiTouchSupport(context);
1796 private void init() {
1797 OnTrimMemoryListener.init(mContext);
1798 mWebView.setWillNotDraw(false);
1799 mWebView.setFocusable(true);
1800 mWebView.setFocusableInTouchMode(true);
1801 mWebView.setClickable(true);
1802 mWebView.setLongClickable(true);
1804 final ViewConfiguration configuration = ViewConfiguration.get(mContext);
1805 int slop = configuration.getScaledTouchSlop();
1806 mTouchSlopSquare = slop * slop;
1807 slop = configuration.getScaledDoubleTapSlop();
1808 mDoubleTapSlopSquare = slop * slop;
1809 final float density = mContext.getResources().getDisplayMetrics().density;
1810 // use one line height, 16 based on our current default font, for how
1811 // far we allow a touch be away from the edge of a link
1812 mNavSlop = (int) (16 * density);
1813 mZoomManager.init(density);
1814 mMaximumFling = configuration.getScaledMaximumFlingVelocity();
1816 // Compute the inverse of the density squared.
1817 DRAG_LAYER_INVERSE_DENSITY_SQUARED = 1 / (density * density);
1819 mOverscrollDistance = configuration.getScaledOverscrollDistance();
1820 mOverflingDistance = configuration.getScaledOverflingDistance();
1822 setScrollBarStyle(mWebViewPrivate.super_getScrollBarStyle());
1823 // Initially use a size of two, since the user is likely to only hold
1824 // down two keys at a time (shift + another key)
1825 mKeysPressed = new Vector<Integer>(2);
1826 mHTML5VideoViewProxy = null ;
1830 public boolean shouldDelayChildPressedState() {
1835 * Adds accessibility APIs to JavaScript.
1837 * Note: This method is responsible to performing the necessary
1838 * check if the accessibility APIs should be exposed.
1840 private void addAccessibilityApisToJavaScript() {
1841 if (AccessibilityManager.getInstance(mContext).isEnabled()
1842 && getSettings().getJavaScriptEnabled()) {
1843 // exposing the TTS for now ...
1844 final Context ctx = mContext;
1846 final String packageName = ctx.getPackageName();
1847 if (packageName != null) {
1848 mTextToSpeech = new TextToSpeech(ctx, null, null,
1849 packageName + ".**webview**", true);
1850 addJavascriptInterface(mTextToSpeech, ALIAS_ACCESSIBILITY_JS_INTERFACE);
1857 * Removes accessibility APIs from JavaScript.
1859 private void removeAccessibilityApisFromJavaScript() {
1860 // exposing the TTS for now ...
1861 if (mTextToSpeech != null) {
1862 removeJavascriptInterface(ALIAS_ACCESSIBILITY_JS_INTERFACE);
1863 mTextToSpeech.shutdown();
1864 mTextToSpeech = null;
1869 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1870 info.setScrollable(isScrollableForAccessibility());
1874 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1875 event.setScrollable(isScrollableForAccessibility());
1876 event.setScrollX(getScrollX());
1877 event.setScrollY(getScrollY());
1878 final int convertedContentWidth = contentToViewX(getContentWidth());
1879 final int adjustedViewWidth = getWidth() - mWebView.getPaddingLeft()
1880 - mWebView.getPaddingLeft();
1881 event.setMaxScrollX(Math.max(convertedContentWidth - adjustedViewWidth, 0));
1882 final int convertedContentHeight = contentToViewY(getContentHeight());
1883 final int adjustedViewHeight = getHeight() - mWebView.getPaddingTop()
1884 - mWebView.getPaddingBottom();
1885 event.setMaxScrollY(Math.max(convertedContentHeight - adjustedViewHeight, 0));
1888 private boolean isScrollableForAccessibility() {
1889 return (contentToViewX(getContentWidth()) > getWidth() - mWebView.getPaddingLeft()
1890 - mWebView.getPaddingRight()
1891 || contentToViewY(getContentHeight()) > getHeight() - mWebView.getPaddingTop()
1892 - mWebView.getPaddingBottom());
1896 public void setOverScrollMode(int mode) {
1897 if (mode != View.OVER_SCROLL_NEVER) {
1898 if (mOverScrollGlow == null) {
1899 mOverScrollGlow = new OverScrollGlow(this);
1902 mOverScrollGlow = null;
1906 /* package */ void adjustDefaultZoomDensity(int zoomDensity) {
1907 final float density = mContext.getResources().getDisplayMetrics().density
1908 * 100 / zoomDensity;
1909 updateDefaultZoomDensity(density);
1912 /* package */ void updateDefaultZoomDensity(float density) {
1913 mNavSlop = (int) (16 * density);
1914 mZoomManager.updateDefaultZoomDensity(density);
1917 /* package */ boolean onSavePassword(String schemePlusHost, String username,
1918 String password, final Message resumeMsg) {
1919 boolean rVal = false;
1920 if (resumeMsg == null) {
1921 // null resumeMsg implies saving password silently
1922 mDatabase.setUsernamePassword(schemePlusHost, username, password);
1924 if (mResumeMsg != null) {
1925 Log.w(LOGTAG, "onSavePassword should not be called while dialog is up");
1926 resumeMsg.sendToTarget();
1929 mResumeMsg = resumeMsg;
1930 final Message remember = mPrivateHandler.obtainMessage(
1932 remember.getData().putString("host", schemePlusHost);
1933 remember.getData().putString("username", username);
1934 remember.getData().putString("password", password);
1935 remember.obj = resumeMsg;
1937 final Message neverRemember = mPrivateHandler.obtainMessage(
1938 NEVER_REMEMBER_PASSWORD);
1939 neverRemember.getData().putString("host", schemePlusHost);
1940 neverRemember.getData().putString("username", username);
1941 neverRemember.getData().putString("password", password);
1942 neverRemember.obj = resumeMsg;
1944 new AlertDialog.Builder(mContext)
1945 .setTitle(com.android.internal.R.string.save_password_label)
1946 .setMessage(com.android.internal.R.string.save_password_message)
1947 .setPositiveButton(com.android.internal.R.string.save_password_notnow,
1948 new DialogInterface.OnClickListener() {
1950 public void onClick(DialogInterface dialog, int which) {
1951 if (mResumeMsg != null) {
1952 resumeMsg.sendToTarget();
1957 .setNeutralButton(com.android.internal.R.string.save_password_remember,
1958 new DialogInterface.OnClickListener() {
1960 public void onClick(DialogInterface dialog, int which) {
1961 if (mResumeMsg != null) {
1962 remember.sendToTarget();
1967 .setNegativeButton(com.android.internal.R.string.save_password_never,
1968 new DialogInterface.OnClickListener() {
1970 public void onClick(DialogInterface dialog, int which) {
1971 if (mResumeMsg != null) {
1972 neverRemember.sendToTarget();
1977 .setOnCancelListener(new OnCancelListener() {
1979 public void onCancel(DialogInterface dialog) {
1980 if (mResumeMsg != null) {
1981 resumeMsg.sendToTarget();
1986 // Return true so that WebViewCore will pause while the dialog is
1994 public void setScrollBarStyle(int style) {
1995 if (style == View.SCROLLBARS_INSIDE_INSET
1996 || style == View.SCROLLBARS_OUTSIDE_INSET) {
1997 mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = false;
1999 mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = true;
2004 * See {@link WebView#setHorizontalScrollbarOverlay(boolean)}
2007 public void setHorizontalScrollbarOverlay(boolean overlay) {
2008 mOverlayHorizontalScrollbar = overlay;
2012 * See {@link WebView#setVerticalScrollbarOverlay(boolean)
2015 public void setVerticalScrollbarOverlay(boolean overlay) {
2016 mOverlayVerticalScrollbar = overlay;
2020 * See {@link WebView#overlayHorizontalScrollbar()}
2023 public boolean overlayHorizontalScrollbar() {
2024 return mOverlayHorizontalScrollbar;
2028 * See {@link WebView#overlayVerticalScrollbar()}
2031 public boolean overlayVerticalScrollbar() {
2032 return mOverlayVerticalScrollbar;
2036 * Return the width of the view where the content of WebView should render
2038 * Note: this can be called from WebCoreThread.
2040 /* package */ int getViewWidth() {
2041 if (!mWebView.isVerticalScrollBarEnabled() || mOverlayVerticalScrollbar) {
2044 return Math.max(0, getWidth() - mWebView.getVerticalScrollbarWidth());
2048 // Interface to enable the browser to override title bar handling.
2049 public interface TitleBarDelegate {
2050 int getTitleHeight();
2051 public void onSetEmbeddedTitleBar(final View title);
2055 * Returns the height (in pixels) of the embedded title bar (if any). Does not care about
2058 protected int getTitleHeight() {
2059 if (mWebView instanceof TitleBarDelegate) {
2060 return ((TitleBarDelegate) mWebView).getTitleHeight();
2066 * See {@link WebView#getVisibleTitleHeight()}
2070 public int getVisibleTitleHeight() {
2071 // Actually, this method returns the height of the embedded title bar if one is set via the
2072 // hidden setEmbeddedTitleBar method.
2073 return getVisibleTitleHeightImpl();
2076 private int getVisibleTitleHeightImpl() {
2077 // need to restrict mScrollY due to over scroll
2078 return Math.max(getTitleHeight() - Math.max(0, getScrollY()),
2079 getOverlappingActionModeHeight());
2082 private int mCachedOverlappingActionModeHeight = -1;
2084 private int getOverlappingActionModeHeight() {
2085 if (mFindCallback == null) {
2088 if (mCachedOverlappingActionModeHeight < 0) {
2089 mWebView.getGlobalVisibleRect(mGlobalVisibleRect, mGlobalVisibleOffset);
2090 mCachedOverlappingActionModeHeight = Math.max(0,
2091 mFindCallback.getActionModeGlobalBottom() - mGlobalVisibleRect.top);
2093 return mCachedOverlappingActionModeHeight;
2097 * Return the height of the view where the content of WebView should render
2098 * to. Note that this excludes mTitleBar, if there is one.
2099 * Note: this can be called from WebCoreThread.
2101 /* package */ int getViewHeight() {
2102 return getViewHeightWithTitle() - getVisibleTitleHeightImpl();
2105 int getViewHeightWithTitle() {
2106 int height = getHeight();
2107 if (mWebView.isHorizontalScrollBarEnabled() && !mOverlayHorizontalScrollbar) {
2108 height -= mWebViewPrivate.getHorizontalScrollbarHeight();
2114 * See {@link WebView#getCertificate()}
2117 public SslCertificate getCertificate() {
2118 return mCertificate;
2122 * See {@link WebView#setCertificate(SslCertificate)}
2125 public void setCertificate(SslCertificate certificate) {
2126 if (DebugFlags.WEB_VIEW) {
2127 Log.v(LOGTAG, "setCertificate=" + certificate);
2129 // here, the certificate can be null (if the site is not secure)
2130 mCertificate = certificate;
2133 //-------------------------------------------------------------------------
2134 // Methods called by activity
2135 //-------------------------------------------------------------------------
2138 * See {@link WebView#savePassword(String, String, String)}
2141 public void savePassword(String host, String username, String password) {
2142 mDatabase.setUsernamePassword(host, username, password);
2146 * See {@link WebView#setHttpAuthUsernamePassword(String, String, String, String)}
2149 public void setHttpAuthUsernamePassword(String host, String realm,
2150 String username, String password) {
2151 mDatabase.setHttpAuthUsernamePassword(host, realm, username, password);
2155 * See {@link WebView#getHttpAuthUsernamePassword(String, String)}
2158 public String[] getHttpAuthUsernamePassword(String host, String realm) {
2159 return mDatabase.getHttpAuthUsernamePassword(host, realm);
2163 * Remove Find or Select ActionModes, if active.
2165 private void clearActionModes() {
2166 if (mSelectCallback != null) {
2167 mSelectCallback.finish();
2169 if (mFindCallback != null) {
2170 mFindCallback.finish();
2175 * Called to clear state when moving from one page to another, or changing
2176 * in some other way that makes elements associated with the current page
2177 * (such as ActionModes) no longer relevant.
2179 private void clearHelpers() {
2182 dismissFullScreenMode();
2183 cancelSelectDialog();
2186 private void cancelSelectDialog() {
2187 if (mListBoxDialog != null) {
2188 mListBoxDialog.cancel();
2189 mListBoxDialog = null;
2194 * See {@link WebView#destroy()}
2197 public void destroy() {
2201 private void destroyImpl() {
2202 mCallbackProxy.blockMessages();
2204 if (mListBoxDialog != null) {
2205 mListBoxDialog.dismiss();
2206 mListBoxDialog = null;
2208 if (mNativeClass != 0) nativeStopGL();
2209 if (mWebViewCore != null) {
2210 // Tell WebViewCore to destroy itself
2211 synchronized (this) {
2212 WebViewCore webViewCore = mWebViewCore;
2213 mWebViewCore = null; // prevent using partial webViewCore
2214 webViewCore.destroy();
2216 // Remove any pending messages that might not be serviced yet.
2217 mPrivateHandler.removeCallbacksAndMessages(null);
2219 if (mNativeClass != 0) {
2226 * See {@link WebView#enablePlatformNotifications()}
2229 public static void enablePlatformNotifications() {
2230 synchronized (WebViewClassic.class) {
2231 sNotificationsEnabled = true;
2232 Context context = JniUtil.getContext();
2233 if (context != null)
2234 setupProxyListener(context);
2239 * See {@link WebView#disablePlatformNotifications()}
2242 public static void disablePlatformNotifications() {
2243 synchronized (WebViewClassic.class) {
2244 sNotificationsEnabled = false;
2245 Context context = JniUtil.getContext();
2246 if (context != null)
2247 disableProxyListener(context);
2252 * Sets JavaScript engine flags.
2254 * @param flags JS engine flags in a String
2256 * This is an implementation detail.
2258 public void setJsFlags(String flags) {
2259 mWebViewCore.sendMessage(EventHub.SET_JS_FLAGS, flags);
2263 * See {@link WebView#setNetworkAvailable(boolean)}
2266 public void setNetworkAvailable(boolean networkUp) {
2267 mWebViewCore.sendMessage(EventHub.SET_NETWORK_STATE,
2268 networkUp ? 1 : 0, 0);
2272 * Inform WebView about the current network type.
2274 public void setNetworkType(String type, String subtype) {
2275 Map<String, String> map = new HashMap<String, String>();
2276 map.put("type", type);
2277 map.put("subtype", subtype);
2278 mWebViewCore.sendMessage(EventHub.SET_NETWORK_TYPE, map);
2282 * See {@link WebView#saveState(Bundle)}
2285 public WebBackForwardList saveState(Bundle outState) {
2286 if (outState == null) {
2289 // We grab a copy of the back/forward list because a client of WebView
2290 // may have invalidated the history list by calling clearHistory.
2291 WebBackForwardList list = copyBackForwardList();
2292 final int currentIndex = list.getCurrentIndex();
2293 final int size = list.getSize();
2294 // We should fail saving the state if the list is empty or the index is
2295 // not in a valid range.
2296 if (currentIndex < 0 || currentIndex >= size || size == 0) {
2299 outState.putInt("index", currentIndex);
2300 // FIXME: This should just be a byte[][] instead of ArrayList but
2301 // Parcel.java does not have the code to handle multi-dimensional
2303 ArrayList<byte[]> history = new ArrayList<byte[]>(size);
2304 for (int i = 0; i < size; i++) {
2305 WebHistoryItem item = list.getItemAtIndex(i);
2307 // FIXME: this shouldn't happen
2308 // need to determine how item got set to null
2309 Log.w(LOGTAG, "saveState: Unexpected null history item.");
2312 byte[] data = item.getFlattenedData();
2314 // It would be very odd to not have any data for a given history
2315 // item. And we will fail to rebuild the history list without
2321 outState.putSerializable("history", history);
2322 if (mCertificate != null) {
2323 outState.putBundle("certificate",
2324 SslCertificate.saveState(mCertificate));
2326 outState.putBoolean("privateBrowsingEnabled", isPrivateBrowsingEnabled());
2327 mZoomManager.saveZoomState(outState);
2332 * See {@link WebView#savePicture(Bundle, File)}
2336 public boolean savePicture(Bundle b, final File dest) {
2337 if (dest == null || b == null) {
2340 final Picture p = capturePicture();
2341 // Use a temporary file while writing to ensure the destination file
2342 // contains valid data.
2343 final File temp = new File(dest.getPath() + ".writing");
2344 new Thread(new Runnable() {
2347 FileOutputStream out = null;
2349 out = new FileOutputStream(temp);
2350 p.writeToStream(out);
2351 // Writing the picture succeeded, rename the temporary file
2352 // to the destination.
2353 temp.renameTo(dest);
2354 } catch (Exception e) {
2355 // too late to do anything about it.
2360 } catch (Exception e) {
2361 // Can't do anything about that
2368 // now update the bundle
2369 b.putInt("scrollX", getScrollX());
2370 b.putInt("scrollY", getScrollY());
2371 mZoomManager.saveZoomState(b);
2375 private void restoreHistoryPictureFields(Picture p, Bundle b) {
2376 int sx = b.getInt("scrollX", 0);
2377 int sy = b.getInt("scrollY", 0);
2379 mDrawHistory = true;
2380 mHistoryPicture = p;
2384 mZoomManager.restoreZoomState(b);
2385 final float scale = mZoomManager.getScale();
2386 mHistoryWidth = Math.round(p.getWidth() * scale);
2387 mHistoryHeight = Math.round(p.getHeight() * scale);
2393 * See {@link WebView#restorePicture(Bundle, File)};
2397 public boolean restorePicture(Bundle b, File src) {
2398 if (src == null || b == null) {
2401 if (!src.exists()) {
2405 final FileInputStream in = new FileInputStream(src);
2406 final Bundle copy = new Bundle(b);
2407 new Thread(new Runnable() {
2411 final Picture p = Picture.createFromStream(in);
2413 // Post a runnable on the main thread to update the
2414 // history picture fields.
2415 mPrivateHandler.post(new Runnable() {
2418 restoreHistoryPictureFields(p, copy);
2425 } catch (Exception e) {
2426 // Nothing we can do now.
2431 } catch (FileNotFoundException e){
2432 e.printStackTrace();
2438 * Saves the view data to the output stream. The output is highly
2439 * version specific, and may not be able to be loaded by newer versions
2441 * @param stream The {@link OutputStream} to save to
2442 * @return True if saved successfully
2444 public boolean saveViewState(OutputStream stream) {
2446 return ViewStateSerializer.serializeViewState(stream, this);
2447 } catch (IOException e) {
2448 Log.w(LOGTAG, "Failed to saveViewState", e);
2454 * Loads the view data from the input stream. See
2455 * {@link #saveViewState(OutputStream)} for more information.
2456 * @param stream The {@link InputStream} to load from
2457 * @return True if loaded successfully
2459 public boolean loadViewState(InputStream stream) {
2461 mLoadedPicture = ViewStateSerializer.deserializeViewState(stream, this);
2462 mBlockWebkitViewMessages = true;
2463 setNewPicture(mLoadedPicture, true);
2464 mLoadedPicture.mViewState = null;
2466 } catch (IOException e) {
2467 Log.w(LOGTAG, "Failed to loadViewState", e);
2473 * Clears the view state set with {@link #loadViewState(InputStream)}.
2474 * This WebView will then switch to showing the content from webkit
2476 public void clearViewState() {
2477 mBlockWebkitViewMessages = false;
2478 mLoadedPicture = null;
2483 * See {@link WebView#restoreState(Bundle)}
2486 public WebBackForwardList restoreState(Bundle inState) {
2487 WebBackForwardList returnList = null;
2488 if (inState == null) {
2491 if (inState.containsKey("index") && inState.containsKey("history")) {
2492 mCertificate = SslCertificate.restoreState(
2493 inState.getBundle("certificate"));
2495 final WebBackForwardList list = mCallbackProxy.getBackForwardList();
2496 final int index = inState.getInt("index");
2497 // We can't use a clone of the list because we need to modify the
2498 // shared copy, so synchronize instead to prevent concurrent
2500 synchronized (list) {
2501 final List<byte[]> history =
2502 (List<byte[]>) inState.getSerializable("history");
2503 final int size = history.size();
2504 // Check the index bounds so we don't crash in native code while
2505 // restoring the history index.
2506 if (index < 0 || index >= size) {
2509 for (int i = 0; i < size; i++) {
2510 byte[] data = history.remove(0);
2512 // If we somehow have null data, we cannot reconstruct
2513 // the item and thus our history list cannot be rebuilt.
2516 WebHistoryItem item = new WebHistoryItem(data);
2517 list.addHistoryItem(item);
2519 // Grab the most recent copy to return to the caller.
2520 returnList = copyBackForwardList();
2521 // Update the copy to have the correct index.
2522 returnList.setCurrentIndex(index);
2524 // Restore private browsing setting.
2525 if (inState.getBoolean("privateBrowsingEnabled")) {
2526 getSettings().setPrivateBrowsingEnabled(true);
2528 mZoomManager.restoreZoomState(inState);
2529 // Remove all pending messages because we are restoring previous
2531 mWebViewCore.removeMessages();
2532 // Send a restore state message.
2533 mWebViewCore.sendMessage(EventHub.RESTORE_STATE, index);
2539 * See {@link WebView#loadUrl(String, Map)}
2542 public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
2543 loadUrlImpl(url, additionalHttpHeaders);
2546 private void loadUrlImpl(String url, Map<String, String> extraHeaders) {
2547 switchOutDrawHistory();
2548 WebViewCore.GetUrlData arg = new WebViewCore.GetUrlData();
2550 arg.mExtraHeaders = extraHeaders;
2551 mWebViewCore.sendMessage(EventHub.LOAD_URL, arg);
2556 * See {@link WebView#loadUrl(String)}
2559 public void loadUrl(String url) {
2563 private void loadUrlImpl(String url) {
2567 loadUrlImpl(url, null);
2571 * See {@link WebView#postUrl(String, byte[])}
2574 public void postUrl(String url, byte[] postData) {
2575 if (URLUtil.isNetworkUrl(url)) {
2576 switchOutDrawHistory();
2577 WebViewCore.PostUrlData arg = new WebViewCore.PostUrlData();
2579 arg.mPostData = postData;
2580 mWebViewCore.sendMessage(EventHub.POST_URL, arg);
2588 * See {@link WebView#loadData(String, String, String)}
2591 public void loadData(String data, String mimeType, String encoding) {
2592 loadDataImpl(data, mimeType, encoding);
2595 private void loadDataImpl(String data, String mimeType, String encoding) {
2596 StringBuilder dataUrl = new StringBuilder("data:");
2597 dataUrl.append(mimeType);
2598 if ("base64".equals(encoding)) {
2599 dataUrl.append(";base64");
2601 dataUrl.append(",");
2602 dataUrl.append(data);
2603 loadUrlImpl(dataUrl.toString());
2607 * See {@link WebView#loadDataWithBaseURL(String, String, String, String, String)}
2610 public void loadDataWithBaseURL(String baseUrl, String data,
2611 String mimeType, String encoding, String historyUrl) {
2613 if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) {
2614 loadDataImpl(data, mimeType, encoding);
2617 switchOutDrawHistory();
2618 WebViewCore.BaseUrlData arg = new WebViewCore.BaseUrlData();
2619 arg.mBaseUrl = baseUrl;
2621 arg.mMimeType = mimeType;
2622 arg.mEncoding = encoding;
2623 arg.mHistoryUrl = historyUrl;
2624 mWebViewCore.sendMessage(EventHub.LOAD_DATA, arg);
2629 * See {@link WebView#saveWebArchive(String)}
2632 public void saveWebArchive(String filename) {
2633 saveWebArchiveImpl(filename, false, null);
2636 /* package */ static class SaveWebArchiveMessage {
2637 SaveWebArchiveMessage (String basename, boolean autoname, ValueCallback<String> callback) {
2638 mBasename = basename;
2639 mAutoname = autoname;
2640 mCallback = callback;
2643 /* package */ final String mBasename;
2644 /* package */ final boolean mAutoname;
2645 /* package */ final ValueCallback<String> mCallback;
2646 /* package */ String mResultFile;
2650 * See {@link WebView#saveWebArchive(String, boolean, ValueCallback)}
2653 public void saveWebArchive(String basename, boolean autoname, ValueCallback<String> callback) {
2654 saveWebArchiveImpl(basename, autoname, callback);
2657 private void saveWebArchiveImpl(String basename, boolean autoname,
2658 ValueCallback<String> callback) {
2659 mWebViewCore.sendMessage(EventHub.SAVE_WEBARCHIVE,
2660 new SaveWebArchiveMessage(basename, autoname, callback));
2664 * See {@link WebView#stopLoading()}
2667 public void stopLoading() {
2668 // TODO: should we clear all the messages in the queue before sending
2670 switchOutDrawHistory();
2671 mWebViewCore.sendMessage(EventHub.STOP_LOADING);
2675 * See {@link WebView#reload()}
2678 public void reload() {
2680 switchOutDrawHistory();
2681 mWebViewCore.sendMessage(EventHub.RELOAD);
2685 * See {@link WebView#canGoBack()}
2688 public boolean canGoBack() {
2689 WebBackForwardList l = mCallbackProxy.getBackForwardList();
2691 if (l.getClearPending()) {
2694 return l.getCurrentIndex() > 0;
2700 * See {@link WebView#goBack()}
2703 public void goBack() {
2704 goBackOrForwardImpl(-1);
2708 * See {@link WebView#canGoForward()}
2711 public boolean canGoForward() {
2712 WebBackForwardList l = mCallbackProxy.getBackForwardList();
2714 if (l.getClearPending()) {
2717 return l.getCurrentIndex() < l.getSize() - 1;
2723 * See {@link WebView#goForward()}
2726 public void goForward() {
2727 goBackOrForwardImpl(1);
2731 * See {@link WebView#canGoBackOrForward(int)}
2734 public boolean canGoBackOrForward(int steps) {
2735 WebBackForwardList l = mCallbackProxy.getBackForwardList();
2737 if (l.getClearPending()) {
2740 int newIndex = l.getCurrentIndex() + steps;
2741 return newIndex >= 0 && newIndex < l.getSize();
2747 * See {@link WebView#goBackOrForward(int)}
2750 public void goBackOrForward(int steps) {
2751 goBackOrForwardImpl(steps);
2754 private void goBackOrForwardImpl(int steps) {
2755 goBackOrForward(steps, false);
2758 private void goBackOrForward(int steps, boolean ignoreSnapshot) {
2761 mWebViewCore.sendMessage(EventHub.GO_BACK_FORWARD, steps,
2762 ignoreSnapshot ? 1 : 0);
2767 * See {@link WebView#isPrivateBrowsingEnabled()}
2770 public boolean isPrivateBrowsingEnabled() {
2771 return getSettings().isPrivateBrowsingEnabled();
2774 private void startPrivateBrowsing() {
2775 getSettings().setPrivateBrowsingEnabled(true);
2778 private boolean extendScroll(int y) {
2779 int finalY = mScroller.getFinalY();
2780 int newY = pinLocY(finalY + y);
2781 if (newY == finalY) return false;
2782 mScroller.setFinalY(newY);
2783 mScroller.extendDuration(computeDuration(0, y));
2788 * See {@link WebView#pageUp(boolean)}
2791 public boolean pageUp(boolean top) {
2792 if (mNativeClass == 0) {
2796 // go to the top of the document
2797 return pinScrollTo(getScrollX(), 0, true, 0);
2800 int h = getHeight();
2802 if (h > 2 * PAGE_SCROLL_OVERLAP) {
2803 y = -h + PAGE_SCROLL_OVERLAP;
2807 return mScroller.isFinished() ? pinScrollBy(0, y, true, 0)
2812 * See {@link WebView#pageDown(boolean)}
2815 public boolean pageDown(boolean bottom) {
2816 if (mNativeClass == 0) {
2820 return pinScrollTo(getScrollX(), computeRealVerticalScrollRange(), true, 0);
2823 int h = getHeight();
2825 if (h > 2 * PAGE_SCROLL_OVERLAP) {
2826 y = h - PAGE_SCROLL_OVERLAP;
2830 return mScroller.isFinished() ? pinScrollBy(0, y, true, 0)
2835 * See {@link WebView#clearView()}
2838 public void clearView() {
2841 setBaseLayer(0, null, false, false);
2842 mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT);
2846 * See {@link WebView#capturePicture()}
2849 public Picture capturePicture() {
2850 if (mNativeClass == 0) return null;
2851 Picture result = new Picture();
2852 nativeCopyBaseContentToPicture(result);
2857 * See {@link WebView#getScale()}
2860 public float getScale() {
2861 return mZoomManager.getScale();
2865 * Compute the reading level scale of the WebView
2866 * @param scale The current scale.
2867 * @return The reading level scale.
2869 /*package*/ float computeReadingLevelScale(float scale) {
2870 return mZoomManager.computeReadingLevelScale(scale);
2874 * See {@link WebView#setInitialScale(int)}
2877 public void setInitialScale(int scaleInPercent) {
2878 mZoomManager.setInitialScaleInPercent(scaleInPercent);
2882 * See {@link WebView#invokeZoomPicker()}
2885 public void invokeZoomPicker() {
2886 if (!getSettings().supportZoom()) {
2887 Log.w(LOGTAG, "This WebView doesn't support zoom.");
2891 mZoomManager.invokeZoomPicker();
2895 * See {@link WebView#getHitTestResult()}
2898 public HitTestResult getHitTestResult() {
2899 return mInitialHitTestResult;
2902 // No left edge for double-tap zoom alignment
2903 static final int NO_LEFTEDGE = -1;
2905 int getBlockLeftEdge(int x, int y, float readingScale) {
2906 float invReadingScale = 1.0f / readingScale;
2907 int readingWidth = (int) (getViewWidth() * invReadingScale);
2908 int left = NO_LEFTEDGE;
2909 if (mFocusedNode != null) {
2910 final int length = mFocusedNode.mEnclosingParentRects.length;
2911 for (int i = 0; i < length; i++) {
2912 Rect rect = mFocusedNode.mEnclosingParentRects[i];
2913 if (rect.width() < mFocusedNode.mHitTestSlop) {
2914 // ignore bounding boxes that are too small
2916 } else if (rect.width() > readingWidth) {
2917 // stop when bounding box doesn't fit the screen width
2930 * See {@link WebView#requestFocusNodeHref(Message)}
2933 public void requestFocusNodeHref(Message hrefMsg) {
2934 if (hrefMsg == null) {
2937 int contentX = viewToContentX(mLastTouchX + getScrollX());
2938 int contentY = viewToContentY(mLastTouchY + getScrollY());
2939 if (mFocusedNode != null && mFocusedNode.mHitTestX == contentX
2940 && mFocusedNode.mHitTestY == contentY) {
2941 hrefMsg.getData().putString(FocusNodeHref.URL, mFocusedNode.mLinkUrl);
2942 hrefMsg.getData().putString(FocusNodeHref.TITLE, mFocusedNode.mAnchorText);
2943 hrefMsg.getData().putString(FocusNodeHref.SRC, mFocusedNode.mImageUrl);
2944 hrefMsg.sendToTarget();
2947 mWebViewCore.sendMessage(EventHub.REQUEST_CURSOR_HREF,
2948 contentX, contentY, hrefMsg);
2952 * See {@link WebView#requestImageRef(Message)}
2955 public void requestImageRef(Message msg) {
2956 if (0 == mNativeClass) return; // client isn't initialized
2957 String url = mFocusedNode != null ? mFocusedNode.mImageUrl : null;
2958 Bundle data = msg.getData();
2959 data.putString("url", url);
2964 static int pinLoc(int x, int viewMax, int docMax) {
2965 // Log.d(LOGTAG, "-- pinLoc " + x + " " + viewMax + " " + docMax);
2966 if (docMax < viewMax) { // the doc has room on the sides for "blank"
2967 // pin the short document to the top/left of the screen
2969 // Log.d(LOGTAG, "--- center " + x);
2972 // Log.d(LOGTAG, "--- zero");
2973 } else if (x + viewMax > docMax) {
2974 x = docMax - viewMax;
2975 // Log.d(LOGTAG, "--- pin " + x);
2980 // Expects x in view coordinates
2981 int pinLocX(int x) {
2982 if (mInOverScrollMode) return x;
2983 return pinLoc(x, getViewWidth(), computeRealHorizontalScrollRange());
2986 // Expects y in view coordinates
2987 int pinLocY(int y) {
2988 if (mInOverScrollMode) return y;
2989 return pinLoc(y, getViewHeightWithTitle(),
2990 computeRealVerticalScrollRange() + getTitleHeight());
2994 * Given a distance in view space, convert it to content space. Note: this
2995 * does not reflect translation, just scaling, so this should not be called
2996 * with coordinates, but should be called for dimensions like width or
2999 private int viewToContentDimension(int d) {
3000 return Math.round(d * mZoomManager.getInvScale());
3004 * Given an x coordinate in view space, convert it to content space. Also
3005 * may be used for absolute heights.
3007 /*package*/ int viewToContentX(int x) {
3008 return viewToContentDimension(x);
3012 * Given a y coordinate in view space, convert it to content space.
3013 * Takes into account the height of the title bar if there is one
3014 * embedded into the WebView.
3016 /*package*/ int viewToContentY(int y) {
3017 return viewToContentDimension(y - getTitleHeight());
3021 * Given a x coordinate in view space, convert it to content space.
3022 * Returns the result as a float.
3024 private float viewToContentXf(int x) {
3025 return x * mZoomManager.getInvScale();
3029 * Given a y coordinate in view space, convert it to content space.
3030 * Takes into account the height of the title bar if there is one
3031 * embedded into the WebView. Returns the result as a float.
3033 private float viewToContentYf(int y) {
3034 return (y - getTitleHeight()) * mZoomManager.getInvScale();
3038 * Given a distance in content space, convert it to view space. Note: this
3039 * does not reflect translation, just scaling, so this should not be called
3040 * with coordinates, but should be called for dimensions like width or
3043 /*package*/ int contentToViewDimension(int d) {
3044 return Math.round(d * mZoomManager.getScale());
3048 * Given an x coordinate in content space, convert it to view
3051 /*package*/ int contentToViewX(int x) {
3052 return contentToViewDimension(x);
3056 * Given a y coordinate in content space, convert it to view
3057 * space. Takes into account the height of the title bar.
3059 /*package*/ int contentToViewY(int y) {
3060 return contentToViewDimension(y) + getTitleHeight();
3063 private Rect contentToViewRect(Rect x) {
3064 return new Rect(contentToViewX(x.left), contentToViewY(x.top),
3065 contentToViewX(x.right), contentToViewY(x.bottom));
3068 /* To invalidate a rectangle in content coordinates, we need to transform
3069 the rect into view coordinates, so we can then call invalidate(...).
3071 Normally, we would just call contentToView[XY](...), which eventually
3072 calls Math.round(coordinate * mActualScale). However, for invalidates,
3073 we need to account for the slop that occurs with antialiasing. To
3074 address that, we are a little more liberal in the size of the rect that
3077 This liberal calculation calls floor() for the top/left, and ceil() for
3078 the bottom/right coordinates. This catches the possible extra pixels of
3079 antialiasing that we might have missed with just round().
3082 // Called by JNI to invalidate the View, given rectangle coordinates in
3084 private void viewInvalidate(int l, int t, int r, int b) {
3085 final float scale = mZoomManager.getScale();
3086 final int dy = getTitleHeight();
3087 mWebView.invalidate((int)Math.floor(l * scale),
3088 (int)Math.floor(t * scale) + dy,
3089 (int)Math.ceil(r * scale),
3090 (int)Math.ceil(b * scale) + dy);
3093 // Called by JNI to invalidate the View after a delay, given rectangle
3094 // coordinates in content space
3095 private void viewInvalidateDelayed(long delay, int l, int t, int r, int b) {
3096 final float scale = mZoomManager.getScale();
3097 final int dy = getTitleHeight();
3098 mWebView.postInvalidateDelayed(delay,
3099 (int)Math.floor(l * scale),
3100 (int)Math.floor(t * scale) + dy,
3101 (int)Math.ceil(r * scale),
3102 (int)Math.ceil(b * scale) + dy);
3105 private void invalidateContentRect(Rect r) {
3106 viewInvalidate(r.left, r.top, r.right, r.bottom);
3109 // stop the scroll animation, and don't let a subsequent fling add
3110 // to the existing velocity
3111 private void abortAnimation() {
3112 mScroller.abortAnimation();
3116 /* call from webcoreview.draw(), so we're still executing in the UI thread
3118 private void recordNewContentSize(int w, int h, boolean updateLayout) {
3120 // premature data from webkit, ignore
3125 // don't abort a scroll animation if we didn't change anything
3126 if (mContentWidth != w || mContentHeight != h) {
3127 // record new dimensions
3130 // If history Picture is drawn, don't update scroll. They will be
3131 // updated when we get out of that mode.
3132 if (!mDrawHistory) {
3133 // repin our scroll, taking into account the new content size
3134 updateScrollCoordinates(pinLocX(getScrollX()), pinLocY(getScrollY()));
3135 if (!mScroller.isFinished()) {
3136 // We are in the middle of a scroll. Repin the final scroll
3138 mScroller.setFinalX(pinLocX(mScroller.getFinalX()));
3139 mScroller.setFinalY(pinLocY(mScroller.getFinalY()));
3143 contentSizeChanged(updateLayout);
3146 // Used to avoid sending many visible rect messages.
3147 private Rect mLastVisibleRectSent = new Rect();
3148 private Rect mLastGlobalRect = new Rect();
3149 private Rect mVisibleRect = new Rect();
3150 private Rect mGlobalVisibleRect = new Rect();
3151 private Point mScrollOffset = new Point();
3153 Rect sendOurVisibleRect() {
3154 if (mZoomManager.isPreventingWebkitUpdates()) return mLastVisibleRectSent;
3155 calcOurContentVisibleRect(mVisibleRect);
3156 // Rect.equals() checks for null input.
3157 if (!mVisibleRect.equals(mLastVisibleRectSent)) {
3158 if (!mBlockWebkitViewMessages) {
3159 mScrollOffset.set(mVisibleRect.left, mVisibleRect.top);
3160 mWebViewCore.removeMessages(EventHub.SET_SCROLL_OFFSET);
3161 mWebViewCore.sendMessage(EventHub.SET_SCROLL_OFFSET,
3162 mSendScrollEvent ? 1 : 0, mScrollOffset);
3164 mLastVisibleRectSent.set(mVisibleRect);
3165 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
3167 if (mWebView.getGlobalVisibleRect(mGlobalVisibleRect)
3168 && !mGlobalVisibleRect.equals(mLastGlobalRect)) {
3169 if (DebugFlags.WEB_VIEW) {
3170 Log.v(LOGTAG, "sendOurVisibleRect=(" + mGlobalVisibleRect.left + ","
3171 + mGlobalVisibleRect.top + ",r=" + mGlobalVisibleRect.right + ",b="
3172 + mGlobalVisibleRect.bottom);
3174 // TODO: the global offset is only used by windowRect()
3175 // in ChromeClientAndroid ; other clients such as touch
3176 // and mouse events could return view + screen relative points.
3177 if (!mBlockWebkitViewMessages) {
3178 mWebViewCore.sendMessage(EventHub.SET_GLOBAL_BOUNDS, mGlobalVisibleRect);
3180 mLastGlobalRect.set(mGlobalVisibleRect);
3182 return mVisibleRect;
3185 private Point mGlobalVisibleOffset = new Point();
3186 // Sets r to be the visible rectangle of our webview in view coordinates
3187 private void calcOurVisibleRect(Rect r) {
3188 mWebView.getGlobalVisibleRect(r, mGlobalVisibleOffset);
3189 r.offset(-mGlobalVisibleOffset.x, -mGlobalVisibleOffset.y);
3192 // Sets r to be our visible rectangle in content coordinates
3193 private void calcOurContentVisibleRect(Rect r) {
3194 calcOurVisibleRect(r);
3195 r.left = viewToContentX(r.left);
3196 // viewToContentY will remove the total height of the title bar. Add
3197 // the visible height back in to account for the fact that if the title
3198 // bar is partially visible, the part of the visible rect which is
3199 // displaying our content is displaced by that amount.
3200 r.top = viewToContentY(r.top + getVisibleTitleHeightImpl());
3201 r.right = viewToContentX(r.right);
3202 r.bottom = viewToContentY(r.bottom);
3205 private Rect mContentVisibleRect = new Rect();
3206 // Sets r to be our visible rectangle in content coordinates. We use this
3207 // method on the native side to compute the position of the fixed layers.
3208 // Uses floating coordinates (necessary to correctly place elements when
3209 // the scale factor is not 1)
3210 private void calcOurContentVisibleRectF(RectF r) {
3211 calcOurVisibleRect(mContentVisibleRect);
3212 r.left = viewToContentXf(mContentVisibleRect.left);
3213 // viewToContentY will remove the total height of the title bar. Add
3214 // the visible height back in to account for the fact that if the title
3215 // bar is partially visible, the part of the visible rect which is
3216 // displaying our content is displaced by that amount.
3217 r.top = viewToContentYf(mContentVisibleRect.top + getVisibleTitleHeightImpl());
3218 r.right = viewToContentXf(mContentVisibleRect.right);
3219 r.bottom = viewToContentYf(mContentVisibleRect.bottom);
3222 static class ViewSizeData {
3225 float mHeightWidthRatio;
3226 int mActualViewHeight;
3231 boolean mIgnoreHeight;
3235 * Compute unzoomed width and height, and if they differ from the last
3236 * values we sent, send them to webkit (to be used as new viewport)
3238 * @param force ensures that the message is sent to webkit even if the width
3239 * or height has not changed since the last message
3241 * @return true if new values were sent
3243 boolean sendViewSizeZoom(boolean force) {
3244 if (mBlockWebkitViewMessages) return false;
3245 if (mZoomManager.isPreventingWebkitUpdates()) return false;
3247 int viewWidth = getViewWidth();
3248 int newWidth = Math.round(viewWidth * mZoomManager.getInvScale());
3249 // This height could be fixed and be different from actual visible height.
3250 int viewHeight = getViewHeightWithTitle() - getTitleHeight();
3251 int newHeight = Math.round(viewHeight * mZoomManager.getInvScale());
3252 // Make the ratio more accurate than (newHeight / newWidth), since the
3253 // latter both are calculated and rounded.
3254 float heightWidthRatio = (float) viewHeight / viewWidth;
3256 * Because the native side may have already done a layout before the
3257 * View system was able to measure us, we have to send a height of 0 to
3258 * remove excess whitespace when we grow our width. This will trigger a
3259 * layout and a change in content size. This content size change will
3260 * mean that contentSizeChanged will either call this method directly or
3261 * indirectly from onSizeChanged.
3263 if (newWidth > mLastWidthSent && mWrapContent) {
3265 heightWidthRatio = 0;
3267 // Actual visible content height.
3268 int actualViewHeight = Math.round(getViewHeight() * mZoomManager.getInvScale());
3269 // Avoid sending another message if the dimensions have not changed.
3270 if (newWidth != mLastWidthSent || newHeight != mLastHeightSent || force ||
3271 actualViewHeight != mLastActualHeightSent) {
3272 ViewSizeData data = new ViewSizeData();
3273 data.mWidth = newWidth;
3274 data.mHeight = newHeight;
3275 data.mHeightWidthRatio = heightWidthRatio;
3276 data.mActualViewHeight = actualViewHeight;
3277 data.mTextWrapWidth = Math.round(viewWidth / mZoomManager.getTextWrapScale());
3278 data.mScale = mZoomManager.getScale();
3279 data.mIgnoreHeight = mZoomManager.isFixedLengthAnimationInProgress()
3280 && !mHeightCanMeasure;
3281 data.mAnchorX = mZoomManager.getDocumentAnchorX();
3282 data.mAnchorY = mZoomManager.getDocumentAnchorY();
3283 mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, data);
3284 mLastWidthSent = newWidth;
3285 mLastHeightSent = newHeight;
3286 mLastActualHeightSent = actualViewHeight;
3287 mZoomManager.clearDocumentAnchor();
3294 * Update the double-tap zoom.
3296 /* package */ void updateDoubleTapZoom(int doubleTapZoom) {
3297 mZoomManager.updateDoubleTapZoom(doubleTapZoom);
3300 private int computeRealHorizontalScrollRange() {
3302 return mHistoryWidth;
3304 // to avoid rounding error caused unnecessary scrollbar, use floor
3305 return (int) Math.floor(mContentWidth * mZoomManager.getScale());
3310 public int computeHorizontalScrollRange() {
3311 int range = computeRealHorizontalScrollRange();
3313 // Adjust reported range if overscrolled to compress the scroll bars
3314 final int scrollX = getScrollX();
3315 final int overscrollRight = computeMaxScrollX();
3318 } else if (scrollX > overscrollRight) {
3319 range += scrollX - overscrollRight;
3326 public int computeHorizontalScrollOffset() {
3327 return Math.max(getScrollX(), 0);
3330 private int computeRealVerticalScrollRange() {
3332 return mHistoryHeight;
3334 // to avoid rounding error caused unnecessary scrollbar, use floor
3335 return (int) Math.floor(mContentHeight * mZoomManager.getScale());
3340 public int computeVerticalScrollRange() {
3341 int range = computeRealVerticalScrollRange();
3343 // Adjust reported range if overscrolled to compress the scroll bars
3344 final int scrollY = getScrollY();
3345 final int overscrollBottom = computeMaxScrollY();
3348 } else if (scrollY > overscrollBottom) {
3349 range += scrollY - overscrollBottom;
3356 public int computeVerticalScrollOffset() {
3357 return Math.max(getScrollY() - getTitleHeight(), 0);
3361 public int computeVerticalScrollExtent() {
3362 return getViewHeight();
3366 public void onDrawVerticalScrollBar(Canvas canvas,
3368 int l, int t, int r, int b) {
3369 if (getScrollY() < 0) {
3372 scrollBar.setBounds(l, t + getVisibleTitleHeightImpl(), r, b);
3373 scrollBar.draw(canvas);
3377 public void onOverScrolled(int scrollX, int scrollY, boolean clampedX,
3379 // Special-case layer scrolling so that we do not trigger normal scroll
3381 if (mTouchMode == TOUCH_DRAG_TEXT_MODE) {
3382 scrollEditText(scrollX, scrollY);
3385 if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
3386 scrollLayerTo(scrollX, scrollY);
3389 mInOverScrollMode = false;
3390 int maxX = computeMaxScrollX();
3391 int maxY = computeMaxScrollY();
3393 // do not over scroll x if the page just fits the screen
3394 scrollX = pinLocX(scrollX);
3395 } else if (scrollX < 0 || scrollX > maxX) {
3396 mInOverScrollMode = true;
3398 if (scrollY < 0 || scrollY > maxY) {
3399 mInOverScrollMode = true;
3402 int oldX = getScrollX();
3403 int oldY = getScrollY();
3405 mWebViewPrivate.super_scrollTo(scrollX, scrollY);
3407 if (mOverScrollGlow != null) {
3408 mOverScrollGlow.pullGlow(getScrollX(), getScrollY(), oldX, oldY, maxX, maxY);
3413 * See {@link WebView#getUrl()}
3416 public String getUrl() {
3417 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
3418 return h != null ? h.getUrl() : null;
3422 * See {@link WebView#getOriginalUrl()}
3425 public String getOriginalUrl() {
3426 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
3427 return h != null ? h.getOriginalUrl() : null;
3431 * See {@link WebView#getTitle()}
3434 public String getTitle() {
3435 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
3436 return h != null ? h.getTitle() : null;
3440 * See {@link WebView#getFavicon()}
3443 public Bitmap getFavicon() {
3444 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
3445 return h != null ? h.getFavicon() : null;
3449 * See {@link WebView#getTouchIconUrl()}
3452 public String getTouchIconUrl() {
3453 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
3454 return h != null ? h.getTouchIconUrl() : null;
3458 * See {@link WebView#getProgress()}
3461 public int getProgress() {
3462 return mCallbackProxy.getProgress();
3466 * See {@link WebView#getContentHeight()}
3469 public int getContentHeight() {
3470 return mContentHeight;
3474 * See {@link WebView#getContentWidth()}
3477 public int getContentWidth() {
3478 return mContentWidth;
3481 public int getPageBackgroundColor() {
3482 return nativeGetBackgroundColor();
3486 * See {@link WebView#pauseTimers()}
3489 public void pauseTimers() {
3490 mWebViewCore.sendMessage(EventHub.PAUSE_TIMERS);
3494 * See {@link WebView#resumeTimers()}
3497 public void resumeTimers() {
3498 mWebViewCore.sendMessage(EventHub.RESUME_TIMERS);
3502 * See {@link WebView#onPause()}
3505 public void onPause() {
3508 mWebViewCore.sendMessage(EventHub.ON_PAUSE);
3509 // We want to pause the current playing video when switching out
3510 // from the current WebView/tab.
3511 if (mHTML5VideoViewProxy != null) {
3512 mHTML5VideoViewProxy.pauseAndDispatch();
3514 if (mNativeClass != 0) {
3515 nativeSetPauseDrawing(mNativeClass, true);
3518 cancelSelectDialog();
3519 WebCoreThreadWatchdog.pause();
3524 public void onWindowVisibilityChanged(int visibility) {
3525 updateDrawingState();
3528 void updateDrawingState() {
3529 if (mNativeClass == 0 || mIsPaused) return;
3530 if (mWebView.getWindowVisibility() != View.VISIBLE) {
3531 nativeSetPauseDrawing(mNativeClass, true);
3532 } else if (mWebView.getVisibility() != View.VISIBLE) {
3533 nativeSetPauseDrawing(mNativeClass, true);
3535 nativeSetPauseDrawing(mNativeClass, false);
3540 * See {@link WebView#onResume()}
3543 public void onResume() {
3546 mWebViewCore.sendMessage(EventHub.ON_RESUME);
3547 if (mNativeClass != 0) {
3548 nativeSetPauseDrawing(mNativeClass, false);
3551 // Ensure that the watchdog has a currently valid Context to be able to display
3552 // a prompt dialog. For example, if the Activity was finished whilst the WebCore
3553 // thread was blocked and the Activity is started again, we may reuse the blocked
3554 // thread, but we'll have a new Activity.
3555 WebCoreThreadWatchdog.updateContext(mContext);
3556 // We get a call to onResume for new WebViews (i.e. mIsPaused will be false). We need
3557 // to ensure that the Watchdog thread is running for the new WebView, so call
3558 // it outside the if block above.
3559 WebCoreThreadWatchdog.resume();
3563 * See {@link WebView#isPaused()}
3566 public boolean isPaused() {
3571 * See {@link WebView#freeMemory()}
3574 public void freeMemory() {
3575 mWebViewCore.sendMessage(EventHub.FREE_MEMORY);
3579 * See {@link WebView#clearCache(boolean)}
3582 public void clearCache(boolean includeDiskFiles) {
3583 // Note: this really needs to be a static method as it clears cache for all
3584 // WebView. But we need mWebViewCore to send message to WebCore thread, so
3585 // we can't make this static.
3586 mWebViewCore.sendMessage(EventHub.CLEAR_CACHE,
3587 includeDiskFiles ? 1 : 0, 0);
3591 * See {@link WebView#clearFormData()}
3594 public void clearFormData() {
3595 if (mAutoCompletePopup != null) {
3596 mAutoCompletePopup.clearAdapter();
3601 * See {@link WebView#clearHistory()}
3604 public void clearHistory() {
3605 mCallbackProxy.getBackForwardList().setClearPending();
3606 mWebViewCore.sendMessage(EventHub.CLEAR_HISTORY);
3610 * See {@link WebView#clearSslPreferences()}
3613 public void clearSslPreferences() {
3614 mWebViewCore.sendMessage(EventHub.CLEAR_SSL_PREF_TABLE);
3618 * See {@link WebView#copyBackForwardList()}
3621 public WebBackForwardList copyBackForwardList() {
3622 return mCallbackProxy.getBackForwardList().clone();
3626 * See {@link WebView#setFindListener(WebView.FindListener)}.
3629 public void setFindListener(WebView.FindListener listener) {
3630 mFindListener = listener;
3634 * See {@link WebView#findNext(boolean)}
3637 public void findNext(boolean forward) {
3638 if (0 == mNativeClass) return; // client isn't initialized
3639 if (mFindRequest != null) {
3640 mWebViewCore.sendMessage(EventHub.FIND_NEXT, forward ? 1 : 0, mFindRequest);
3645 * See {@link WebView#findAll(String)}
3648 public int findAll(String find) {
3649 return findAllBody(find, false);
3652 public void findAllAsync(String find) {
3653 findAllBody(find, true);
3656 private int findAllBody(String find, boolean isAsync) {
3657 if (0 == mNativeClass) return 0; // client isn't initialized
3658 mFindRequest = null;
3659 if (find == null) return 0;
3660 mWebViewCore.removeMessages(EventHub.FIND_ALL);
3661 mFindRequest = new WebViewCore.FindAllRequest(find);
3663 mWebViewCore.sendMessage(EventHub.FIND_ALL, mFindRequest);
3664 return 0; // no need to wait for response
3666 synchronized(mFindRequest) {
3668 mWebViewCore.sendMessageAtFrontOfQueue(EventHub.FIND_ALL, mFindRequest);
3669 while (mFindRequest.mMatchCount == -1) {
3670 mFindRequest.wait();
3673 catch (InterruptedException e) {
3676 return mFindRequest.mMatchCount;
3681 * Start an ActionMode for finding text in this WebView. Only works if this
3682 * WebView is attached to the view system.
3683 * @param text If non-null, will be the initial text to search for.
3684 * Otherwise, the last String searched for in this WebView will
3686 * @param showIme If true, show the IME, assuming the user will begin typing.
3687 * If false and text is non-null, perform a find all.
3688 * @return boolean True if the find dialog is shown, false otherwise.
3690 public boolean showFindDialog(String text, boolean showIme) {
3691 FindActionModeCallback callback = new FindActionModeCallback(mContext);
3692 if (mWebView.getParent() == null || mWebView.startActionMode(callback) == null) {
3693 // Could not start the action mode, so end Find on page
3696 mCachedOverlappingActionModeHeight = -1;
3697 mFindCallback = callback;
3699 mFindCallback.setWebView(this);
3701 mFindCallback.showSoftInput();
3702 } else if (text != null) {
3703 mFindCallback.setText(text);
3704 mFindCallback.findAll();
3708 text = mFindRequest == null ? null : mFindRequest.mSearchText;
3711 mFindCallback.setText(text);
3712 mFindCallback.findAll();
3718 * Keep track of the find callback so that we can remove its titlebar if
3721 private FindActionModeCallback mFindCallback;
3724 * Toggle whether the find dialog is showing, for both native and Java.
3726 private void setFindIsUp(boolean isUp) {
3730 // Used to know whether the find dialog is open. Affects whether
3731 // or not we draw the highlights for matches.
3732 private boolean mFindIsUp;
3734 // Keep track of the last find request sent.
3735 private WebViewCore.FindAllRequest mFindRequest = null;
3738 * Return the first substring consisting of the address of a physical
3739 * location. Currently, only addresses in the United States are detected,
3743 * - a street type (Road, Circle, etc), either spelled out or abbreviated
3745 * - a state or territory, either spelled out or two-letter abbr.
3746 * - an optional 5 digit or 9 digit zip code.
3748 * All names must be correctly capitalized, and the zip code, if present,
3749 * must be valid for the state. The street type must be a standard USPS
3750 * spelling or abbreviation. The state or territory must also be spelled
3751 * or abbreviated using USPS standards. The house number may not exceed
3753 * @param addr The string to search for addresses.
3755 * @return the address, or if no address is found, return null.
3757 public static String findAddress(String addr) {
3758 return findAddress(addr, false);
3762 * Return the first substring consisting of the address of a physical
3763 * location. Currently, only addresses in the United States are detected,
3767 * - a street type (Road, Circle, etc), either spelled out or abbreviated
3769 * - a state or territory, either spelled out or two-letter abbr.
3770 * - an optional 5 digit or 9 digit zip code.
3772 * Names are optionally capitalized, and the zip code, if present,
3773 * must be valid for the state. The street type must be a standard USPS
3774 * spelling or abbreviation. The state or territory must also be spelled
3775 * or abbreviated using USPS standards. The house number may not exceed
3777 * @param addr The string to search for addresses.
3778 * @param caseInsensitive addr Set to true to make search ignore case.
3780 * @return the address, or if no address is found, return null.
3782 public static String findAddress(String addr, boolean caseInsensitive) {
3783 return WebViewCore.nativeFindAddress(addr, caseInsensitive);
3787 * See {@link WebView#clearMatches()}
3790 public void clearMatches() {
3791 if (mNativeClass == 0)
3793 mWebViewCore.removeMessages(EventHub.FIND_ALL);
3794 mWebViewCore.sendMessage(EventHub.FIND_ALL, null);
3799 * Called when the find ActionMode ends.
3801 void notifyFindDialogDismissed() {
3802 mFindCallback = null;
3803 mCachedOverlappingActionModeHeight = -1;
3804 if (mWebViewCore == null) {
3809 // Now that the dialog has been removed, ensure that we scroll to a
3810 // location that is not beyond the end of the page.
3811 pinScrollTo(getScrollX(), getScrollY(), false, 0);
3816 * See {@link WebView#documentHasImages(Message)}
3819 public void documentHasImages(Message response) {
3820 if (response == null) {
3823 mWebViewCore.sendMessage(EventHub.DOC_HAS_IMAGES, response);
3827 * Request the scroller to abort any ongoing animation
3829 public void stopScroll() {
3830 mScroller.forceFinished(true);
3835 public void computeScroll() {
3836 if (mScroller.computeScrollOffset()) {
3837 int oldX = getScrollX();
3838 int oldY = getScrollY();
3839 int x = mScroller.getCurrX();
3840 int y = mScroller.getCurrY();
3841 invalidate(); // So we draw again
3843 if (!mScroller.isFinished()) {
3844 int rangeX = computeMaxScrollX();
3845 int rangeY = computeMaxScrollY();
3846 int overflingDistance = mOverflingDistance;
3848 // Use the layer's scroll data if needed.
3849 if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
3850 oldX = mScrollingLayerRect.left;
3851 oldY = mScrollingLayerRect.top;
3852 rangeX = mScrollingLayerRect.right;
3853 rangeY = mScrollingLayerRect.bottom;
3854 // No overscrolling for layers.
3855 overflingDistance = 0;
3856 } else if (mTouchMode == TOUCH_DRAG_TEXT_MODE) {
3857 oldX = getTextScrollX();
3858 oldY = getTextScrollY();
3859 rangeX = getMaxTextScrollX();
3860 rangeY = getMaxTextScrollY();
3861 overflingDistance = 0;
3864 mWebViewPrivate.overScrollBy(x - oldX, y - oldY, oldX, oldY,
3866 overflingDistance, overflingDistance, false);
3868 if (mOverScrollGlow != null) {
3869 mOverScrollGlow.absorbGlow(x, y, oldX, oldY, rangeX, rangeY);
3872 if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
3873 // Update the layer position instead of WebView.
3874 scrollLayerTo(x, y);
3875 } else if (mTouchMode == TOUCH_DRAG_TEXT_MODE) {
3876 scrollEditText(x, y);
3882 nativeSetIsScrolling(false);
3883 if (!mBlockWebkitViewMessages) {
3884 WebViewCore.resumePriority();
3885 if (!mSelectingText) {
3886 WebViewCore.resumeUpdatePicture(mWebViewCore);
3889 if (oldX != getScrollX() || oldY != getScrollY()) {
3890 sendOurVisibleRect();
3894 mWebViewPrivate.super_computeScroll();
3898 private void scrollLayerTo(int x, int y) {
3899 int dx = mScrollingLayerRect.left - x;
3900 int dy = mScrollingLayerRect.top - y;
3901 if (dx == 0 && dy == 0) {
3904 if (mSelectingText) {
3905 if (mSelectCursorBaseLayerId == mCurrentScrollingLayerId) {
3906 mSelectCursorBase.offset(dx, dy);
3907 mSelectCursorBaseTextQuad.offset(dx, dy);
3909 if (mSelectCursorExtentLayerId == mCurrentScrollingLayerId) {
3910 mSelectCursorExtent.offset(dx, dy);
3911 mSelectCursorExtentTextQuad.offset(dx, dy);
3914 if (mAutoCompletePopup != null &&
3915 mCurrentScrollingLayerId == mEditTextLayerId) {
3916 mEditTextContentBounds.offset(dx, dy);
3917 mAutoCompletePopup.resetRect();
3919 nativeScrollLayer(mCurrentScrollingLayerId, x, y);
3920 mScrollingLayerRect.left = x;
3921 mScrollingLayerRect.top = y;
3922 mWebViewCore.sendMessage(WebViewCore.EventHub.SCROLL_LAYER, mCurrentScrollingLayerId,
3923 mScrollingLayerRect);
3924 mWebViewPrivate.onScrollChanged(getScrollX(), getScrollY(), getScrollX(), getScrollY());
3928 private static int computeDuration(int dx, int dy) {
3929 int distance = Math.max(Math.abs(dx), Math.abs(dy));
3930 int duration = distance * 1000 / STD_SPEED;
3931 return Math.min(duration, MAX_DURATION);
3934 // helper to pin the scrollBy parameters (already in view coordinates)
3935 // returns true if the scroll was changed
3936 private boolean pinScrollBy(int dx, int dy, boolean animate, int animationDuration) {
3937 return pinScrollTo(getScrollX() + dx, getScrollY() + dy, animate, animationDuration);
3939 // helper to pin the scrollTo parameters (already in view coordinates)
3940 // returns true if the scroll was changed
3941 private boolean pinScrollTo(int x, int y, boolean animate, int animationDuration) {
3945 int dx = x - getScrollX();
3946 int dy = y - getScrollY();
3948 if ((dx | dy) == 0) {
3952 // Log.d(LOGTAG, "startScroll: " + dx + " " + dy);
3953 mScroller.startScroll(getScrollX(), getScrollY(), dx, dy,
3954 animationDuration > 0 ? animationDuration : computeDuration(dx, dy));
3955 mWebViewPrivate.awakenScrollBars(mScroller.getDuration());
3958 mWebView.scrollTo(x, y);
3963 // Scale from content to view coordinates, and pin.
3964 // Also called by jni webview.cpp
3965 private boolean setContentScrollBy(int cx, int cy, boolean animate) {
3967 // disallow WebView to change the scroll position as History Picture
3968 // is used in the view system.
3969 // TODO: as we switchOutDrawHistory when trackball or navigation
3970 // keys are hit, this should be safe. Right?
3973 cx = contentToViewDimension(cx);
3974 cy = contentToViewDimension(cy);
3975 if (mHeightCanMeasure) {
3976 // move our visible rect according to scroll request
3978 Rect tempRect = new Rect();
3979 calcOurVisibleRect(tempRect);
3980 tempRect.offset(cx, cy);
3981 mWebView.requestRectangleOnScreen(tempRect);
3983 // FIXME: We scroll horizontally no matter what because currently
3984 // ScrollView and ListView will not scroll horizontally.
3985 // FIXME: Why do we only scroll horizontally if there is no
3987 // Log.d(LOGTAG, "setContentScrollBy cy=" + cy);
3988 return cy == 0 && cx != 0 && pinScrollBy(cx, 0, animate, 0);
3990 return pinScrollBy(cx, cy, animate, 0);
3995 * Called by CallbackProxy when the page starts loading.
3996 * @param url The URL of the page which has started loading.
3998 /* package */ void onPageStarted(String url) {
3999 // every time we start a new page, we want to reset the
4000 // WebView certificate: if the new site is secure, we
4001 // will reload it and get a new certificate set;
4002 // if the new site is not secure, the certificate must be
4003 // null, and that will be the case
4004 mWebView.setCertificate(null);
4006 // reset the flag since we set to true in if need after
4007 // loading is see onPageFinished(Url)
4008 mAccessibilityScriptInjected = false;
4012 * Called by CallbackProxy when the page finishes loading.
4013 * @param url The URL of the page which has finished loading.
4015 /* package */ void onPageFinished(String url) {
4016 if (mPageThatNeedsToSlideTitleBarOffScreen != null) {
4017 // If the user is now on a different page, or has scrolled the page
4018 // past the point where the title bar is offscreen, ignore the
4020 if (mPageThatNeedsToSlideTitleBarOffScreen.equals(url)
4021 && getScrollX() == 0 && getScrollY() == 0) {
4022 pinScrollTo(0, mYDistanceToSlideTitleOffScreen, true,
4023 SLIDE_TITLE_DURATION);
4025 mPageThatNeedsToSlideTitleBarOffScreen = null;
4027 mZoomManager.onPageFinished(url);
4028 injectAccessibilityForUrl(url);
4032 * This method injects accessibility in the loaded document if accessibility
4033 * is enabled. If JavaScript is enabled we try to inject a URL specific script.
4034 * If no URL specific script is found or JavaScript is disabled we fallback to
4035 * the default {@link AccessibilityInjector} implementation.
4037 * If the URL has the "axs" paramter set to 1 it has already done the
4038 * script injection so we do nothing. If the parameter is set to 0
4039 * the URL opts out accessibility script injection so we fall back to
4040 * the default {@link AccessibilityInjector}.
4042 * Note: If the user has not opted-in the accessibility script injection no scripts
4043 * are injected rather the default {@link AccessibilityInjector} implementation
4046 * @param url The URL loaded by this {@link WebView}.
4048 private void injectAccessibilityForUrl(String url) {
4049 if (mWebViewCore == null) {
4052 AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
4054 if (!accessibilityManager.isEnabled()) {
4055 // it is possible that accessibility was turned off between reloads
4056 ensureAccessibilityScriptInjectorInstance(false);
4060 if (!getSettings().getJavaScriptEnabled()) {
4061 // no JS so we fallback to the basic buil-in support
4062 ensureAccessibilityScriptInjectorInstance(true);
4066 // check the URL "axs" parameter to choose appropriate action
4067 int axsParameterValue = getAxsUrlParameterValue(url);
4068 if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED) {
4069 boolean onDeviceScriptInjectionEnabled = (Settings.Secure.getInt(mContext
4070 .getContentResolver(), Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1);
4071 if (onDeviceScriptInjectionEnabled) {
4072 ensureAccessibilityScriptInjectorInstance(false);
4073 // neither script injected nor script injection opted out => we inject
4074 mWebView.loadUrl(getScreenReaderInjectingJs());
4075 // TODO: Set this flag after successfull script injection. Maybe upon injection
4076 // the chooser should update the meta tag and we check it to declare success
4077 mAccessibilityScriptInjected = true;
4079 // injection disabled so we fallback to the basic built-in support
4080 ensureAccessibilityScriptInjectorInstance(true);
4082 } else if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT) {
4083 // injection opted out so we fallback to the basic buil-in support
4084 ensureAccessibilityScriptInjectorInstance(true);
4085 } else if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED) {
4086 ensureAccessibilityScriptInjectorInstance(false);
4087 // the URL provides accessibility but we still need to add our generic script
4088 mWebView.loadUrl(getScreenReaderInjectingJs());
4090 Log.e(LOGTAG, "Unknown URL value for the \"axs\" URL parameter: " + axsParameterValue);
4095 * Ensures the instance of the {@link AccessibilityInjector} to be present ot not.
4097 * @param present True to ensure an insance, false to ensure no instance.
4099 private void ensureAccessibilityScriptInjectorInstance(boolean present) {
4101 if (mAccessibilityInjector == null) {
4102 mAccessibilityInjector = new AccessibilityInjector(this);
4105 mAccessibilityInjector = null;
4110 * Gets JavaScript that injects a screen-reader.
4112 * @return The JavaScript snippet.
4114 private String getScreenReaderInjectingJs() {
4115 String screenReaderUrl = Settings.Secure.getString(mContext.getContentResolver(),
4116 Settings.Secure.ACCESSIBILITY_SCREEN_READER_URL);
4117 return String.format(ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE, screenReaderUrl);
4121 * Gets the "axs" URL parameter value.
4123 * @param url A url to fetch the paramter from.
4124 * @return The parameter value if such, -1 otherwise.
4126 private int getAxsUrlParameterValue(String url) {
4127 if (mMatchAxsUrlParameterPattern == null) {
4128 mMatchAxsUrlParameterPattern = Pattern.compile(PATTERN_MATCH_AXS_URL_PARAMETER);
4130 Matcher matcher = mMatchAxsUrlParameterPattern.matcher(url);
4131 if (matcher.find()) {
4132 String keyValuePair = url.substring(matcher.start(), matcher.end());
4133 return Integer.parseInt(keyValuePair.split("=")[1]);
4139 * The URL of a page that sent a message to scroll the title bar off screen.
4141 * Many mobile sites tell the page to scroll to (0,1) in order to scroll the
4142 * title bar off the screen. Sometimes, the scroll position is set before
4143 * the page finishes loading. Rather than scrolling while the page is still
4144 * loading, keep track of the URL and new scroll position so we can perform
4145 * the scroll once the page finishes loading.
4147 private String mPageThatNeedsToSlideTitleBarOffScreen;
4150 * The destination Y scroll position to be used when the page finishes
4151 * loading. See mPageThatNeedsToSlideTitleBarOffScreen.
4153 private int mYDistanceToSlideTitleOffScreen;
4155 // scale from content to view coordinates, and pin
4156 // return true if pin caused the final x/y different than the request cx/cy,
4157 // and a future scroll may reach the request cx/cy after our size has
4159 // return false if the view scroll to the exact position as it is requested,
4160 // where negative numbers are taken to mean 0
4161 private boolean setContentScrollTo(int cx, int cy) {
4163 // disallow WebView to change the scroll position as History Picture
4164 // is used in the view system.
4165 // One known case where this is called is that WebCore tries to
4166 // restore the scroll position. As history Picture already uses the
4167 // saved scroll position, it is ok to skip this.
4172 if ((cx | cy) == 0) {
4173 // If the page is being scrolled to (0,0), do not add in the title
4174 // bar's height, and simply scroll to (0,0). (The only other work
4175 // in contentToView_ is to multiply, so this would not change 0.)
4179 vx = contentToViewX(cx);
4180 vy = contentToViewY(cy);
4182 // Log.d(LOGTAG, "content scrollTo [" + cx + " " + cy + "] view=[" +
4183 // vx + " " + vy + "]");
4184 // Some mobile sites attempt to scroll the title bar off the page by
4185 // scrolling to (0,1). If we are at the top left corner of the
4186 // page, assume this is an attempt to scroll off the title bar, and
4187 // animate the title bar off screen slowly enough that the user can see
4189 if (cx == 0 && cy == 1 && getScrollX() == 0 && getScrollY() == 0
4190 && getTitleHeight() > 0) {
4191 // FIXME: 100 should be defined somewhere as our max progress.
4192 if (getProgress() < 100) {
4193 // Wait to scroll the title bar off screen until the page has
4194 // finished loading. Keep track of the URL and the destination
4196 mPageThatNeedsToSlideTitleBarOffScreen = getUrl();
4197 mYDistanceToSlideTitleOffScreen = vy;
4199 pinScrollTo(vx, vy, true, SLIDE_TITLE_DURATION);
4201 // Since we are animating, we have not yet reached the desired
4202 // scroll position. Do not return true to request another attempt
4205 pinScrollTo(vx, vy, false, 0);
4206 // If the request was to scroll to a negative coordinate, treat it as if
4207 // it was a request to scroll to 0
4208 if ((getScrollX() != vx && cx >= 0) || (getScrollY() != vy && cy >= 0)) {
4215 // scale from content to view coordinates, and pin
4216 private void spawnContentScrollTo(int cx, int cy) {
4218 // disallow WebView to change the scroll position as History Picture
4219 // is used in the view system.
4222 int vx = contentToViewDimension(cx - mScrollOffset.x);
4223 int vy = contentToViewDimension(cy - mScrollOffset.y);
4224 pinScrollBy(vx, vy, true, 0);
4228 * These are from webkit, and are in content coordinate system (unzoomed)
4230 private void contentSizeChanged(boolean updateLayout) {
4231 // suppress 0,0 since we usually see real dimensions soon after
4232 // this avoids drawing the prev content in a funny place. If we find a
4233 // way to consolidate these notifications, this check may become
4235 if ((mContentWidth | mContentHeight) == 0) {
4239 if (mHeightCanMeasure) {
4240 if (mWebView.getMeasuredHeight() != contentToViewDimension(mContentHeight)
4242 mWebView.requestLayout();
4244 } else if (mWidthCanMeasure) {
4245 if (mWebView.getMeasuredWidth() != contentToViewDimension(mContentWidth)
4247 mWebView.requestLayout();
4250 // If we don't request a layout, try to send our view size to the
4251 // native side to ensure that WebCore has the correct dimensions.
4252 sendViewSizeZoom(false);
4257 * See {@link WebView#setWebViewClient(WebViewClient)}
4260 public void setWebViewClient(WebViewClient client) {
4261 mCallbackProxy.setWebViewClient(client);
4265 * Gets the WebViewClient
4266 * @return the current WebViewClient instance.
4268 * This is an implementation detail.
4270 public WebViewClient getWebViewClient() {
4271 return mCallbackProxy.getWebViewClient();
4275 * See {@link WebView#setDownloadListener(DownloadListener)}
4278 public void setDownloadListener(DownloadListener listener) {
4279 mCallbackProxy.setDownloadListener(listener);
4283 * See {@link WebView#setWebChromeClient(WebChromeClient)}
4286 public void setWebChromeClient(WebChromeClient client) {
4287 mCallbackProxy.setWebChromeClient(client);
4291 * Gets the chrome handler.
4292 * @return the current WebChromeClient instance.
4294 * This is an implementation detail.
4296 public WebChromeClient getWebChromeClient() {
4297 return mCallbackProxy.getWebChromeClient();
4301 * Set the back/forward list client. This is an implementation of
4302 * WebBackForwardListClient for handling new items and changes in the
4304 * @param client An implementation of WebBackForwardListClient.
4306 public void setWebBackForwardListClient(WebBackForwardListClient client) {
4307 mCallbackProxy.setWebBackForwardListClient(client);
4311 * Gets the WebBackForwardListClient.
4313 public WebBackForwardListClient getWebBackForwardListClient() {
4314 return mCallbackProxy.getWebBackForwardListClient();
4318 * See {@link WebView#setPictureListener(PictureListener)}
4322 public void setPictureListener(PictureListener listener) {
4323 mPictureListener = listener;
4326 /* FIXME: Debug only! Remove for SDK! */
4327 public void externalRepresentation(Message callback) {
4328 mWebViewCore.sendMessage(EventHub.REQUEST_EXT_REPRESENTATION, callback);
4331 /* FIXME: Debug only! Remove for SDK! */
4332 public void documentAsText(Message callback) {
4333 mWebViewCore.sendMessage(EventHub.REQUEST_DOC_AS_TEXT, callback);
4337 * See {@link WebView#addJavascriptInterface(Object, String)}
4340 public void addJavascriptInterface(Object object, String name) {
4341 if (object == null) {
4344 WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData();
4345 arg.mObject = object;
4346 arg.mInterfaceName = name;
4347 mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg);
4351 * See {@link WebView#removeJavascriptInterface(String)}
4354 public void removeJavascriptInterface(String interfaceName) {
4355 if (mWebViewCore != null) {
4356 WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData();
4357 arg.mInterfaceName = interfaceName;
4358 mWebViewCore.sendMessage(EventHub.REMOVE_JS_INTERFACE, arg);
4363 * See {@link WebView#getSettings()}
4364 * Note this returns WebSettingsClassic, a sub-class of WebSettings, which can be used
4365 * to access extension APIs.
4368 public WebSettingsClassic getSettings() {
4369 return (mWebViewCore != null) ? mWebViewCore.getSettings() : null;
4373 * See {@link WebView#getPluginList()}
4376 public static synchronized PluginList getPluginList() {
4377 return new PluginList();
4381 * See {@link WebView#refreshPlugins(boolean)}
4384 public void refreshPlugins(boolean reloadOpenPages) {
4387 //-------------------------------------------------------------------------
4388 // Override View methods
4389 //-------------------------------------------------------------------------
4392 protected void finalize() throws Throwable {
4394 if (mNativeClass != 0) {
4395 mPrivateHandler.post(new Runnable() {
4407 private void drawContent(Canvas canvas) {
4409 canvas.scale(mZoomManager.getScale(), mZoomManager.getScale());
4410 canvas.drawPicture(mHistoryPicture);
4413 if (mNativeClass == 0) return;
4415 boolean animateZoom = mZoomManager.isFixedLengthAnimationInProgress();
4416 boolean animateScroll = ((!mScroller.isFinished()
4417 || mVelocityTracker != null)
4418 && (mTouchMode != TOUCH_DRAG_MODE ||
4419 mHeldMotionless != MOTIONLESS_TRUE));
4420 if (mTouchMode == TOUCH_DRAG_MODE) {
4421 if (mHeldMotionless == MOTIONLESS_PENDING) {
4422 mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
4423 mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
4424 mHeldMotionless = MOTIONLESS_FALSE;
4426 if (mHeldMotionless == MOTIONLESS_FALSE) {
4427 mPrivateHandler.sendMessageDelayed(mPrivateHandler
4428 .obtainMessage(DRAG_HELD_MOTIONLESS), MOTIONLESS_TIME);
4429 mPrivateHandler.sendMessageDelayed(mPrivateHandler
4430 .obtainMessage(AWAKEN_SCROLL_BARS),
4431 ViewConfiguration.getScrollDefaultDelay());
4432 mHeldMotionless = MOTIONLESS_PENDING;
4435 int saveCount = canvas.save();
4437 mZoomManager.animateZoom(canvas);
4438 } else if (!canvas.isHardwareAccelerated()) {
4439 canvas.scale(mZoomManager.getScale(), mZoomManager.getScale());
4442 boolean UIAnimationsRunning = false;
4443 // Currently for each draw we compute the animation values;
4444 // We may in the future decide to do that independently.
4445 if (mNativeClass != 0 && !canvas.isHardwareAccelerated()
4446 && nativeEvaluateLayersAnimations(mNativeClass)) {
4447 UIAnimationsRunning = true;
4448 // If we have unfinished (or unstarted) animations,
4449 // we ask for a repaint. We only need to do this in software
4450 // rendering (with hardware rendering we already have a different
4451 // method of requesting a repaint)
4452 mWebViewCore.sendMessage(EventHub.NOTIFY_ANIMATION_STARTED);
4456 // decide which adornments to draw
4457 int extras = DRAW_EXTRAS_NONE;
4458 if (!mFindIsUp && mSelectingText) {
4459 extras = DRAW_EXTRAS_SELECTION;
4462 calcOurContentVisibleRectF(mVisibleContentRect);
4463 if (canvas.isHardwareAccelerated()) {
4464 Rect glRectViewport = mGLViewportEmpty ? null : mGLRectViewport;
4465 Rect viewRectViewport = mGLViewportEmpty ? null : mViewRectViewport;
4467 int functor = nativeGetDrawGLFunction(mNativeClass, glRectViewport,
4468 viewRectViewport, mVisibleContentRect, getScale(), extras);
4469 ((HardwareCanvas) canvas).callDrawGLFunction(functor);
4470 if (mHardwareAccelSkia != getSettings().getHardwareAccelSkiaEnabled()) {
4471 mHardwareAccelSkia = getSettings().getHardwareAccelSkiaEnabled();
4472 nativeUseHardwareAccelSkia(mHardwareAccelSkia);
4476 DrawFilter df = null;
4477 if (mZoomManager.isZoomAnimating() || UIAnimationsRunning) {
4479 } else if (animateScroll) {
4482 canvas.setDrawFilter(df);
4483 // XXX: Revisit splitting content. Right now it causes a
4484 // synchronization problem with layers.
4485 int content = nativeDraw(canvas, mVisibleContentRect, mBackgroundColor,
4487 canvas.setDrawFilter(null);
4488 if (!mBlockWebkitViewMessages && content != 0) {
4489 mWebViewCore.sendMessage(EventHub.SPLIT_PICTURE_SET, content, 0);
4493 canvas.restoreToCount(saveCount);
4494 if (mSelectingText) {
4495 drawTextSelectionHandles(canvas);
4498 if (extras == DRAW_EXTRAS_CURSOR_RING) {
4499 if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
4500 mTouchMode = TOUCH_SHORTPRESS_MODE;
4506 * Draw the background when beyond bounds
4507 * @param canvas Canvas to draw into
4509 private void drawOverScrollBackground(Canvas canvas) {
4510 if (mOverScrollBackground == null) {
4511 mOverScrollBackground = new Paint();
4512 Bitmap bm = BitmapFactory.decodeResource(
4513 mContext.getResources(),
4514 com.android.internal.R.drawable.status_bar_background);
4515 mOverScrollBackground.setShader(new BitmapShader(bm,
4516 Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
4517 mOverScrollBorder = new Paint();
4518 mOverScrollBorder.setStyle(Paint.Style.STROKE);
4519 mOverScrollBorder.setStrokeWidth(0);
4520 mOverScrollBorder.setColor(0xffbbbbbb);
4524 int right = computeRealHorizontalScrollRange();
4525 int bottom = top + computeRealVerticalScrollRange();
4526 // first draw the background and anchor to the top of the view
4528 canvas.translate(getScrollX(), getScrollY());
4529 canvas.clipRect(-getScrollX(), top - getScrollY(), right - getScrollX(), bottom
4530 - getScrollY(), Region.Op.DIFFERENCE);
4531 canvas.drawPaint(mOverScrollBackground);
4533 // then draw the border
4534 canvas.drawRect(-1, top - 1, right, bottom, mOverScrollBorder);
4535 // next clip the region for the content
4536 canvas.clipRect(0, top, right, bottom);
4540 public void onDraw(Canvas canvas) {
4541 if (inFullScreenMode()) {
4542 return; // no need to draw anything if we aren't visible.
4544 // if mNativeClass is 0, the WebView is either destroyed or not
4545 // initialized. In either case, just draw the background color and return
4546 if (mNativeClass == 0) {
4547 canvas.drawColor(mBackgroundColor);
4551 // if both mContentWidth and mContentHeight are 0, it means there is no
4552 // valid Picture passed to WebView yet. This can happen when WebView
4553 // just starts. Draw the background and return.
4554 if ((mContentWidth | mContentHeight) == 0 && mHistoryPicture == null) {
4555 canvas.drawColor(mBackgroundColor);
4559 if (canvas.isHardwareAccelerated()) {
4560 mZoomManager.setHardwareAccelerated();
4562 mWebViewCore.resumeWebKitDraw();
4565 int saveCount = canvas.save();
4566 if (mInOverScrollMode && !getSettings()
4567 .getUseWebViewBackgroundForOverscrollBackground()) {
4568 drawOverScrollBackground(canvas);
4571 canvas.translate(0, getTitleHeight());
4572 drawContent(canvas);
4573 canvas.restoreToCount(saveCount);
4575 if (AUTO_REDRAW_HACK && mAutoRedraw) {
4578 mWebViewCore.signalRepaintDone();
4580 if (mOverScrollGlow != null && mOverScrollGlow.drawEdgeGlows(canvas)) {
4584 if (mFocusTransition != null) {
4585 mFocusTransition.draw(canvas);
4586 } else if (shouldDrawHighlightRect()) {
4587 RegionIterator iter = new RegionIterator(mTouchHighlightRegion);
4588 Rect r = new Rect();
4589 while (iter.next(r)) {
4590 canvas.drawRect(r, mTouchHightlightPaint);
4593 if (DEBUG_TOUCH_HIGHLIGHT) {
4594 if (getSettings().getNavDump()) {
4595 if ((mTouchHighlightX | mTouchHighlightY) != 0) {
4596 if (mTouchCrossHairColor == null) {
4597 mTouchCrossHairColor = new Paint();
4598 mTouchCrossHairColor.setColor(Color.RED);
4600 canvas.drawLine(mTouchHighlightX - mNavSlop,
4601 mTouchHighlightY - mNavSlop, mTouchHighlightX
4602 + mNavSlop + 1, mTouchHighlightY + mNavSlop
4603 + 1, mTouchCrossHairColor);
4604 canvas.drawLine(mTouchHighlightX + mNavSlop + 1,
4605 mTouchHighlightY - mNavSlop, mTouchHighlightX
4607 mTouchHighlightY + mNavSlop + 1,
4608 mTouchCrossHairColor);
4614 private void removeTouchHighlight() {
4615 mWebViewCore.removeMessages(EventHub.HIT_TEST);
4616 mPrivateHandler.removeMessages(HIT_TEST_RESULT);
4617 setTouchHighlightRects(null);
4621 public void setLayoutParams(ViewGroup.LayoutParams params) {
4622 if (params.height == AbsoluteLayout.LayoutParams.WRAP_CONTENT) {
4623 mWrapContent = true;
4625 mWebViewPrivate.super_setLayoutParams(params);
4629 public boolean performLongClick() {
4630 // performLongClick() is the result of a delayed message. If we switch
4631 // to windows overview, the WebView will be temporarily removed from the
4632 // view system. In that case, do nothing.
4633 if (mWebView.getParent() == null) return false;
4635 // A multi-finger gesture can look like a long press; make sure we don't take
4636 // long press actions if we're scaling.
4637 final ScaleGestureDetector detector = mZoomManager.getMultiTouchGestureDetector();
4638 if (detector != null && detector.isInProgress()) {
4642 if (mSelectingText) return false; // long click does nothing on selection
4643 /* if long click brings up a context menu, the super function
4644 * returns true and we're done. Otherwise, nothing happened when
4645 * the user clicked. */
4646 if (mWebViewPrivate.super_performLongClick()) {
4649 /* In the case where the application hasn't already handled the long
4650 * click action, look for a word under the click. If one is found,
4651 * animate the text selection into view.
4652 * FIXME: no animation code yet */
4653 final boolean isSelecting = selectText();
4655 mWebView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
4656 } else if (focusCandidateIsEditableText()) {
4657 mSelectCallback = new SelectActionModeCallback();
4658 mSelectCallback.setWebView(this);
4659 mSelectCallback.setTextSelected(false);
4660 mWebView.startActionMode(mSelectCallback);
4666 * Select the word at the last click point.
4668 * This is an implementation detail.
4670 public boolean selectText() {
4671 int x = viewToContentX(mLastTouchX + getScrollX());
4672 int y = viewToContentY(mLastTouchY + getScrollY());
4673 return selectText(x, y);
4677 * Select the word at the indicated content coordinates.
4679 boolean selectText(int x, int y) {
4680 if (mWebViewCore == null) {
4683 mWebViewCore.sendMessage(EventHub.SELECT_WORD_AT, x, y);
4687 private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
4690 public void onConfigurationChanged(Configuration newConfig) {
4691 mCachedOverlappingActionModeHeight = -1;
4692 if (mSelectingText && mOrientation != newConfig.orientation) {
4695 mOrientation = newConfig.orientation;
4696 if (mWebViewCore != null && !mBlockWebkitViewMessages) {
4697 mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT);
4702 * Keep track of the Callback so we can end its ActionMode or remove its
4705 private SelectActionModeCallback mSelectCallback;
4707 void setBaseLayer(int layer, Region invalRegion, boolean showVisualIndicator,
4708 boolean isPictureAfterFirstLayout) {
4709 if (mNativeClass == 0)
4712 queueFull = nativeSetBaseLayer(mNativeClass, layer, invalRegion,
4713 showVisualIndicator, isPictureAfterFirstLayout);
4716 mWebViewCore.pauseWebKitDraw();
4718 mWebViewCore.resumeWebKitDraw();
4721 if (mHTML5VideoViewProxy != null) {
4722 mHTML5VideoViewProxy.setBaseLayer(layer);
4726 int getBaseLayer() {
4727 if (mNativeClass == 0) {
4730 return nativeGetBaseLayer();
4733 private void onZoomAnimationStart() {
4736 private void onZoomAnimationEnd() {
4737 mPrivateHandler.sendEmptyMessage(RELOCATE_AUTO_COMPLETE_POPUP);
4740 void onFixedLengthZoomAnimationStart() {
4741 WebViewCore.pauseUpdatePicture(getWebViewCore());
4742 onZoomAnimationStart();
4745 void onFixedLengthZoomAnimationEnd() {
4746 if (!mBlockWebkitViewMessages && !mSelectingText) {
4747 WebViewCore.resumeUpdatePicture(mWebViewCore);
4749 onZoomAnimationEnd();
4752 private static final int ZOOM_BITS = Paint.FILTER_BITMAP_FLAG |
4754 Paint.SUBPIXEL_TEXT_FLAG;
4755 private static final int SCROLL_BITS = Paint.FILTER_BITMAP_FLAG |
4758 private final DrawFilter mZoomFilter =
4759 new PaintFlagsDrawFilter(ZOOM_BITS, Paint.LINEAR_TEXT_FLAG);
4760 // If we need to trade better quality for speed, set mScrollFilter to null
4761 private final DrawFilter mScrollFilter =
4762 new PaintFlagsDrawFilter(SCROLL_BITS, 0);
4764 private void ensureSelectionHandles() {
4765 if (mSelectHandleCenter == null) {
4766 mSelectHandleCenter = mContext.getResources().getDrawable(
4767 com.android.internal.R.drawable.text_select_handle_middle);
4768 mSelectHandleLeft = mContext.getResources().getDrawable(
4769 com.android.internal.R.drawable.text_select_handle_left);
4770 mSelectHandleRight = mContext.getResources().getDrawable(
4771 com.android.internal.R.drawable.text_select_handle_right);
4772 mSelectHandleCenterOffset = new Point(0,
4773 -mSelectHandleCenter.getIntrinsicHeight());
4774 mSelectHandleLeftOffset = new Point(0,
4775 -mSelectHandleLeft.getIntrinsicHeight());
4776 mSelectHandleRightOffset = new Point(
4777 -mSelectHandleLeft.getIntrinsicWidth() / 2,
4778 -mSelectHandleRight.getIntrinsicHeight());
4782 private void drawTextSelectionHandles(Canvas canvas) {
4783 ensureSelectionHandles();
4784 int[] handles = new int[4];
4785 getSelectionHandles(handles);
4786 int start_x = contentToViewDimension(handles[0]);
4787 int start_y = contentToViewDimension(handles[1]);
4788 int end_x = contentToViewDimension(handles[2]);
4789 int end_y = contentToViewDimension(handles[3]);
4791 if (mIsCaretSelection) {
4792 // Caret handle is centered
4793 start_x -= (mSelectHandleCenter.getIntrinsicWidth() / 2);
4794 mSelectHandleCenter.setBounds(start_x, start_y,
4795 start_x + mSelectHandleCenter.getIntrinsicWidth(),
4796 start_y + mSelectHandleCenter.getIntrinsicHeight());
4797 mSelectHandleCenter.draw(canvas);
4799 // Magic formula copied from TextView
4800 start_x -= (mSelectHandleLeft.getIntrinsicWidth() * 3) / 4;
4801 mSelectHandleLeft.setBounds(start_x, start_y,
4802 start_x + mSelectHandleLeft.getIntrinsicWidth(),
4803 start_y + mSelectHandleLeft.getIntrinsicHeight());
4804 end_x -= mSelectHandleRight.getIntrinsicWidth() / 4;
4805 mSelectHandleRight.setBounds(end_x, end_y,
4806 end_x + mSelectHandleRight.getIntrinsicWidth(),
4807 end_y + mSelectHandleRight.getIntrinsicHeight());
4808 mSelectHandleLeft.draw(canvas);
4809 mSelectHandleRight.draw(canvas);
4814 * Takes an int[4] array as an output param with the values being
4815 * startX, startY, endX, endY
4817 private void getSelectionHandles(int[] handles) {
4818 handles[0] = mSelectCursorBase.x;
4819 handles[1] = mSelectCursorBase.y;
4820 handles[2] = mSelectCursorExtent.x;
4821 handles[3] = mSelectCursorExtent.y;
4822 if (!nativeIsBaseFirst(mNativeClass)) {
4823 int swap = handles[0];
4824 handles[0] = handles[2];
4827 handles[1] = handles[3];
4833 private boolean mDrawHistory = false;
4834 private Picture mHistoryPicture = null;
4835 private int mHistoryWidth = 0;
4836 private int mHistoryHeight = 0;
4838 // Only check the flag, can be called from WebCore thread
4839 boolean drawHistory() {
4840 return mDrawHistory;
4843 int getHistoryPictureWidth() {
4844 return (mHistoryPicture != null) ? mHistoryPicture.getWidth() : 0;
4847 // Should only be called in UI thread
4848 void switchOutDrawHistory() {
4849 if (null == mWebViewCore) return; // CallbackProxy may trigger this
4850 if (mDrawHistory && (getProgress() == 100 || nativeHasContent())) {
4851 mDrawHistory = false;
4852 mHistoryPicture = null;
4854 int oldScrollX = getScrollX();
4855 int oldScrollY = getScrollY();
4856 setScrollXRaw(pinLocX(getScrollX()));
4857 setScrollYRaw(pinLocY(getScrollY()));
4858 if (oldScrollX != getScrollX() || oldScrollY != getScrollY()) {
4859 mWebViewPrivate.onScrollChanged(getScrollX(), getScrollY(), oldScrollX, oldScrollY);
4861 sendOurVisibleRect();
4867 * Delete text from start to end in the focused textfield. If there is no
4868 * focus, or if start == end, silently fail. If start and end are out of
4870 * @param start Beginning of selection to delete.
4871 * @param end End of selection to delete.
4873 /* package */ void deleteSelection(int start, int end) {
4875 WebViewCore.TextSelectionData data
4876 = new WebViewCore.TextSelectionData(start, end, 0);
4877 mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, mTextGeneration, 0,
4882 * Set the selection to (start, end) in the focused textfield. If start and
4883 * end are out of order, swap them.
4884 * @param start Beginning of selection.
4885 * @param end End of selection.
4887 /* package */ void setSelection(int start, int end) {
4888 if (mWebViewCore != null) {
4889 mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end);
4894 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
4895 if (mInputConnection == null) {
4896 mInputConnection = new WebViewInputConnection();
4897 mAutoCompletePopup = new AutoCompletePopup(mContext, this,
4900 mInputConnection.setupEditorInfo(outAttrs);
4901 return mInputConnection;
4904 private void relocateAutoCompletePopup() {
4905 if (mAutoCompletePopup != null) {
4906 mAutoCompletePopup.resetRect();
4907 mAutoCompletePopup.setText(mInputConnection.getEditable());
4912 * Called in response to a message from webkit telling us that the soft
4913 * keyboard should be launched.
4915 private void displaySoftKeyboard(boolean isTextView) {
4916 InputMethodManager imm = (InputMethodManager)
4917 mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
4919 // bring it back to the default level scale so that user can enter text
4920 boolean zoom = mZoomManager.getScale() < mZoomManager.getDefaultScale();
4922 mZoomManager.setZoomCenter(mLastTouchX, mLastTouchY);
4923 mZoomManager.setZoomScale(mZoomManager.getDefaultScale(), false);
4925 // Used by plugins and contentEditable.
4926 // Also used if the navigation cache is out of date, and
4927 // does not recognize that a textfield is in focus. In that
4928 // case, use WebView as the targeted view.
4929 // see http://b/issue?id=2457459
4930 imm.showSoftInput(mWebView, 0);
4933 // Called by WebKit to instruct the UI to hide the keyboard
4934 private void hideSoftKeyboard() {
4935 InputMethodManager imm = InputMethodManager.peekInstance();
4936 if (imm != null && (imm.isActive(mWebView))) {
4937 imm.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
4942 * Called by AutoCompletePopup to find saved form data associated with the
4944 * @param name Name of the textfield.
4945 * @param nodePointer Pointer to the node of the textfield, so it can be
4946 * compared to the currently focused textfield when the data is
4948 * @param autoFillable true if WebKit has determined this field is part of
4949 * a form that can be auto filled.
4950 * @param autoComplete true if the attribute "autocomplete" is set to true
4953 /* package */ void requestFormData(String name, int nodePointer,
4954 boolean autoFillable, boolean autoComplete) {
4955 if (mWebViewCore.getSettings().getSaveFormData()) {
4956 Message update = mPrivateHandler.obtainMessage(REQUEST_FORM_DATA);
4957 update.arg1 = nodePointer;
4958 RequestFormData updater = new RequestFormData(name, getUrl(),
4959 update, autoFillable, autoComplete);
4960 Thread t = new Thread(updater);
4966 * This class requests an Adapter for the AutoCompletePopup which shows past
4967 * entries stored in the database. It is a Runnable so that it can be done
4968 * in its own thread, without slowing down the UI.
4970 private class RequestFormData implements Runnable {
4971 private String mName;
4972 private String mUrl;
4973 private Message mUpdateMessage;
4974 private boolean mAutoFillable;
4975 private boolean mAutoComplete;
4976 private WebSettingsClassic mWebSettings;
4978 public RequestFormData(String name, String url, Message msg,
4979 boolean autoFillable, boolean autoComplete) {
4981 mUrl = WebTextView.urlForAutoCompleteData(url);
4982 mUpdateMessage = msg;
4983 mAutoFillable = autoFillable;
4984 mAutoComplete = autoComplete;
4985 mWebSettings = getSettings();
4990 ArrayList<String> pastEntries = new ArrayList<String>();
4992 if (mAutoFillable) {
4993 // Note that code inside the adapter click handler in AutoCompletePopup depends
4994 // on the AutoFill item being at the top of the drop down list. If you change
4995 // the order, make sure to do it there too!
4996 if (mWebSettings != null && mWebSettings.getAutoFillProfile() != null) {
4997 pastEntries.add(mWebView.getResources().getText(
4998 com.android.internal.R.string.autofill_this_form).toString() +
5000 mAutoFillData.getPreviewString());
5001 mAutoCompletePopup.setIsAutoFillProfileSet(true);
5003 // There is no autofill profile set up yet, so add an option that
5004 // will invite the user to set their profile up.
5005 pastEntries.add(mWebView.getResources().getText(
5006 com.android.internal.R.string.setup_autofill).toString());
5007 mAutoCompletePopup.setIsAutoFillProfileSet(false);
5011 if (mAutoComplete) {
5012 pastEntries.addAll(mDatabase.getFormData(mUrl, mName));
5015 if (pastEntries.size() > 0) {
5016 ArrayAdapter<String> adapter = new ArrayAdapter<String>(
5018 com.android.internal.R.layout.web_text_view_dropdown,
5020 mUpdateMessage.obj = adapter;
5021 mUpdateMessage.sendToTarget();
5027 * Dump the display tree to "/sdcard/displayTree.txt"
5031 public void dumpDisplayTree() {
5032 nativeDumpDisplayTree(getUrl());
5036 * Dump the dom tree to adb shell if "toFile" is False, otherwise dump it to
5037 * "/sdcard/domTree.txt"
5041 public void dumpDomTree(boolean toFile) {
5042 mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE, toFile ? 1 : 0, 0);
5046 * Dump the render tree to adb shell if "toFile" is False, otherwise dump it
5047 * to "/sdcard/renderTree.txt"
5051 public void dumpRenderTree(boolean toFile) {
5052 mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE, toFile ? 1 : 0, 0);
5056 * Called by DRT on UI thread, need to proxy to WebCore thread.
5060 public void setUseMockDeviceOrientation() {
5061 mWebViewCore.sendMessage(EventHub.SET_USE_MOCK_DEVICE_ORIENTATION);
5065 * Called by DRT on WebCore thread.
5069 public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
5070 boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
5071 mWebViewCore.setMockDeviceOrientation(canProvideAlpha, alpha, canProvideBeta, beta,
5072 canProvideGamma, gamma);
5075 // This is used to determine long press with the center key. Does not
5076 // affect long press with the trackball/touch.
5077 private boolean mGotCenterDown = false;
5080 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
5081 if (mBlockWebkitViewMessages) {
5084 // send complex characters to webkit for use by JS and plugins
5085 if (keyCode == KeyEvent.KEYCODE_UNKNOWN && event.getCharacters() != null) {
5086 // pass the key to DOM
5087 sendBatchableInputMessage(EventHub.KEY_DOWN, 0, 0, event);
5088 sendBatchableInputMessage(EventHub.KEY_UP, 0, 0, event);
5089 // return true as DOM handles the key
5095 private boolean isEnterActionKey(int keyCode) {
5096 return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
5097 || keyCode == KeyEvent.KEYCODE_ENTER
5098 || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER;
5101 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
5102 if (mAutoCompletePopup != null) {
5103 return mAutoCompletePopup.onKeyPreIme(keyCode, event);
5109 public boolean onKeyDown(int keyCode, KeyEvent event) {
5110 if (DebugFlags.WEB_VIEW) {
5111 Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis()
5112 + "keyCode=" + keyCode
5113 + ", " + event + ", unicode=" + event.getUnicodeChar());
5115 if (mIsCaretSelection) {
5118 if (mBlockWebkitViewMessages) {
5122 // don't implement accelerator keys here; defer to host application
5123 if (event.isCtrlPressed()) {
5127 if (mNativeClass == 0) {
5131 // do this hack up front, so it always works, regardless of touch-mode
5132 if (AUTO_REDRAW_HACK && (keyCode == KeyEvent.KEYCODE_CALL)) {
5133 mAutoRedraw = !mAutoRedraw;
5140 // Bubble up the key event if
5141 // 1. it is a system key; or
5142 // 2. the host application wants to handle it;
5143 if (event.isSystem()
5144 || mCallbackProxy.uiOverrideKeyEvent(event)) {
5148 // accessibility support
5149 if (accessibilityScriptInjected()) {
5150 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
5151 // if an accessibility script is injected we delegate to it the key handling.
5152 // this script is a screen reader which is a fully fledged solution for blind
5153 // users to navigate in and interact with web pages.
5154 sendBatchableInputMessage(EventHub.KEY_DOWN, 0, 0, event);
5157 // Clean up if accessibility was disabled after loading the current URL.
5158 mAccessibilityScriptInjected = false;
5160 } else if (mAccessibilityInjector != null) {
5161 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
5162 if (mAccessibilityInjector.onKeyEvent(event)) {
5163 // if an accessibility injector is present (no JavaScript enabled or the site
5164 // opts out injecting our JavaScript screen reader) we let it decide whether
5165 // to act on and consume the event.
5169 // Clean up if accessibility was disabled after loading the current URL.
5170 mAccessibilityInjector = null;
5174 if (keyCode == KeyEvent.KEYCODE_PAGE_UP) {
5175 if (event.hasNoModifiers()) {
5178 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
5184 if (keyCode == KeyEvent.KEYCODE_PAGE_DOWN) {
5185 if (event.hasNoModifiers()) {
5188 } else if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
5194 if (keyCode == KeyEvent.KEYCODE_MOVE_HOME && event.hasNoModifiers()) {
5199 if (keyCode == KeyEvent.KEYCODE_MOVE_END && event.hasNoModifiers()) {
5204 if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
5205 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
5206 switchOutDrawHistory();
5209 if (isEnterActionKey(keyCode)) {
5210 switchOutDrawHistory();
5211 if (event.getRepeatCount() == 0) {
5212 if (mSelectingText) {
5213 return true; // discard press if copy in progress
5215 mGotCenterDown = true;
5216 mPrivateHandler.sendMessageDelayed(mPrivateHandler
5217 .obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT);
5221 if (getSettings().getNavDump()) {
5223 case KeyEvent.KEYCODE_4:
5226 case KeyEvent.KEYCODE_5:
5227 case KeyEvent.KEYCODE_6:
5228 dumpDomTree(keyCode == KeyEvent.KEYCODE_5);
5230 case KeyEvent.KEYCODE_7:
5231 case KeyEvent.KEYCODE_8:
5232 dumpRenderTree(keyCode == KeyEvent.KEYCODE_7);
5237 // pass the key to DOM
5238 sendKeyEvent(event);
5239 // return true as DOM handles the key
5244 public boolean onKeyUp(int keyCode, KeyEvent event) {
5245 if (DebugFlags.WEB_VIEW) {
5246 Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis()
5247 + ", " + event + ", unicode=" + event.getUnicodeChar());
5249 if (mBlockWebkitViewMessages) {
5253 if (mNativeClass == 0) {
5257 // special CALL handling when cursor node's href is "tel:XXX"
5258 if (keyCode == KeyEvent.KEYCODE_CALL
5259 && mInitialHitTestResult != null
5260 && mInitialHitTestResult.getType() == HitTestResult.PHONE_TYPE) {
5261 String text = mInitialHitTestResult.getExtra();
5262 Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(text));
5263 mContext.startActivity(intent);
5267 // Bubble up the key event if
5268 // 1. it is a system key; or
5269 // 2. the host application wants to handle it;
5270 if (event.isSystem()
5271 || mCallbackProxy.uiOverrideKeyEvent(event)) {
5275 // accessibility support
5276 if (accessibilityScriptInjected()) {
5277 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
5278 // if an accessibility script is injected we delegate to it the key handling.
5279 // this script is a screen reader which is a fully fledged solution for blind
5280 // users to navigate in and interact with web pages.
5281 sendBatchableInputMessage(EventHub.KEY_UP, 0, 0, event);
5284 // Clean up if accessibility was disabled after loading the current URL.
5285 mAccessibilityScriptInjected = false;
5287 } else if (mAccessibilityInjector != null) {
5288 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
5289 if (mAccessibilityInjector.onKeyEvent(event)) {
5290 // if an accessibility injector is present (no JavaScript enabled or the site
5291 // opts out injecting our JavaScript screen reader) we let it decide whether to
5292 // act on and consume the event.
5296 // Clean up if accessibility was disabled after loading the current URL.
5297 mAccessibilityInjector = null;
5301 if (isEnterActionKey(keyCode)) {
5302 // remove the long press message first
5303 mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
5304 mGotCenterDown = false;
5306 if (mSelectingText) {
5309 return true; // discard press if copy in progress
5313 // pass the key to DOM
5314 sendKeyEvent(event);
5315 // return true as DOM handles the key
5319 private boolean startSelectActionMode() {
5320 mSelectCallback = new SelectActionModeCallback();
5321 mSelectCallback.setTextSelected(!mIsCaretSelection);
5322 mSelectCallback.setWebView(this);
5323 if (mWebView.startActionMode(mSelectCallback) == null) {
5324 // There is no ActionMode, so do not allow the user to modify a
5329 mWebView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
5333 private void showPasteWindow() {
5334 ClipboardManager cm = (ClipboardManager)(mContext
5335 .getSystemService(Context.CLIPBOARD_SERVICE));
5336 if (cm.hasPrimaryClip()) {
5337 Point cursorPoint = new Point(contentToViewX(mSelectCursorBase.x),
5338 contentToViewY(mSelectCursorBase.y));
5339 Point cursorTop = calculateCaretTop();
5340 cursorTop.set(contentToViewX(cursorTop.x),
5341 contentToViewY(cursorTop.y));
5343 int[] location = new int[2];
5344 mWebView.getLocationInWindow(location);
5345 int offsetX = location[0] - getScrollX();
5346 int offsetY = location[1] - getScrollY();
5347 cursorPoint.offset(offsetX, offsetY);
5348 cursorTop.offset(offsetX, offsetY);
5349 if (mPasteWindow == null) {
5350 mPasteWindow = new PastePopupWindow();
5352 mPasteWindow.show(cursorPoint, cursorTop, location[0], location[1]);
5357 * Given segment AB, this finds the point C along AB that is closest to
5358 * point and then returns it scale along AB. The scale factor is AC/AB.
5360 * @param x The x coordinate of the point near segment AB that determines
5362 * @param y The y coordinate of the point near segment AB that determines
5364 * @param a The first point of the line segment.
5365 * @param b The second point of the line segment.
5366 * @return The scale factor AC/AB, where C is the point on AB closest to
5369 private static float scaleAlongSegment(int x, int y, PointF a, PointF b) {
5370 // The bottom line of the text box is line AB
5371 float abX = b.x - a.x;
5372 float abY = b.y - a.y;
5373 float ab2 = (abX * abX) + (abY * abY);
5375 // The line from first point in text bounds to bottom is AP
5376 float apX = x - a.x;
5377 float apY = y - a.y;
5378 float abDotAP = (apX * abX) + (apY * abY);
5379 float scale = abDotAP / ab2;
5384 * Assuming arbitrary shape of a quadralateral forming text bounds, this
5385 * calculates the top of a caret.
5387 private Point calculateCaretTop() {
5388 float scale = scaleAlongSegment(mSelectCursorBase.x, mSelectCursorBase.y,
5389 mSelectCursorBaseTextQuad.p4, mSelectCursorBaseTextQuad.p3);
5390 int x = Math.round(scaleCoordinate(scale,
5391 mSelectCursorBaseTextQuad.p1.x, mSelectCursorBaseTextQuad.p2.x));
5392 int y = Math.round(scaleCoordinate(scale,
5393 mSelectCursorBaseTextQuad.p1.y, mSelectCursorBaseTextQuad.p2.y));
5394 return new Point(x, y);
5397 private void hidePasteButton() {
5398 if (mPasteWindow != null) {
5399 mPasteWindow.hide();
5403 private void syncSelectionCursors() {
5404 mSelectCursorBaseLayerId =
5405 nativeGetHandleLayerId(mNativeClass, HANDLE_ID_BASE,
5406 mSelectCursorBase, mSelectCursorBaseTextQuad);
5407 mSelectCursorExtentLayerId =
5408 nativeGetHandleLayerId(mNativeClass, HANDLE_ID_EXTENT,
5409 mSelectCursorExtent, mSelectCursorExtentTextQuad);
5412 private void adjustSelectionCursors() {
5413 boolean wasDraggingStart = (mSelectDraggingCursor == mSelectCursorBase);
5414 int oldX = mSelectDraggingCursor.x;
5415 int oldY = mSelectDraggingCursor.y;
5416 int oldStartX = mSelectCursorBase.x;
5417 int oldStartY = mSelectCursorBase.y;
5418 int oldEndX = mSelectCursorExtent.x;
5419 int oldEndY = mSelectCursorExtent.y;
5421 syncSelectionCursors();
5422 boolean dragChanged = oldX != mSelectDraggingCursor.x ||
5423 oldY != mSelectDraggingCursor.y;
5424 if (dragChanged && !mIsCaretSelection) {
5425 boolean draggingStart;
5426 if (wasDraggingStart) {
5427 float endStart = distanceSquared(oldEndX, oldEndY,
5429 float endEnd = distanceSquared(oldEndX, oldEndY,
5430 mSelectCursorExtent);
5431 draggingStart = endStart > endEnd;
5433 float startStart = distanceSquared(oldStartX, oldStartY,
5435 float startEnd = distanceSquared(oldStartX, oldStartY,
5436 mSelectCursorExtent);
5437 draggingStart = startStart > startEnd;
5439 mSelectDraggingCursor = (draggingStart
5440 ? mSelectCursorBase : mSelectCursorExtent);
5441 mSelectDraggingTextQuad = (draggingStart
5442 ? mSelectCursorBaseTextQuad : mSelectCursorExtentTextQuad);
5443 mSelectDraggingOffset = (draggingStart
5444 ? mSelectHandleLeftOffset : mSelectHandleRightOffset);
5446 mSelectDraggingCursor.set(oldX, oldY);
5449 private float distanceSquared(int x, int y, Point p) {
5452 return (dx * dx) + (dy * dy);
5455 private boolean setupWebkitSelect() {
5456 syncSelectionCursors();
5457 if (!mIsCaretSelection && !startSelectActionMode()) {
5461 mSelectingText = true;
5462 mTouchMode = TOUCH_DRAG_MODE;
5466 private void updateWebkitSelection() {
5467 int[] handles = null;
5468 if (mIsCaretSelection) {
5469 mSelectCursorExtent.set(mSelectCursorBase.x, mSelectCursorBase.y);
5471 if (mSelectingText) {
5472 handles = new int[4];
5473 handles[0] = mSelectCursorBase.x;
5474 handles[1] = mSelectCursorBase.y;
5475 handles[2] = mSelectCursorExtent.x;
5476 handles[3] = mSelectCursorExtent.y;
5478 nativeSetTextSelection(mNativeClass, 0);
5480 mWebViewCore.removeMessages(EventHub.SELECT_TEXT);
5481 mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SELECT_TEXT, handles);
5484 private void resetCaretTimer() {
5485 mPrivateHandler.removeMessages(CLEAR_CARET_HANDLE);
5486 if (!mSelectionStarted) {
5487 mPrivateHandler.sendEmptyMessageDelayed(CLEAR_CARET_HANDLE,
5488 CARET_HANDLE_STAMINA_MS);
5493 * See {@link WebView#emulateShiftHeld()}
5497 public void emulateShiftHeld() {
5501 * Select all of the text in this WebView.
5503 * This is an implementation detail.
5505 public void selectAll() {
5506 mWebViewCore.sendMessage(EventHub.SELECT_ALL);
5510 * Called when the selection has been removed.
5512 void selectionDone() {
5513 if (mSelectingText) {
5515 mSelectingText = false;
5516 // finish is idempotent, so this is fine even if selectionDone was
5517 // called by mSelectCallback.onDestroyActionMode
5518 if (mSelectCallback != null) {
5519 mSelectCallback.finish();
5520 mSelectCallback = null;
5522 if (!mIsCaretSelection) {
5523 updateWebkitSelection();
5525 invalidate(); // redraw without selection
5528 mSentAutoScrollMessage = false;
5533 * Copy the selection to the clipboard
5535 * This is an implementation detail.
5537 public boolean copySelection() {
5538 boolean copiedSomething = false;
5539 String selection = getSelection();
5540 if (selection != null && selection != "") {
5541 if (DebugFlags.WEB_VIEW) {
5542 Log.v(LOGTAG, "copySelection \"" + selection + "\"");
5544 Toast.makeText(mContext
5545 , com.android.internal.R.string.text_copied
5546 , Toast.LENGTH_SHORT).show();
5547 copiedSomething = true;
5548 ClipboardManager cm = (ClipboardManager)mContext
5549 .getSystemService(Context.CLIPBOARD_SERVICE);
5550 cm.setText(selection);
5551 int[] handles = new int[4];
5552 getSelectionHandles(handles);
5553 mWebViewCore.sendMessage(EventHub.COPY_TEXT, handles);
5555 invalidate(); // remove selection region and pointer
5556 return copiedSomething;
5560 * Cut the selected text into the clipboard
5562 * This is an implementation detail
5564 public void cutSelection() {
5566 int[] handles = new int[4];
5567 getSelectionHandles(handles);
5568 mWebViewCore.sendMessage(EventHub.DELETE_TEXT, handles);
5572 * Paste text from the clipboard to the cursor position.
5574 * This is an implementation detail
5576 public void pasteFromClipboard() {
5577 ClipboardManager cm = (ClipboardManager)mContext
5578 .getSystemService(Context.CLIPBOARD_SERVICE);
5579 ClipData clipData = cm.getPrimaryClip();
5580 if (clipData != null) {
5581 ClipData.Item clipItem = clipData.getItemAt(0);
5582 CharSequence pasteText = clipItem.getText();
5583 if (mInputConnection != null) {
5584 mInputConnection.replaceSelection(pasteText);
5590 * This is an implementation detail.
5592 public SearchBox getSearchBox() {
5593 if ((mWebViewCore == null) || (mWebViewCore.getBrowserFrame() == null)) {
5596 return mWebViewCore.getBrowserFrame().getSearchBox();
5600 * Returns the currently highlighted text as a string.
5602 String getSelection() {
5603 if (mNativeClass == 0) return "";
5604 return nativeGetSelection();
5608 public void onAttachedToWindow() {
5609 if (mWebView.hasWindowFocus()) setActive(true);
5610 final ViewTreeObserver treeObserver = mWebView.getViewTreeObserver();
5611 if (mGlobalLayoutListener == null) {
5612 mGlobalLayoutListener = new InnerGlobalLayoutListener();
5613 treeObserver.addOnGlobalLayoutListener(mGlobalLayoutListener);
5615 if (mScrollChangedListener == null) {
5616 mScrollChangedListener = new InnerScrollChangedListener();
5617 treeObserver.addOnScrollChangedListener(mScrollChangedListener);
5620 addAccessibilityApisToJavaScript();
5622 updateHwAccelerated();
5626 public void onDetachedFromWindow() {
5628 mZoomManager.dismissZoomPicker();
5629 if (mWebView.hasWindowFocus()) setActive(false);
5631 final ViewTreeObserver treeObserver = mWebView.getViewTreeObserver();
5632 if (mGlobalLayoutListener != null) {
5633 treeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener);
5634 mGlobalLayoutListener = null;
5636 if (mScrollChangedListener != null) {
5637 treeObserver.removeOnScrollChangedListener(mScrollChangedListener);
5638 mScrollChangedListener = null;
5641 removeAccessibilityApisFromJavaScript();
5642 updateHwAccelerated();
5646 public void onVisibilityChanged(View changedView, int visibility) {
5647 // The zoomManager may be null if the webview is created from XML that
5648 // specifies the view's visibility param as not visible (see http://b/2794841)
5649 if (visibility != View.VISIBLE && mZoomManager != null) {
5650 mZoomManager.dismissZoomPicker();
5652 updateDrawingState();
5655 void setActive(boolean active) {
5657 if (mWebView.hasFocus()) {
5658 // If our window regained focus, and we have focus, then begin
5659 // drawing the cursor ring
5660 mDrawCursorRing = true;
5661 setFocusControllerActive(true);
5663 mDrawCursorRing = false;
5664 setFocusControllerActive(false);
5667 if (!mZoomManager.isZoomPickerVisible()) {
5669 * The external zoom controls come in their own window, so our
5670 * window loses focus. Our policy is to not draw the cursor ring
5671 * if our window is not focused, but this is an exception since
5672 * the user can still navigate the web page with the zoom
5675 mDrawCursorRing = false;
5677 mKeysPressed.clear();
5678 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
5679 mTouchMode = TOUCH_DONE_MODE;
5680 setFocusControllerActive(false);
5685 // To avoid drawing the cursor ring, and remove the TextView when our window
5688 public void onWindowFocusChanged(boolean hasWindowFocus) {
5689 setActive(hasWindowFocus);
5690 if (hasWindowFocus) {
5691 JWebCoreJavaBridge.setActiveWebView(this);
5692 if (mPictureUpdatePausedForFocusChange) {
5693 WebViewCore.resumeUpdatePicture(mWebViewCore);
5694 mPictureUpdatePausedForFocusChange = false;
5697 JWebCoreJavaBridge.removeActiveWebView(this);
5698 final WebSettings settings = getSettings();
5699 if (settings != null && settings.enableSmoothTransition() &&
5700 mWebViewCore != null && !WebViewCore.isUpdatePicturePaused(mWebViewCore)) {
5701 WebViewCore.pauseUpdatePicture(mWebViewCore);
5702 mPictureUpdatePausedForFocusChange = true;
5708 * Pass a message to WebCore Thread, telling the WebCore::Page's
5709 * FocusController to be "inactive" so that it will
5710 * not draw the blinking cursor. It gets set to "active" to draw the cursor
5711 * in WebViewCore.cpp, when the WebCore thread receives key events/clicks.
5713 /* package */ void setFocusControllerActive(boolean active) {
5714 if (mWebViewCore == null) return;
5715 mWebViewCore.sendMessage(EventHub.SET_ACTIVE, active ? 1 : 0, 0);
5716 // Need to send this message after the document regains focus.
5717 if (active && mListBoxMessage != null) {
5718 mWebViewCore.sendMessage(mListBoxMessage);
5719 mListBoxMessage = null;
5724 public void onFocusChanged(boolean focused, int direction,
5725 Rect previouslyFocusedRect) {
5726 if (DebugFlags.WEB_VIEW) {
5727 Log.v(LOGTAG, "MT focusChanged " + focused + ", " + direction);
5730 mDrawCursorRing = true;
5731 setFocusControllerActive(true);
5733 mDrawCursorRing = false;
5734 setFocusControllerActive(false);
5735 mKeysPressed.clear();
5737 if (!mTouchHighlightRegion.isEmpty()) {
5738 mWebView.invalidate(mTouchHighlightRegion.getBounds());
5742 void setGLRectViewport() {
5743 // Use the getGlobalVisibleRect() to get the intersection among the parents
5744 // visible == false means we're clipped - send a null rect down to indicate that
5745 // we should not draw
5746 boolean visible = mWebView.getGlobalVisibleRect(mGLRectViewport);
5748 // Then need to invert the Y axis, just for GL
5749 View rootView = mWebView.getRootView();
5750 int rootViewHeight = rootView.getHeight();
5751 mViewRectViewport.set(mGLRectViewport);
5752 int savedWebViewBottom = mGLRectViewport.bottom;
5753 mGLRectViewport.bottom = rootViewHeight - mGLRectViewport.top - getVisibleTitleHeightImpl();
5754 mGLRectViewport.top = rootViewHeight - savedWebViewBottom;
5755 mGLViewportEmpty = false;
5757 mGLViewportEmpty = true;
5759 calcOurContentVisibleRectF(mVisibleContentRect);
5760 nativeUpdateDrawGLFunction(mGLViewportEmpty ? null : mGLRectViewport,
5761 mGLViewportEmpty ? null : mViewRectViewport,
5762 mVisibleContentRect, getScale());
5766 public boolean setFrame(int left, int top, int right, int bottom) {
5767 boolean changed = mWebViewPrivate.super_setFrame(left, top, right, bottom);
5768 if (!changed && mHeightCanMeasure) {
5769 // When mHeightCanMeasure is true, we will set mLastHeightSent to 0
5770 // in WebViewCore after we get the first layout. We do call
5771 // requestLayout() when we get contentSizeChanged(). But the View
5772 // system won't call onSizeChanged if the dimension is not changed.
5773 // In this case, we need to call sendViewSizeZoom() explicitly to
5774 // notify the WebKit about the new dimensions.
5775 sendViewSizeZoom(false);
5777 setGLRectViewport();
5782 public void onSizeChanged(int w, int h, int ow, int oh) {
5783 // adjust the max viewport width depending on the view dimensions. This
5784 // is to ensure the scaling is not going insane. So do not shrink it if
5785 // the view size is temporarily smaller, e.g. when soft keyboard is up.
5786 int newMaxViewportWidth = (int) (Math.max(w, h) / mZoomManager.getDefaultMinZoomScale());
5787 if (newMaxViewportWidth > sMaxViewportWidth) {
5788 sMaxViewportWidth = newMaxViewportWidth;
5791 mZoomManager.onSizeChanged(w, h, ow, oh);
5793 if (mLoadedPicture != null && mDelaySetPicture == null) {
5794 // Size changes normally result in a new picture
5795 // Re-set the loaded picture to simulate that
5796 // However, do not update the base layer as that hasn't changed
5797 setNewPicture(mLoadedPicture, false);
5799 relocateAutoCompletePopup();
5803 public void onScrollChanged(int l, int t, int oldl, int oldt) {
5804 if (!mInOverScrollMode) {
5805 sendOurVisibleRect();
5806 // update WebKit if visible title bar height changed. The logic is same
5807 // as getVisibleTitleHeightImpl.
5808 int titleHeight = getTitleHeight();
5809 if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) {
5810 sendViewSizeZoom(false);
5816 public boolean dispatchKeyEvent(KeyEvent event) {
5817 switch (event.getAction()) {
5818 case KeyEvent.ACTION_DOWN:
5819 mKeysPressed.add(Integer.valueOf(event.getKeyCode()));
5821 case KeyEvent.ACTION_MULTIPLE:
5822 // Always accept the action.
5824 case KeyEvent.ACTION_UP:
5825 int location = mKeysPressed.indexOf(Integer.valueOf(event.getKeyCode()));
5826 if (location == -1) {
5827 // We did not receive the key down for this key, so do not
5828 // handle the key up.
5831 // We did receive the key down. Handle the key up, and
5832 // remove it from our pressed keys.
5833 mKeysPressed.remove(location);
5837 // Accept the action. This should not happen, unless a new
5838 // action is added to KeyEvent.
5841 return mWebViewPrivate.super_dispatchKeyEvent(event);
5845 * Here is the snap align logic:
5846 * 1. If it starts nearly horizontally or vertically, snap align;
5847 * 2. If there is a dramitic direction change, let it go;
5849 * Adjustable parameters. Angle is the radians on a unit circle, limited
5850 * to quadrant 1. Values range from 0f (horizontal) to PI/2 (vertical)
5852 private static final float HSLOPE_TO_START_SNAP = .25f;
5853 private static final float HSLOPE_TO_BREAK_SNAP = .4f;
5854 private static final float VSLOPE_TO_START_SNAP = 1.25f;
5855 private static final float VSLOPE_TO_BREAK_SNAP = .95f;
5857 * These values are used to influence the average angle when entering
5858 * snap mode. If is is the first movement entering snap, we set the average
5859 * to the appropriate ideal. If the user is entering into snap after the
5860 * first movement, then we average the average angle with these values.
5862 private static final float ANGLE_VERT = 2f;
5863 private static final float ANGLE_HORIZ = 0f;
5865 * The modified moving average weight.
5866 * Formula: MAV[t]=MAV[t-1] + (P[t]-MAV[t-1])/n
5868 private static final float MMA_WEIGHT_N = 5;
5870 private boolean inFullScreenMode() {
5871 return mFullScreenHolder != null;
5874 private void dismissFullScreenMode() {
5875 if (inFullScreenMode()) {
5876 mFullScreenHolder.hide();
5877 mFullScreenHolder = null;
5882 void onPinchToZoomAnimationStart() {
5883 // cancel the single touch handling
5885 onZoomAnimationStart();
5888 void onPinchToZoomAnimationEnd(ScaleGestureDetector detector) {
5889 onZoomAnimationEnd();
5890 // start a drag, TOUCH_PINCH_DRAG, can't use TOUCH_INIT_MODE as
5891 // it may trigger the unwanted click, can't use TOUCH_DRAG_MODE
5892 // as it may trigger the unwanted fling.
5893 mTouchMode = TOUCH_PINCH_DRAG;
5894 mConfirmMove = true;
5895 startTouch(detector.getFocusX(), detector.getFocusY(), mLastTouchTime);
5898 // See if there is a layer at x, y and switch to TOUCH_DRAG_LAYER_MODE if a
5900 private void startScrollingLayer(float x, float y) {
5901 int contentX = viewToContentX((int) x + getScrollX());
5902 int contentY = viewToContentY((int) y + getScrollY());
5903 mCurrentScrollingLayerId = nativeScrollableLayer(contentX, contentY,
5904 mScrollingLayerRect, mScrollingLayerBounds);
5905 if (mCurrentScrollingLayerId != 0) {
5906 mTouchMode = TOUCH_DRAG_LAYER_MODE;
5910 // 1/(density * density) used to compute the distance between points.
5911 // Computed in init().
5912 private float DRAG_LAYER_INVERSE_DENSITY_SQUARED;
5914 // The distance between two points reported in onTouchEvent scaled by the
5915 // density of the screen.
5916 private static final int DRAG_LAYER_FINGER_DISTANCE = 20000;
5919 public boolean onHoverEvent(MotionEvent event) {
5920 if (mNativeClass == 0) {
5923 int x = viewToContentX((int) event.getX() + getScrollX());
5924 int y = viewToContentY((int) event.getY() + getScrollY());
5925 mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, x, y);
5930 public boolean onTouchEvent(MotionEvent ev) {
5931 if (mNativeClass == 0 || (!mWebView.isClickable() && !mWebView.isLongClickable())) {
5935 if (!mWebView.isFocused()) {
5936 mWebView.requestFocus();
5939 if (mInputDispatcher == null) {
5943 if (mInputDispatcher.postPointerEvent(ev, getScrollX(),
5944 getScrollY() - getTitleHeight(), mZoomManager.getInvScale())) {
5947 Log.w(LOGTAG, "mInputDispatcher rejected the event!");
5952 private float calculateDragAngle(int dx, int dy) {
5955 return (float) Math.atan2(dy, dx);
5959 * Common code for single touch and multi-touch.
5960 * (x, y) denotes current focus point, which is the touch point for single touch
5961 * and the middle point for multi-touch.
5963 private void handleTouchEventCommon(MotionEvent event, int action, int x, int y) {
5964 ScaleGestureDetector detector = mZoomManager.getMultiTouchGestureDetector();
5966 long eventTime = event.getEventTime();
5968 // Due to the touch screen edge effect, a touch closer to the edge
5969 // always snapped to the edge. As getViewWidth() can be different from
5970 // getWidth() due to the scrollbar, adjusting the point to match
5971 // getViewWidth(). Same applied to the height.
5972 x = Math.min(x, getViewWidth() - 1);
5973 y = Math.min(y, getViewHeightWithTitle() - 1);
5975 int deltaX = mLastTouchX - x;
5976 int deltaY = mLastTouchY - y;
5977 int contentX = viewToContentX(x + getScrollX());
5978 int contentY = viewToContentY(y + getScrollY());
5981 case MotionEvent.ACTION_DOWN: {
5982 mConfirmMove = false;
5983 mInitialHitTestResult = null;
5984 if (!mEditTextScroller.isFinished()) {
5985 mEditTextScroller.abortAnimation();
5987 if (!mScroller.isFinished()) {
5988 // stop the current scroll animation, but if this is
5989 // the start of a fling, allow it to add to the current
5991 mScroller.abortAnimation();
5992 mTouchMode = TOUCH_DRAG_START_MODE;
5993 mConfirmMove = true;
5994 nativeSetIsScrolling(false);
5995 } else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) {
5996 mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP);
5997 removeTouchHighlight();
5998 if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) {
5999 mTouchMode = TOUCH_DOUBLE_TAP_MODE;
6001 mTouchMode = TOUCH_INIT_MODE;
6003 } else { // the normal case
6004 mTouchMode = TOUCH_INIT_MODE;
6005 // TODO: Have WebViewInputDispatch handle this
6006 TouchHighlightData data = new TouchHighlightData();
6009 data.mNativeLayerRect = new Rect();
6010 data.mNativeLayer = nativeScrollableLayer(
6011 contentX, contentY, data.mNativeLayerRect, null);
6012 data.mSlop = viewToContentDimension(mNavSlop);
6013 removeTouchHighlight();
6014 if (!mBlockWebkitViewMessages) {
6015 mTouchHighlightRequested = SystemClock.uptimeMillis();
6016 mWebViewCore.sendMessageAtFrontOfQueue(
6017 EventHub.HIT_TEST, data);
6019 if (mLogEvent && eventTime - mLastTouchUpTime < 1000) {
6020 EventLog.writeEvent(EventLogTags.BROWSER_DOUBLE_TAP_DURATION,
6021 (eventTime - mLastTouchUpTime), eventTime);
6023 mSelectionStarted = false;
6024 if (mSelectingText) {
6025 ensureSelectionHandles();
6026 int shiftedY = y - getTitleHeight() + getScrollY();
6027 int shiftedX = x + getScrollX();
6028 if (mSelectHandleCenter != null && mSelectHandleCenter.getBounds()
6029 .contains(shiftedX, shiftedY)) {
6030 mSelectionStarted = true;
6031 mSelectDraggingCursor = mSelectCursorBase;
6032 mSelectDraggingOffset = mSelectHandleCenterOffset;
6033 mSelectDraggingTextQuad = mSelectCursorBaseTextQuad;
6034 mPrivateHandler.removeMessages(CLEAR_CARET_HANDLE);
6036 } else if (mSelectHandleLeft != null
6037 && mSelectHandleLeft.getBounds()
6038 .contains(shiftedX, shiftedY)) {
6039 mSelectionStarted = true;
6040 mSelectDraggingCursor = mSelectCursorBase;
6041 mSelectDraggingOffset = mSelectHandleLeftOffset;
6042 mSelectDraggingTextQuad = mSelectCursorBaseTextQuad;
6043 } else if (mSelectHandleRight != null
6044 && mSelectHandleRight.getBounds()
6045 .contains(shiftedX, shiftedY)) {
6046 mSelectionStarted = true;
6047 mSelectDraggingCursor = mSelectCursorExtent;
6048 mSelectDraggingOffset = mSelectHandleRightOffset;
6049 mSelectDraggingTextQuad = mSelectCursorExtentTextQuad;
6050 } else if (mIsCaretSelection) {
6053 if (DebugFlags.WEB_VIEW) {
6054 Log.v(LOGTAG, "select=" + contentX + "," + contentY);
6059 if (!mSelectingText && (mTouchMode == TOUCH_INIT_MODE
6060 || mTouchMode == TOUCH_DOUBLE_TAP_MODE)) {
6061 mPrivateHandler.sendEmptyMessageDelayed(
6062 SWITCH_TO_SHORTPRESS, TAP_TIMEOUT);
6063 mPrivateHandler.sendEmptyMessageDelayed(
6064 SWITCH_TO_LONGPRESS, LONG_PRESS_TIMEOUT);
6066 startTouch(x, y, eventTime);
6067 if (mIsEditingText) {
6068 mTouchInEditText = mEditTextContentBounds
6069 .contains(contentX, contentY);
6073 case MotionEvent.ACTION_MOVE: {
6074 if (!mConfirmMove && (deltaX * deltaX + deltaY * deltaY)
6075 >= mTouchSlopSquare) {
6076 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
6077 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
6078 mConfirmMove = true;
6079 if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
6080 mTouchMode = TOUCH_INIT_MODE;
6082 removeTouchHighlight();
6084 if (mSelectingText && mSelectionStarted) {
6085 if (DebugFlags.WEB_VIEW) {
6086 Log.v(LOGTAG, "extend=" + contentX + "," + contentY);
6088 ViewParent parent = mWebView.getParent();
6089 if (parent != null) {
6090 parent.requestDisallowInterceptTouchEvent(true);
6092 if (deltaX != 0 || deltaY != 0) {
6093 int handleX = contentX +
6094 viewToContentDimension(mSelectDraggingOffset.x);
6095 int handleY = contentY +
6096 viewToContentDimension(mSelectDraggingOffset.y);
6097 mSelectDraggingCursor.set(handleX, handleY);
6098 boolean inCursorText =
6099 mSelectDraggingTextQuad.containsPoint(handleX, handleY);
6100 boolean inEditBounds = mEditTextContentBounds
6101 .contains(handleX, handleY);
6102 if (mIsEditingText && !inEditBounds) {
6107 if (inCursorText || (mIsEditingText && !inEditBounds)) {
6108 snapDraggingCursor();
6110 updateWebkitSelection();
6111 if (!inCursorText && mIsEditingText && inEditBounds) {
6112 // Visually snap even if we have moved the handle.
6113 snapDraggingCursor();
6122 if (mTouchMode == TOUCH_DONE_MODE) {
6123 // no dragging during scroll zoom animation, or when prevent
6127 if (mVelocityTracker == null) {
6128 Log.e(LOGTAG, "Got null mVelocityTracker when "
6129 + " mTouchMode = " + mTouchMode);
6131 mVelocityTracker.addMovement(event);
6134 if (mTouchMode != TOUCH_DRAG_MODE &&
6135 mTouchMode != TOUCH_DRAG_LAYER_MODE &&
6136 mTouchMode != TOUCH_DRAG_TEXT_MODE) {
6138 if (!mConfirmMove) {
6142 // Only lock dragging to one axis if we don't have a scale in progress.
6143 // Scaling implies free-roaming movement. Note this is only ever a question
6144 // if mZoomManager.supportsPanDuringZoom() is true.
6145 mAverageAngle = calculateDragAngle(deltaX, deltaY);
6146 if (detector == null || !detector.isInProgress()) {
6147 // if it starts nearly horizontal or vertical, enforce it
6148 if (mAverageAngle < HSLOPE_TO_START_SNAP) {
6149 mSnapScrollMode = SNAP_X;
6150 mSnapPositive = deltaX > 0;
6151 mAverageAngle = ANGLE_HORIZ;
6152 } else if (mAverageAngle > VSLOPE_TO_START_SNAP) {
6153 mSnapScrollMode = SNAP_Y;
6154 mSnapPositive = deltaY > 0;
6155 mAverageAngle = ANGLE_VERT;
6159 mTouchMode = TOUCH_DRAG_MODE;
6165 startScrollingLayer(x, y);
6170 boolean keepScrollBarsVisible = false;
6171 if (deltaX == 0 && deltaY == 0) {
6172 keepScrollBarsVisible = true;
6175 (calculateDragAngle(deltaX, deltaY) - mAverageAngle)
6177 if (mSnapScrollMode != SNAP_NONE) {
6178 if (mSnapScrollMode == SNAP_Y) {
6179 // radical change means getting out of snap mode
6180 if (mAverageAngle < VSLOPE_TO_BREAK_SNAP) {
6181 mSnapScrollMode = SNAP_NONE;
6184 if (mSnapScrollMode == SNAP_X) {
6185 // radical change means getting out of snap mode
6186 if (mAverageAngle > HSLOPE_TO_BREAK_SNAP) {
6187 mSnapScrollMode = SNAP_NONE;
6191 if (mAverageAngle < HSLOPE_TO_START_SNAP) {
6192 mSnapScrollMode = SNAP_X;
6193 mSnapPositive = deltaX > 0;
6194 mAverageAngle = (mAverageAngle + ANGLE_HORIZ) / 2;
6195 } else if (mAverageAngle > VSLOPE_TO_START_SNAP) {
6196 mSnapScrollMode = SNAP_Y;
6197 mSnapPositive = deltaY > 0;
6198 mAverageAngle = (mAverageAngle + ANGLE_VERT) / 2;
6201 if (mSnapScrollMode != SNAP_NONE) {
6202 if ((mSnapScrollMode & SNAP_X) == SNAP_X) {
6208 if (deltaX * deltaX + deltaY * deltaY > mTouchSlopSquare) {
6209 mHeldMotionless = MOTIONLESS_FALSE;
6210 nativeSetIsScrolling(true);
6212 mHeldMotionless = MOTIONLESS_TRUE;
6213 nativeSetIsScrolling(false);
6214 keepScrollBarsVisible = true;
6217 mLastTouchTime = eventTime;
6218 boolean allDrag = doDrag(deltaX, deltaY);
6223 int contentDeltaX = (int)Math.floor(deltaX * mZoomManager.getInvScale());
6224 int roundedDeltaX = contentToViewDimension(contentDeltaX);
6225 int contentDeltaY = (int)Math.floor(deltaY * mZoomManager.getInvScale());
6226 int roundedDeltaY = contentToViewDimension(contentDeltaY);
6227 mLastTouchX -= roundedDeltaX;
6228 mLastTouchY -= roundedDeltaY;
6232 // Turn off scrollbars when dragging a layer.
6233 if (keepScrollBarsVisible &&
6234 mTouchMode != TOUCH_DRAG_LAYER_MODE &&
6235 mTouchMode != TOUCH_DRAG_TEXT_MODE) {
6236 if (mHeldMotionless != MOTIONLESS_TRUE) {
6237 mHeldMotionless = MOTIONLESS_TRUE;
6240 // keep the scrollbar on the screen even there is no scroll
6241 mWebViewPrivate.awakenScrollBars(ViewConfiguration.getScrollDefaultDelay(),
6243 // Post a message so that we'll keep them alive while we're not scrolling.
6244 mPrivateHandler.sendMessageDelayed(mPrivateHandler
6245 .obtainMessage(AWAKEN_SCROLL_BARS),
6246 ViewConfiguration.getScrollDefaultDelay());
6247 // return false to indicate that we can't pan out of the
6251 mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
6255 case MotionEvent.ACTION_UP: {
6257 if (!mConfirmMove && mIsEditingText && mSelectionStarted &&
6258 mIsCaretSelection) {
6263 mLastTouchUpTime = eventTime;
6264 if (mSentAutoScrollMessage) {
6265 mAutoScrollX = mAutoScrollY = 0;
6267 switch (mTouchMode) {
6268 case TOUCH_DOUBLE_TAP_MODE: // double tap
6269 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
6270 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
6271 mTouchMode = TOUCH_DONE_MODE;
6273 case TOUCH_INIT_MODE: // tap
6274 case TOUCH_SHORTPRESS_START_MODE:
6275 case TOUCH_SHORTPRESS_MODE:
6276 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
6277 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
6278 if (!mConfirmMove) {
6279 if (mSelectingText) {
6280 // tapping on selection or controls does nothing
6281 if (!mSelectionStarted) {
6286 // only trigger double tap if the WebView is
6288 if (mTouchMode == TOUCH_INIT_MODE
6289 && (canZoomIn() || canZoomOut())) {
6290 mPrivateHandler.sendEmptyMessageDelayed(
6291 RELEASE_SINGLE_TAP, ViewConfiguration
6292 .getDoubleTapTimeout());
6296 case TOUCH_DRAG_MODE:
6297 case TOUCH_DRAG_LAYER_MODE:
6298 case TOUCH_DRAG_TEXT_MODE:
6299 mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
6300 mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
6301 // if the user waits a while w/o moving before the
6302 // up, we don't want to do a fling
6303 if (eventTime - mLastTouchTime <= MIN_FLING_TIME) {
6304 if (mVelocityTracker == null) {
6305 Log.e(LOGTAG, "Got null mVelocityTracker");
6307 mVelocityTracker.addMovement(event);
6309 // set to MOTIONLESS_IGNORE so that it won't keep
6310 // removing and sending message in
6311 // drawCoreAndCursorRing()
6312 mHeldMotionless = MOTIONLESS_IGNORE;
6316 if (mScroller.springBack(getScrollX(), getScrollY(), 0,
6317 computeMaxScrollX(), 0,
6318 computeMaxScrollY())) {
6322 // redraw in high-quality, as we're done dragging
6323 mHeldMotionless = MOTIONLESS_TRUE;
6326 case TOUCH_DRAG_START_MODE:
6327 // TOUCH_DRAG_START_MODE should not happen for the real
6328 // device as we almost certain will get a MOVE. But this
6329 // is possible on emulator.
6331 WebViewCore.resumePriority();
6332 if (!mSelectingText) {
6333 WebViewCore.resumeUpdatePicture(mWebViewCore);
6340 case MotionEvent.ACTION_CANCEL: {
6341 if (mTouchMode == TOUCH_DRAG_MODE) {
6342 mScroller.springBack(getScrollX(), getScrollY(), 0,
6343 computeMaxScrollX(), 0, computeMaxScrollY());
6353 * Returns the text scroll speed in content pixels per millisecond based on
6354 * the touch location.
6355 * @param coordinate The x or y touch coordinate in content space
6356 * @param min The minimum coordinate (x or y) of the edit content bounds
6357 * @param max The maximum coordinate (x or y) of the edit content bounds
6359 private static float getTextScrollSpeed(int coordinate, int min, int max) {
6360 if (coordinate < min) {
6361 return (coordinate - min) * TEXT_SCROLL_RATE;
6362 } else if (coordinate >= max) {
6363 return (coordinate - max + 1) * TEXT_SCROLL_RATE;
6369 private void beginScrollEdit() {
6370 if (mLastEditScroll == 0) {
6371 mLastEditScroll = SystemClock.uptimeMillis() -
6372 TEXT_SCROLL_FIRST_SCROLL_MS;
6373 scrollEditWithCursor();
6377 private void endScrollEdit() {
6378 mLastEditScroll = 0;
6381 private static int getTextScrollDelta(float speed, long deltaT) {
6382 float distance = speed * deltaT;
6383 int intDistance = (int)Math.floor(distance);
6384 float probability = distance - intDistance;
6385 if (Math.random() < probability) {
6391 * Scrolls edit text a distance based on the last touch point,
6392 * the last scroll time, and the edit text content bounds.
6394 private void scrollEditWithCursor() {
6395 if (mLastEditScroll != 0) {
6396 int x = viewToContentX(mLastTouchX + getScrollX() + mSelectDraggingOffset.x);
6397 float scrollSpeedX = getTextScrollSpeed(x, mEditTextContentBounds.left,
6398 mEditTextContentBounds.right);
6399 int y = viewToContentY(mLastTouchY + getScrollY() + mSelectDraggingOffset.y);
6400 float scrollSpeedY = getTextScrollSpeed(y, mEditTextContentBounds.top,
6401 mEditTextContentBounds.bottom);
6402 if (scrollSpeedX == 0.0f && scrollSpeedY == 0.0f) {
6405 long currentTime = SystemClock.uptimeMillis();
6406 long timeSinceLastUpdate = currentTime - mLastEditScroll;
6407 int deltaX = getTextScrollDelta(scrollSpeedX, timeSinceLastUpdate);
6408 int deltaY = getTextScrollDelta(scrollSpeedY, timeSinceLastUpdate);
6409 mLastEditScroll = currentTime;
6410 if (deltaX == 0 && deltaY == 0) {
6411 // By probability no text scroll this time. Try again later.
6412 mPrivateHandler.sendEmptyMessageDelayed(SCROLL_EDIT_TEXT,
6413 TEXT_SCROLL_FIRST_SCROLL_MS);
6415 int scrollX = getTextScrollX() + deltaX;
6416 scrollX = Math.min(getMaxTextScrollX(), scrollX);
6417 scrollX = Math.max(0, scrollX);
6418 int scrollY = getTextScrollY() + deltaY;
6419 scrollY = Math.min(getMaxTextScrollY(), scrollY);
6420 scrollY = Math.max(0, scrollY);
6421 scrollEditText(scrollX, scrollY);
6422 int cursorX = mSelectDraggingCursor.x;
6423 int cursorY = mSelectDraggingCursor.y;
6424 mSelectDraggingCursor.set(x - deltaX, y - deltaY);
6425 updateWebkitSelection();
6426 mSelectDraggingCursor.set(cursorX, cursorY);
6432 private void startTouch(float x, float y, long eventTime) {
6433 // Remember where the motion event started
6434 mStartTouchX = mLastTouchX = Math.round(x);
6435 mStartTouchY = mLastTouchY = Math.round(y);
6436 mLastTouchTime = eventTime;
6437 mVelocityTracker = VelocityTracker.obtain();
6438 mSnapScrollMode = SNAP_NONE;
6441 private void startDrag() {
6442 WebViewCore.reducePriority();
6443 // to get better performance, pause updating the picture
6444 WebViewCore.pauseUpdatePicture(mWebViewCore);
6445 nativeSetIsScrolling(true);
6447 if (mHorizontalScrollBarMode != SCROLLBAR_ALWAYSOFF
6448 || mVerticalScrollBarMode != SCROLLBAR_ALWAYSOFF) {
6449 mZoomManager.invokeZoomPicker();
6453 private boolean doDrag(int deltaX, int deltaY) {
6454 boolean allDrag = true;
6455 if ((deltaX | deltaY) != 0) {
6456 int oldX = getScrollX();
6457 int oldY = getScrollY();
6458 int rangeX = computeMaxScrollX();
6459 int rangeY = computeMaxScrollY();
6460 final int contentX = (int)Math.floor(deltaX * mZoomManager.getInvScale());
6461 final int contentY = (int)Math.floor(deltaY * mZoomManager.getInvScale());
6463 // Assume page scrolling and change below if we're wrong
6464 mTouchMode = TOUCH_DRAG_MODE;
6466 // Check special scrolling before going to main page scrolling.
6467 if (mIsEditingText && mTouchInEditText && canTextScroll(deltaX, deltaY)) {
6468 // Edit text scrolling
6469 oldX = getTextScrollX();
6470 rangeX = getMaxTextScrollX();
6472 oldY = getTextScrollY();
6473 rangeY = getMaxTextScrollY();
6475 mTouchMode = TOUCH_DRAG_TEXT_MODE;
6477 } else if (mCurrentScrollingLayerId != 0) {
6478 // Check the scrolling bounds to see if we will actually do any
6479 // scrolling. The rectangle is in document coordinates.
6480 final int maxX = mScrollingLayerRect.right;
6481 final int maxY = mScrollingLayerRect.bottom;
6482 final int resultX = Math.max(0,
6483 Math.min(mScrollingLayerRect.left + contentX, maxX));
6484 final int resultY = Math.max(0,
6485 Math.min(mScrollingLayerRect.top + contentY, maxY));
6487 if (resultX != mScrollingLayerRect.left ||
6488 resultY != mScrollingLayerRect.top) {
6489 // In case we switched to dragging the page.
6490 mTouchMode = TOUCH_DRAG_LAYER_MODE;
6493 oldX = mScrollingLayerRect.left;
6494 oldY = mScrollingLayerRect.top;
6501 if (mOverScrollGlow != null) {
6502 mOverScrollGlow.setOverScrollDeltas(deltaX, deltaY);
6505 mWebViewPrivate.overScrollBy(deltaX, deltaY, oldX, oldY,
6507 mOverscrollDistance, mOverscrollDistance, true);
6508 if (mOverScrollGlow != null && mOverScrollGlow.isAnimating()) {
6512 mZoomManager.keepZoomPickerVisible();
6516 private void stopTouch() {
6517 if (mScroller.isFinished() && !mSelectingText
6518 && (mTouchMode == TOUCH_DRAG_MODE
6519 || mTouchMode == TOUCH_DRAG_LAYER_MODE)) {
6520 WebViewCore.resumePriority();
6521 WebViewCore.resumeUpdatePicture(mWebViewCore);
6522 nativeSetIsScrolling(false);
6525 // we also use mVelocityTracker == null to tell us that we are
6526 // not "moving around", so we can take the slower/prettier
6527 // mode in the drawing code
6528 if (mVelocityTracker != null) {
6529 mVelocityTracker.recycle();
6530 mVelocityTracker = null;
6533 // Release any pulled glows
6534 if (mOverScrollGlow != null) {
6535 mOverScrollGlow.releaseAll();
6538 if (mSelectingText) {
6539 mSelectionStarted = false;
6540 syncSelectionCursors();
6541 if (mIsCaretSelection) {
6548 private void cancelTouch() {
6549 // we also use mVelocityTracker == null to tell us that we are
6550 // not "moving around", so we can take the slower/prettier
6551 // mode in the drawing code
6552 if (mVelocityTracker != null) {
6553 mVelocityTracker.recycle();
6554 mVelocityTracker = null;
6557 if ((mTouchMode == TOUCH_DRAG_MODE
6558 || mTouchMode == TOUCH_DRAG_LAYER_MODE) && !mSelectingText) {
6559 WebViewCore.resumePriority();
6560 WebViewCore.resumeUpdatePicture(mWebViewCore);
6561 nativeSetIsScrolling(false);
6563 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
6564 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
6565 mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
6566 mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
6567 removeTouchHighlight();
6568 mHeldMotionless = MOTIONLESS_TRUE;
6569 mTouchMode = TOUCH_DONE_MODE;
6572 private void snapDraggingCursor() {
6573 float scale = scaleAlongSegment(
6574 mSelectDraggingCursor.x, mSelectDraggingCursor.y,
6575 mSelectDraggingTextQuad.p4, mSelectDraggingTextQuad.p3);
6576 // clamp scale to ensure point is on the bottom segment
6577 scale = Math.max(0.0f, scale);
6578 scale = Math.min(scale, 1.0f);
6579 float newX = scaleCoordinate(scale,
6580 mSelectDraggingTextQuad.p4.x, mSelectDraggingTextQuad.p3.x);
6581 float newY = scaleCoordinate(scale,
6582 mSelectDraggingTextQuad.p4.y, mSelectDraggingTextQuad.p3.y);
6583 int x = Math.max(mEditTextContentBounds.left,
6584 Math.min(mEditTextContentBounds.right, Math.round(newX)));
6585 int y = Math.max(mEditTextContentBounds.top,
6586 Math.min(mEditTextContentBounds.bottom, Math.round(newY)));
6587 mSelectDraggingCursor.set(x, y);
6590 private static float scaleCoordinate(float scale, float coord1, float coord2) {
6591 float diff = coord2 - coord1;
6592 return coord1 + (scale * diff);
6596 public boolean onGenericMotionEvent(MotionEvent event) {
6597 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
6598 switch (event.getAction()) {
6599 case MotionEvent.ACTION_SCROLL: {
6600 final float vscroll;
6601 final float hscroll;
6602 if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
6604 hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
6606 vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
6607 hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
6609 if (hscroll != 0 || vscroll != 0) {
6610 final int vdelta = (int) (vscroll *
6611 mWebViewPrivate.getVerticalScrollFactor());
6612 final int hdelta = (int) (hscroll *
6613 mWebViewPrivate.getHorizontalScrollFactor());
6614 if (pinScrollBy(hdelta, vdelta, false, 0)) {
6621 return mWebViewPrivate.super_onGenericMotionEvent(event);
6624 private long mTrackballFirstTime = 0;
6625 private long mTrackballLastTime = 0;
6626 private float mTrackballRemainsX = 0.0f;
6627 private float mTrackballRemainsY = 0.0f;
6628 private int mTrackballXMove = 0;
6629 private int mTrackballYMove = 0;
6630 private boolean mSelectingText = false;
6631 private boolean mSelectionStarted = false;
6632 private static final int TRACKBALL_KEY_TIMEOUT = 1000;
6633 private static final int TRACKBALL_TIMEOUT = 200;
6634 private static final int TRACKBALL_WAIT = 100;
6635 private static final int TRACKBALL_SCALE = 400;
6636 private static final int TRACKBALL_SCROLL_COUNT = 5;
6637 private static final int TRACKBALL_MOVE_COUNT = 10;
6638 private static final int TRACKBALL_MULTIPLIER = 3;
6639 private static final int SELECT_CURSOR_OFFSET = 16;
6640 private static final int SELECT_SCROLL = 5;
6641 private int mSelectX = 0;
6642 private int mSelectY = 0;
6643 private boolean mTrackballDown = false;
6644 private long mTrackballUpTime = 0;
6645 private long mLastCursorTime = 0;
6646 private Rect mLastCursorBounds;
6648 // Set by default; BrowserActivity clears to interpret trackball data
6649 // directly for movement. Currently, the framework only passes
6650 // arrow key events, not trackball events, from one child to the next
6651 private boolean mMapTrackballToArrowKeys = true;
6653 private DrawData mDelaySetPicture;
6654 private DrawData mLoadedPicture;
6656 public void setMapTrackballToArrowKeys(boolean setMap) {
6657 mMapTrackballToArrowKeys = setMap;
6660 void resetTrackballTime() {
6661 mTrackballLastTime = 0;
6665 public boolean onTrackballEvent(MotionEvent ev) {
6666 long time = ev.getEventTime();
6667 if ((ev.getMetaState() & KeyEvent.META_ALT_ON) != 0) {
6668 if (ev.getY() > 0) pageDown(true);
6669 if (ev.getY() < 0) pageUp(true);
6672 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
6673 if (mSelectingText) {
6674 return true; // discard press if copy in progress
6676 mTrackballDown = true;
6677 if (mNativeClass == 0) {
6680 if (DebugFlags.WEB_VIEW) {
6681 Log.v(LOGTAG, "onTrackballEvent down ev=" + ev
6683 + " mLastCursorTime=" + mLastCursorTime);
6685 if (mWebView.isInTouchMode()) mWebView.requestFocusFromTouch();
6686 return false; // let common code in onKeyDown at it
6688 if (ev.getAction() == MotionEvent.ACTION_UP) {
6689 // LONG_PRESS_CENTER is set in common onKeyDown
6690 mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
6691 mTrackballDown = false;
6692 mTrackballUpTime = time;
6693 if (mSelectingText) {
6696 return true; // discard press if copy in progress
6698 if (DebugFlags.WEB_VIEW) {
6699 Log.v(LOGTAG, "onTrackballEvent up ev=" + ev
6703 return false; // let common code in onKeyUp at it
6705 if ((mMapTrackballToArrowKeys && (ev.getMetaState() & KeyEvent.META_SHIFT_ON) == 0) ||
6706 AccessibilityManager.getInstance(mContext).isEnabled()) {
6707 if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent gmail quit");
6710 if (mTrackballDown) {
6711 if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent down quit");
6712 return true; // discard move if trackball is down
6714 if (time - mTrackballUpTime < TRACKBALL_TIMEOUT) {
6715 if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent up timeout quit");
6718 // TODO: alternatively we can do panning as touch does
6719 switchOutDrawHistory();
6720 if (time - mTrackballLastTime > TRACKBALL_TIMEOUT) {
6721 if (DebugFlags.WEB_VIEW) {
6722 Log.v(LOGTAG, "onTrackballEvent time="
6723 + time + " last=" + mTrackballLastTime);
6725 mTrackballFirstTime = time;
6726 mTrackballXMove = mTrackballYMove = 0;
6728 mTrackballLastTime = time;
6729 if (DebugFlags.WEB_VIEW) {
6730 Log.v(LOGTAG, "onTrackballEvent ev=" + ev + " time=" + time);
6732 mTrackballRemainsX += ev.getX();
6733 mTrackballRemainsY += ev.getY();
6734 doTrackball(time, ev.getMetaState());
6738 private int scaleTrackballX(float xRate, int width) {
6739 int xMove = (int) (xRate / TRACKBALL_SCALE * width);
6740 int nextXMove = xMove;
6742 if (xMove > mTrackballXMove) {
6743 xMove -= mTrackballXMove;
6745 } else if (xMove < mTrackballXMove) {
6746 xMove -= mTrackballXMove;
6748 mTrackballXMove = nextXMove;
6752 private int scaleTrackballY(float yRate, int height) {
6753 int yMove = (int) (yRate / TRACKBALL_SCALE * height);
6754 int nextYMove = yMove;
6756 if (yMove > mTrackballYMove) {
6757 yMove -= mTrackballYMove;
6759 } else if (yMove < mTrackballYMove) {
6760 yMove -= mTrackballYMove;
6762 mTrackballYMove = nextYMove;
6766 private int keyCodeToSoundsEffect(int keyCode) {
6768 case KeyEvent.KEYCODE_DPAD_UP:
6769 return SoundEffectConstants.NAVIGATION_UP;
6770 case KeyEvent.KEYCODE_DPAD_RIGHT:
6771 return SoundEffectConstants.NAVIGATION_RIGHT;
6772 case KeyEvent.KEYCODE_DPAD_DOWN:
6773 return SoundEffectConstants.NAVIGATION_DOWN;
6774 case KeyEvent.KEYCODE_DPAD_LEFT:
6775 return SoundEffectConstants.NAVIGATION_LEFT;
6780 private void doTrackball(long time, int metaState) {
6781 int elapsed = (int) (mTrackballLastTime - mTrackballFirstTime);
6783 elapsed = TRACKBALL_TIMEOUT;
6785 float xRate = mTrackballRemainsX * 1000 / elapsed;
6786 float yRate = mTrackballRemainsY * 1000 / elapsed;
6787 int viewWidth = getViewWidth();
6788 int viewHeight = getViewHeight();
6789 float ax = Math.abs(xRate);
6790 float ay = Math.abs(yRate);
6791 float maxA = Math.max(ax, ay);
6792 if (DebugFlags.WEB_VIEW) {
6793 Log.v(LOGTAG, "doTrackball elapsed=" + elapsed
6796 + " mTrackballRemainsX=" + mTrackballRemainsX
6797 + " mTrackballRemainsY=" + mTrackballRemainsY);
6799 int width = mContentWidth - viewWidth;
6800 int height = mContentHeight - viewHeight;
6801 if (width < 0) width = 0;
6802 if (height < 0) height = 0;
6803 ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER);
6804 ay = Math.abs(mTrackballRemainsY * TRACKBALL_MULTIPLIER);
6805 maxA = Math.max(ax, ay);
6806 int count = Math.max(0, (int) maxA);
6807 int oldScrollX = getScrollX();
6808 int oldScrollY = getScrollY();
6810 int selectKeyCode = ax < ay ? mTrackballRemainsY < 0 ?
6811 KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN :
6812 mTrackballRemainsX < 0 ? KeyEvent.KEYCODE_DPAD_LEFT :
6813 KeyEvent.KEYCODE_DPAD_RIGHT;
6814 count = Math.min(count, TRACKBALL_MOVE_COUNT);
6815 if (DebugFlags.WEB_VIEW) {
6816 Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode
6818 + " mTrackballRemainsX=" + mTrackballRemainsX
6819 + " mTrackballRemainsY=" + mTrackballRemainsY);
6821 if (mNativeClass != 0) {
6822 for (int i = 0; i < count; i++) {
6823 letPageHandleNavKey(selectKeyCode, time, true, metaState);
6825 letPageHandleNavKey(selectKeyCode, time, false, metaState);
6827 mTrackballRemainsX = mTrackballRemainsY = 0;
6829 if (count >= TRACKBALL_SCROLL_COUNT) {
6830 int xMove = scaleTrackballX(xRate, width);
6831 int yMove = scaleTrackballY(yRate, height);
6832 if (DebugFlags.WEB_VIEW) {
6833 Log.v(LOGTAG, "doTrackball pinScrollBy"
6835 + " xMove=" + xMove + " yMove=" + yMove
6836 + " mScrollX-oldScrollX=" + (getScrollX()-oldScrollX)
6837 + " mScrollY-oldScrollY=" + (getScrollY()-oldScrollY)
6840 if (Math.abs(getScrollX() - oldScrollX) > Math.abs(xMove)) {
6843 if (Math.abs(getScrollY() - oldScrollY) > Math.abs(yMove)) {
6846 if (xMove != 0 || yMove != 0) {
6847 pinScrollBy(xMove, yMove, true, 0);
6853 * Compute the maximum horizontal scroll position. Used by {@link OverScrollGlow}.
6854 * @return Maximum horizontal scroll position within real content
6856 int computeMaxScrollX() {
6857 return Math.max(computeRealHorizontalScrollRange() - getViewWidth(), 0);
6861 * Compute the maximum vertical scroll position. Used by {@link OverScrollGlow}.
6862 * @return Maximum vertical scroll position within real content
6864 int computeMaxScrollY() {
6865 return Math.max(computeRealVerticalScrollRange() + getTitleHeight()
6866 - getViewHeightWithTitle(), 0);
6869 boolean updateScrollCoordinates(int x, int y) {
6870 int oldX = getScrollX();
6871 int oldY = getScrollY();
6874 if (oldX != getScrollX() || oldY != getScrollY()) {
6875 mWebViewPrivate.onScrollChanged(getScrollX(), getScrollY(), oldX, oldY);
6882 public void flingScroll(int vx, int vy) {
6883 mScroller.fling(getScrollX(), getScrollY(), vx, vy, 0, computeMaxScrollX(), 0,
6884 computeMaxScrollY(), mOverflingDistance, mOverflingDistance);
6888 private void doFling() {
6889 if (mVelocityTracker == null) {
6892 int maxX = computeMaxScrollX();
6893 int maxY = computeMaxScrollY();
6895 mVelocityTracker.computeCurrentVelocity(1000, mMaximumFling);
6896 int vx = (int) mVelocityTracker.getXVelocity();
6897 int vy = (int) mVelocityTracker.getYVelocity();
6899 int scrollX = getScrollX();
6900 int scrollY = getScrollY();
6901 int overscrollDistance = mOverscrollDistance;
6902 int overflingDistance = mOverflingDistance;
6904 // Use the layer's scroll data if applicable.
6905 if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
6906 scrollX = mScrollingLayerRect.left;
6907 scrollY = mScrollingLayerRect.top;
6908 maxX = mScrollingLayerRect.right;
6909 maxY = mScrollingLayerRect.bottom;
6910 // No overscrolling for layers.
6911 overscrollDistance = overflingDistance = 0;
6912 } else if (mTouchMode == TOUCH_DRAG_TEXT_MODE) {
6913 scrollX = getTextScrollX();
6914 scrollY = getTextScrollY();
6915 maxX = getMaxTextScrollX();
6916 maxY = getMaxTextScrollY();
6917 // No overscrolling for edit text.
6918 overscrollDistance = overflingDistance = 0;
6921 if (mSnapScrollMode != SNAP_NONE) {
6922 if ((mSnapScrollMode & SNAP_X) == SNAP_X) {
6928 if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) {
6929 WebViewCore.resumePriority();
6930 if (!mSelectingText) {
6931 WebViewCore.resumeUpdatePicture(mWebViewCore);
6933 if (mScroller.springBack(scrollX, scrollY, 0, maxX, 0, maxY)) {
6938 float currentVelocity = mScroller.getCurrVelocity();
6939 float velocity = (float) Math.hypot(vx, vy);
6940 if (mLastVelocity > 0 && currentVelocity > 0 && velocity
6941 > mLastVelocity * MINIMUM_VELOCITY_RATIO_FOR_ACCELERATION) {
6942 float deltaR = (float) (Math.abs(Math.atan2(mLastVelY, mLastVelX)
6943 - Math.atan2(vy, vx)));
6944 final float circle = (float) (Math.PI) * 2.0f;
6945 if (deltaR > circle * 0.9f || deltaR < circle * 0.1f) {
6946 vx += currentVelocity * mLastVelX / mLastVelocity;
6947 vy += currentVelocity * mLastVelY / mLastVelocity;
6948 velocity = (float) Math.hypot(vx, vy);
6949 if (DebugFlags.WEB_VIEW) {
6950 Log.v(LOGTAG, "doFling vx= " + vx + " vy=" + vy);
6952 } else if (DebugFlags.WEB_VIEW) {
6953 Log.v(LOGTAG, "doFling missed " + deltaR / circle);
6955 } else if (DebugFlags.WEB_VIEW) {
6956 Log.v(LOGTAG, "doFling start last=" + mLastVelocity
6957 + " current=" + currentVelocity
6958 + " vx=" + vx + " vy=" + vy
6959 + " maxX=" + maxX + " maxY=" + maxY
6960 + " scrollX=" + scrollX + " scrollY=" + scrollY
6961 + " layer=" + mCurrentScrollingLayerId);
6964 // Allow sloppy flings without overscrolling at the edges.
6965 if ((scrollX == 0 || scrollX == maxX) && Math.abs(vx) < Math.abs(vy)) {
6968 if ((scrollY == 0 || scrollY == maxY) && Math.abs(vy) < Math.abs(vx)) {
6972 if (overscrollDistance < overflingDistance) {
6973 if ((vx > 0 && scrollX == -overscrollDistance) ||
6974 (vx < 0 && scrollX == maxX + overscrollDistance)) {
6977 if ((vy > 0 && scrollY == -overscrollDistance) ||
6978 (vy < 0 && scrollY == maxY + overscrollDistance)) {
6985 mLastVelocity = velocity;
6987 // no horizontal overscroll if the content just fits
6988 mScroller.fling(scrollX, scrollY, -vx, -vy, 0, maxX, 0, maxY,
6989 maxX == 0 ? 0 : overflingDistance, overflingDistance);
6990 // Duration is calculated based on velocity. With range boundaries and overscroll
6991 // we may not know how long the final animation will take. (Hence the deprecation
6992 // warning on the call below.) It's not a big deal for scroll bars but if webcore
6993 // resumes during this effect we will take a performance hit. See computeScroll;
6994 // we resume webcore there when the animation is finished.
6995 final int time = mScroller.getDuration();
6997 // Suppress scrollbars for layer scrolling.
6998 if (mTouchMode != TOUCH_DRAG_LAYER_MODE && mTouchMode != TOUCH_DRAG_TEXT_MODE) {
6999 mWebViewPrivate.awakenScrollBars(time);
7006 * See {@link WebView#getZoomControls()}
7010 public View getZoomControls() {
7011 if (!getSettings().supportZoom()) {
7012 Log.w(LOGTAG, "This WebView doesn't support zoom.");
7015 return mZoomManager.getExternalZoomPicker();
7018 void dismissZoomControl() {
7019 mZoomManager.dismissZoomPicker();
7022 float getDefaultZoomScale() {
7023 return mZoomManager.getDefaultScale();
7027 * Return the overview scale of the WebView
7028 * @return The overview scale.
7030 float getZoomOverviewScale() {
7031 return mZoomManager.getZoomOverviewScale();
7035 * See {@link WebView#canZoomIn()}
7038 public boolean canZoomIn() {
7039 return mZoomManager.canZoomIn();
7043 * See {@link WebView#canZoomOut()}
7046 public boolean canZoomOut() {
7047 return mZoomManager.canZoomOut();
7051 * See {@link WebView#zoomIn()}
7054 public boolean zoomIn() {
7055 return mZoomManager.zoomIn();
7059 * See {@link WebView#zoomOut()}
7062 public boolean zoomOut() {
7063 return mZoomManager.zoomOut();
7067 * Return true if the rect (e.g. plugin) is fully visible and maximized
7068 * inside the WebView.
7070 boolean isRectFitOnScreen(Rect rect) {
7071 final int rectWidth = rect.width();
7072 final int rectHeight = rect.height();
7073 final int viewWidth = getViewWidth();
7074 final int viewHeight = getViewHeightWithTitle();
7075 float scale = Math.min((float) viewWidth / rectWidth, (float) viewHeight / rectHeight);
7076 scale = mZoomManager.computeScaleWithLimits(scale);
7077 return !mZoomManager.willScaleTriggerZoom(scale)
7078 && contentToViewX(rect.left) >= getScrollX()
7079 && contentToViewX(rect.right) <= getScrollX() + viewWidth
7080 && contentToViewY(rect.top) >= getScrollY()
7081 && contentToViewY(rect.bottom) <= getScrollY() + viewHeight;
7085 * Maximize and center the rectangle, specified in the document coordinate
7086 * space, inside the WebView. If the zoom doesn't need to be changed, do an
7087 * animated scroll to center it. If the zoom needs to be changed, find the
7088 * zoom center and do a smooth zoom transition. The rect is in document
7091 void centerFitRect(Rect rect) {
7092 final int rectWidth = rect.width();
7093 final int rectHeight = rect.height();
7094 final int viewWidth = getViewWidth();
7095 final int viewHeight = getViewHeightWithTitle();
7096 float scale = Math.min((float) viewWidth / rectWidth, (float) viewHeight
7098 scale = mZoomManager.computeScaleWithLimits(scale);
7099 if (!mZoomManager.willScaleTriggerZoom(scale)) {
7100 pinScrollTo(contentToViewX(rect.left + rectWidth / 2) - viewWidth / 2,
7101 contentToViewY(rect.top + rectHeight / 2) - viewHeight / 2,
7104 float actualScale = mZoomManager.getScale();
7105 float oldScreenX = rect.left * actualScale - getScrollX();
7106 float rectViewX = rect.left * scale;
7107 float rectViewWidth = rectWidth * scale;
7108 float newMaxWidth = mContentWidth * scale;
7109 float newScreenX = (viewWidth - rectViewWidth) / 2;
7110 // pin the newX to the WebView
7111 if (newScreenX > rectViewX) {
7112 newScreenX = rectViewX;
7113 } else if (newScreenX > (newMaxWidth - rectViewX - rectViewWidth)) {
7114 newScreenX = viewWidth - (newMaxWidth - rectViewX);
7116 float zoomCenterX = (oldScreenX * scale - newScreenX * actualScale)
7117 / (scale - actualScale);
7118 float oldScreenY = rect.top * actualScale + getTitleHeight()
7120 float rectViewY = rect.top * scale + getTitleHeight();
7121 float rectViewHeight = rectHeight * scale;
7122 float newMaxHeight = mContentHeight * scale + getTitleHeight();
7123 float newScreenY = (viewHeight - rectViewHeight) / 2;
7124 // pin the newY to the WebView
7125 if (newScreenY > rectViewY) {
7126 newScreenY = rectViewY;
7127 } else if (newScreenY > (newMaxHeight - rectViewY - rectViewHeight)) {
7128 newScreenY = viewHeight - (newMaxHeight - rectViewY);
7130 float zoomCenterY = (oldScreenY * scale - newScreenY * actualScale)
7131 / (scale - actualScale);
7132 mZoomManager.setZoomCenter(zoomCenterX, zoomCenterY);
7133 mZoomManager.startZoomAnimation(scale, false);
7137 // Called by JNI to handle a touch on a node representing an email address,
7138 // address, or phone number
7139 private void overrideLoading(String url) {
7140 mCallbackProxy.uiOverrideUrlLoading(url);
7144 public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
7145 // FIXME: If a subwindow is showing find, and the user touches the
7146 // background window, it can steal focus.
7147 if (mFindIsUp) return false;
7148 boolean result = false;
7149 result = mWebViewPrivate.super_requestFocus(direction, previouslyFocusedRect);
7150 if (mWebViewCore.getSettings().getNeedInitialFocus()
7151 && !mWebView.isInTouchMode()) {
7152 // For cases such as GMail, where we gain focus from a direction,
7153 // we want to move to the first available link.
7154 // FIXME: If there are no visible links, we may not want to
7155 int fakeKeyDirection = 0;
7158 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_UP;
7160 case View.FOCUS_DOWN:
7161 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_DOWN;
7163 case View.FOCUS_LEFT:
7164 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_LEFT;
7166 case View.FOCUS_RIGHT:
7167 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_RIGHT;
7172 mWebViewCore.sendMessage(EventHub.SET_INITIAL_FOCUS, fakeKeyDirection);
7178 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
7179 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
7180 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
7181 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
7182 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
7184 int measuredHeight = heightSize;
7185 int measuredWidth = widthSize;
7187 // Grab the content size from WebViewCore.
7188 int contentHeight = contentToViewDimension(mContentHeight);
7189 int contentWidth = contentToViewDimension(mContentWidth);
7191 // Log.d(LOGTAG, "------- measure " + heightMode);
7193 if (heightMode != MeasureSpec.EXACTLY) {
7194 mHeightCanMeasure = true;
7195 measuredHeight = contentHeight;
7196 if (heightMode == MeasureSpec.AT_MOST) {
7197 // If we are larger than the AT_MOST height, then our height can
7198 // no longer be measured and we should scroll internally.
7199 if (measuredHeight > heightSize) {
7200 measuredHeight = heightSize;
7201 mHeightCanMeasure = false;
7202 measuredHeight |= View.MEASURED_STATE_TOO_SMALL;
7206 mHeightCanMeasure = false;
7208 if (mNativeClass != 0) {
7209 nativeSetHeightCanMeasure(mHeightCanMeasure);
7211 // For the width, always use the given size unless unspecified.
7212 if (widthMode == MeasureSpec.UNSPECIFIED) {
7213 mWidthCanMeasure = true;
7214 measuredWidth = contentWidth;
7216 if (measuredWidth < contentWidth) {
7217 measuredWidth |= View.MEASURED_STATE_TOO_SMALL;
7219 mWidthCanMeasure = false;
7222 synchronized (this) {
7223 mWebViewPrivate.setMeasuredDimension(measuredWidth, measuredHeight);
7228 public boolean requestChildRectangleOnScreen(View child,
7230 boolean immediate) {
7231 if (mNativeClass == 0) {
7234 // don't scroll while in zoom animation. When it is done, we will adjust
7235 // the necessary components
7236 if (mZoomManager.isFixedLengthAnimationInProgress()) {
7240 rect.offset(child.getLeft() - child.getScrollX(),
7241 child.getTop() - child.getScrollY());
7243 Rect content = new Rect(viewToContentX(getScrollX()),
7244 viewToContentY(getScrollY()),
7245 viewToContentX(getScrollX() + getWidth()
7246 - mWebView.getVerticalScrollbarWidth()),
7247 viewToContentY(getScrollY() + getViewHeightWithTitle()));
7248 int screenTop = contentToViewY(content.top);
7249 int screenBottom = contentToViewY(content.bottom);
7250 int height = screenBottom - screenTop;
7251 int scrollYDelta = 0;
7253 if (rect.bottom > screenBottom) {
7254 int oneThirdOfScreenHeight = height / 3;
7255 if (rect.height() > 2 * oneThirdOfScreenHeight) {
7256 // If the rectangle is too tall to fit in the bottom two thirds
7257 // of the screen, place it at the top.
7258 scrollYDelta = rect.top - screenTop;
7260 // If the rectangle will still fit on screen, we want its
7261 // top to be in the top third of the screen.
7262 scrollYDelta = rect.top - (screenTop + oneThirdOfScreenHeight);
7264 } else if (rect.top < screenTop) {
7265 scrollYDelta = rect.top - screenTop;
7268 int screenLeft = contentToViewX(content.left);
7269 int screenRight = contentToViewX(content.right);
7270 int width = screenRight - screenLeft;
7271 int scrollXDelta = 0;
7273 if (rect.right > screenRight && rect.left > screenLeft) {
7274 if (rect.width() > width) {
7275 scrollXDelta += (rect.left - screenLeft);
7277 scrollXDelta += (rect.right - screenRight);
7279 } else if (rect.left < screenLeft) {
7280 scrollXDelta -= (screenLeft - rect.left);
7283 if ((scrollYDelta | scrollXDelta) != 0) {
7284 return pinScrollBy(scrollXDelta, scrollYDelta, !immediate, 0);
7290 /* package */ void replaceTextfieldText(int oldStart, int oldEnd,
7291 String replace, int newStart, int newEnd) {
7292 WebViewCore.ReplaceTextData arg = new WebViewCore.ReplaceTextData();
7293 arg.mReplace = replace;
7294 arg.mNewStart = newStart;
7295 arg.mNewEnd = newEnd;
7297 arg.mTextGeneration = mTextGeneration;
7298 sendBatchableInputMessage(EventHub.REPLACE_TEXT, oldStart, oldEnd, arg);
7301 /* package */ void passToJavaScript(String currentText, KeyEvent event) {
7302 // check if mWebViewCore has been destroyed
7303 if (mWebViewCore == null) {
7306 WebViewCore.JSKeyData arg = new WebViewCore.JSKeyData();
7308 arg.mCurrentText = currentText;
7309 // Increase our text generation number, and pass it to webcore thread
7311 mWebViewCore.sendMessage(EventHub.PASS_TO_JS, mTextGeneration, 0, arg);
7312 // WebKit's document state is not saved until about to leave the page.
7313 // To make sure the host application, like Browser, has the up to date
7314 // document state when it goes to background, we force to save the
7316 mWebViewCore.removeMessages(EventHub.SAVE_DOCUMENT_STATE);
7317 mWebViewCore.sendMessageDelayed(EventHub.SAVE_DOCUMENT_STATE, null, 1000);
7320 public synchronized WebViewCore getWebViewCore() {
7321 return mWebViewCore;
7324 private boolean canTextScroll(int directionX, int directionY) {
7325 int scrollX = getTextScrollX();
7326 int scrollY = getTextScrollY();
7327 int maxScrollX = getMaxTextScrollX();
7328 int maxScrollY = getMaxTextScrollY();
7329 boolean canScrollX = (directionX > 0)
7330 ? (scrollX < maxScrollX)
7332 boolean canScrollY = (directionY > 0)
7333 ? (scrollY < maxScrollY)
7335 return canScrollX || canScrollY;
7338 private int getTextScrollX() {
7339 return -mEditTextContent.left;
7342 private int getTextScrollY() {
7343 return -mEditTextContent.top;
7346 private int getMaxTextScrollX() {
7347 return Math.max(0, mEditTextContent.width() - mEditTextContentBounds.width());
7350 private int getMaxTextScrollY() {
7351 return Math.max(0, mEditTextContent.height() - mEditTextContentBounds.height());
7354 //-------------------------------------------------------------------------
7355 // Methods can be called from a separate thread, like WebViewCore
7356 // If it needs to call the View system, it has to send message.
7357 //-------------------------------------------------------------------------
7360 * General handler to receive message coming from webkit thread
7362 class PrivateHandler extends Handler implements WebViewInputDispatcher.UiCallbacks {
7364 public void handleMessage(Message msg) {
7365 // exclude INVAL_RECT_MSG_ID since it is frequently output
7366 if (DebugFlags.WEB_VIEW && msg.what != INVAL_RECT_MSG_ID) {
7367 if (msg.what >= FIRST_PRIVATE_MSG_ID
7368 && msg.what <= LAST_PRIVATE_MSG_ID) {
7369 Log.v(LOGTAG, HandlerPrivateDebugString[msg.what
7370 - FIRST_PRIVATE_MSG_ID]);
7371 } else if (msg.what >= FIRST_PACKAGE_MSG_ID
7372 && msg.what <= LAST_PACKAGE_MSG_ID) {
7373 Log.v(LOGTAG, HandlerPackageDebugString[msg.what
7374 - FIRST_PACKAGE_MSG_ID]);
7376 Log.v(LOGTAG, Integer.toString(msg.what));
7379 if (mWebViewCore == null) {
7380 // after WebView's destroy() is called, skip handling messages.
7383 if (mBlockWebkitViewMessages
7384 && msg.what != WEBCORE_INITIALIZED_MSG_ID) {
7385 // Blocking messages from webkit
7389 case REMEMBER_PASSWORD: {
7390 mDatabase.setUsernamePassword(
7391 msg.getData().getString("host"),
7392 msg.getData().getString("username"),
7393 msg.getData().getString("password"));
7394 ((Message) msg.obj).sendToTarget();
7397 case NEVER_REMEMBER_PASSWORD: {
7398 mDatabase.setUsernamePassword(
7399 msg.getData().getString("host"), null, null);
7400 ((Message) msg.obj).sendToTarget();
7403 case SCROLL_SELECT_TEXT: {
7404 if (mAutoScrollX == 0 && mAutoScrollY == 0) {
7405 mSentAutoScrollMessage = false;
7408 if (mCurrentScrollingLayerId == 0) {
7409 pinScrollBy(mAutoScrollX, mAutoScrollY, true, 0);
7411 scrollLayerTo(mScrollingLayerRect.left + mAutoScrollX,
7412 mScrollingLayerRect.top + mAutoScrollY);
7414 sendEmptyMessageDelayed(
7415 SCROLL_SELECT_TEXT, SELECT_SCROLL_INTERVAL);
7418 case SCROLL_TO_MSG_ID: {
7419 // arg1 = animate, arg2 = onlyIfImeIsShowing
7420 // obj = Point(x, y)
7421 if (msg.arg2 == 1) {
7422 // This scroll is intended to bring the textfield into
7423 // view, but is only necessary if the IME is showing
7424 InputMethodManager imm = InputMethodManager.peekInstance();
7425 if (imm == null || !imm.isAcceptingText()
7426 || !imm.isActive(mWebView)) {
7430 final Point p = (Point) msg.obj;
7431 if (msg.arg1 == 1) {
7432 spawnContentScrollTo(p.x, p.y);
7434 setContentScrollTo(p.x, p.y);
7438 case UPDATE_ZOOM_RANGE: {
7439 WebViewCore.ViewState viewState = (WebViewCore.ViewState) msg.obj;
7440 // mScrollX contains the new minPrefWidth
7441 mZoomManager.updateZoomRange(viewState, getViewWidth(), viewState.mScrollX);
7444 case UPDATE_ZOOM_DENSITY: {
7445 final float density = (Float) msg.obj;
7446 mZoomManager.updateDefaultZoomDensity(density);
7449 case REPLACE_BASE_CONTENT: {
7450 nativeReplaceBaseContent(msg.arg1);
7453 case NEW_PICTURE_MSG_ID: {
7454 // called for new content
7455 final WebViewCore.DrawData draw = (WebViewCore.DrawData) msg.obj;
7456 setNewPicture(draw, true);
7459 case WEBCORE_INITIALIZED_MSG_ID:
7460 // nativeCreate sets mNativeClass to a non-zero value
7461 String drawableDir = BrowserFrame.getRawResFilename(
7462 BrowserFrame.DRAWABLEDIR, mContext);
7463 WindowManager windowManager =
7464 (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
7465 Display display = windowManager.getDefaultDisplay();
7466 nativeCreate(msg.arg1, drawableDir,
7467 ActivityManager.isHighEndGfx(display));
7468 if (mDelaySetPicture != null) {
7469 setNewPicture(mDelaySetPicture, true);
7470 mDelaySetPicture = null;
7473 nativeSetPauseDrawing(mNativeClass, true);
7475 mInputDispatcher = new WebViewInputDispatcher(this,
7476 mWebViewCore.getInputDispatcherCallbacks());
7478 case UPDATE_TEXTFIELD_TEXT_MSG_ID:
7479 // Make sure that the textfield is currently focused
7480 // and representing the same node as the pointer.
7481 if (msg.arg2 == mTextGeneration) {
7482 String text = (String) msg.obj;
7486 if (mInputConnection != null &&
7487 mFieldPointer == msg.arg1) {
7488 mInputConnection.setTextAndKeepSelection(text);
7492 case UPDATE_TEXT_SELECTION_MSG_ID:
7493 updateTextSelectionFromMessage(msg.arg1, msg.arg2,
7494 (WebViewCore.TextSelectionData) msg.obj);
7497 int direction = msg.arg1;
7498 View focusSearch = mWebView.focusSearch(direction);
7499 if (focusSearch != null && focusSearch != mWebView) {
7500 focusSearch.requestFocus();
7503 case CLEAR_TEXT_ENTRY:
7506 case INVAL_RECT_MSG_ID: {
7507 Rect r = (Rect)msg.obj;
7511 // we need to scale r from content into view coords,
7512 // which viewInvalidate() does for us
7513 viewInvalidate(r.left, r.top, r.right, r.bottom);
7517 case REQUEST_FORM_DATA:
7518 if (mFieldPointer == msg.arg1) {
7519 ArrayAdapter<String> adapter = (ArrayAdapter<String>)msg.obj;
7520 mAutoCompletePopup.setAdapter(adapter);
7524 case LONG_PRESS_CENTER:
7525 // as this is shared by keydown and trackballdown, reset all
7527 mGotCenterDown = false;
7528 mTrackballDown = false;
7532 case WEBCORE_NEED_TOUCH_EVENTS:
7533 mInputDispatcher.setWebKitWantsTouchEvents(msg.arg1 != 0);
7536 case REQUEST_KEYBOARD:
7537 if (msg.arg1 == 0) {
7540 displaySoftKeyboard(false);
7544 case DRAG_HELD_MOTIONLESS:
7545 mHeldMotionless = MOTIONLESS_TRUE;
7547 // fall through to keep scrollbars awake
7549 case AWAKEN_SCROLL_BARS:
7550 if (mTouchMode == TOUCH_DRAG_MODE
7551 && mHeldMotionless == MOTIONLESS_TRUE) {
7552 mWebViewPrivate.awakenScrollBars(ViewConfiguration
7553 .getScrollDefaultDelay(), false);
7554 mPrivateHandler.sendMessageDelayed(mPrivateHandler
7555 .obtainMessage(AWAKEN_SCROLL_BARS),
7556 ViewConfiguration.getScrollDefaultDelay());
7561 mWebView.setKeepScreenOn(msg.arg1 == 1);
7564 case ENTER_FULLSCREEN_VIDEO:
7565 int layerId = msg.arg1;
7567 String url = (String) msg.obj;
7568 if (mHTML5VideoViewProxy != null) {
7569 mHTML5VideoViewProxy.enterFullScreenVideo(layerId, url);
7573 case EXIT_FULLSCREEN_VIDEO:
7574 if (mHTML5VideoViewProxy != null) {
7575 mHTML5VideoViewProxy.exitFullScreenVideo();
7579 case SHOW_FULLSCREEN: {
7580 View view = (View) msg.obj;
7581 int orientation = msg.arg1;
7584 if (inFullScreenMode()) {
7585 Log.w(LOGTAG, "Should not have another full screen.");
7586 dismissFullScreenMode();
7588 mFullScreenHolder = new PluginFullScreenHolder(WebViewClassic.this, orientation, npp);
7589 mFullScreenHolder.setContentView(view);
7590 mFullScreenHolder.show();
7595 case HIDE_FULLSCREEN:
7596 dismissFullScreenMode();
7599 case SHOW_RECT_MSG_ID: {
7600 WebViewCore.ShowRectData data = (WebViewCore.ShowRectData) msg.obj;
7601 int x = getScrollX();
7602 int left = contentToViewX(data.mLeft);
7603 int width = contentToViewDimension(data.mWidth);
7604 int maxWidth = contentToViewDimension(data.mContentWidth);
7605 int viewWidth = getViewWidth();
7606 if (width < viewWidth) {
7608 x += left + width / 2 - getScrollX() - viewWidth / 2;
7610 x += (int) (left + data.mXPercentInDoc * width
7611 - getScrollX() - data.mXPercentInView * viewWidth);
7613 if (DebugFlags.WEB_VIEW) {
7614 Log.v(LOGTAG, "showRectMsg=(left=" + left + ",width=" +
7615 width + ",maxWidth=" + maxWidth +
7616 ",viewWidth=" + viewWidth + ",x="
7617 + x + ",xPercentInDoc=" + data.mXPercentInDoc +
7618 ",xPercentInView=" + data.mXPercentInView+ ")");
7620 // use the passing content width to cap x as the current
7621 // mContentWidth may not be updated yet
7623 (Math.min(maxWidth, x + viewWidth)) - viewWidth);
7624 int top = contentToViewY(data.mTop);
7625 int height = contentToViewDimension(data.mHeight);
7626 int maxHeight = contentToViewDimension(data.mContentHeight);
7627 int viewHeight = getViewHeight();
7628 int y = (int) (top + data.mYPercentInDoc * height -
7629 data.mYPercentInView * viewHeight);
7630 if (DebugFlags.WEB_VIEW) {
7631 Log.v(LOGTAG, "showRectMsg=(top=" + top + ",height=" +
7632 height + ",maxHeight=" + maxHeight +
7633 ",viewHeight=" + viewHeight + ",y="
7634 + y + ",yPercentInDoc=" + data.mYPercentInDoc +
7635 ",yPercentInView=" + data.mYPercentInView+ ")");
7637 // use the passing content height to cap y as the current
7638 // mContentHeight may not be updated yet
7640 (Math.min(maxHeight, y + viewHeight) - viewHeight));
7641 // We need to take into account the visible title height
7642 // when scrolling since y is an absolute view position.
7643 y = Math.max(0, y - getVisibleTitleHeightImpl());
7644 mWebView.scrollTo(x, y);
7648 case CENTER_FIT_RECT:
7649 centerFitRect((Rect)msg.obj);
7652 case SET_SCROLLBAR_MODES:
7653 mHorizontalScrollBarMode = msg.arg1;
7654 mVerticalScrollBarMode = msg.arg2;
7657 case SELECTION_STRING_CHANGED:
7658 if (mAccessibilityInjector != null) {
7659 String selectionString = (String) msg.obj;
7660 mAccessibilityInjector.onSelectionStringChange(selectionString);
7664 case FOCUS_NODE_CHANGED:
7665 mIsEditingText = (msg.arg1 == mFieldPointer);
7666 if (mAutoCompletePopup != null && !mIsEditingText) {
7667 mAutoCompletePopup.clearAdapter();
7669 // fall through to HIT_TEST_RESULT
7670 case HIT_TEST_RESULT:
7671 WebKitHitTest hit = (WebKitHitTest) msg.obj;
7673 setTouchHighlightRects(hit);
7674 setHitTestResult(hit);
7677 case SAVE_WEBARCHIVE_FINISHED:
7678 SaveWebArchiveMessage saveMessage = (SaveWebArchiveMessage)msg.obj;
7679 if (saveMessage.mCallback != null) {
7680 saveMessage.mCallback.onReceiveValue(saveMessage.mResultFile);
7684 case SET_AUTOFILLABLE:
7685 mAutoFillData = (WebViewCore.AutoFillData) msg.obj;
7686 if (mInputConnection != null) {
7687 mInputConnection.setAutoFillable(mAutoFillData.getQueryId());
7688 mAutoCompletePopup.setAutoFillQueryId(mAutoFillData.getQueryId());
7692 case AUTOFILL_COMPLETE:
7693 if (mAutoCompletePopup != null) {
7694 ArrayList<String> pastEntries = new ArrayList<String>();
7695 mAutoCompletePopup.setAdapter(new ArrayAdapter<String>(
7697 com.android.internal.R.layout.web_text_view_dropdown,
7702 case COPY_TO_CLIPBOARD:
7703 copyToClipboard((String) msg.obj);
7706 case INIT_EDIT_FIELD:
7707 if (mInputConnection != null) {
7708 TextFieldInitData initData = (TextFieldInitData) msg.obj;
7709 mTextGeneration = 0;
7710 mFieldPointer = initData.mFieldPointer;
7711 mInputConnection.initEditorInfo(initData);
7712 mInputConnection.setTextAndKeepSelection(initData.mText);
7713 mEditTextContentBounds.set(initData.mContentBounds);
7714 mEditTextLayerId = initData.mNodeLayerId;
7715 nativeMapLayerRect(mNativeClass, mEditTextLayerId,
7716 mEditTextContentBounds);
7717 mEditTextContent.set(initData.mContentRect);
7718 relocateAutoCompletePopup();
7723 String text = (String)msg.obj;
7724 int start = msg.arg1;
7726 int cursorPosition = start + text.length();
7727 replaceTextfieldText(start, end, text,
7728 cursorPosition, cursorPosition);
7732 case UPDATE_MATCH_COUNT: {
7733 WebViewCore.FindAllRequest request = (WebViewCore.FindAllRequest)msg.obj;
7734 if (request == null) {
7735 if (mFindCallback != null) {
7736 mFindCallback.updateMatchCount(0, 0, true);
7738 } else if (request == mFindRequest) {
7739 int matchCount, matchIndex;
7740 synchronized (mFindRequest) {
7741 matchCount = request.mMatchCount;
7742 matchIndex = request.mMatchIndex;
7744 if (mFindCallback != null) {
7745 mFindCallback.updateMatchCount(matchIndex, matchCount, false);
7747 if (mFindListener != null) {
7748 mFindListener.onFindResultReceived(matchIndex, matchCount, true);
7754 case CLEAR_CARET_HANDLE:
7759 sendBatchableInputMessage(EventHub.KEY_PRESS, msg.arg1, 0, null);
7762 case RELOCATE_AUTO_COMPLETE_POPUP:
7763 relocateAutoCompletePopup();
7767 mWebViewCore.sendMessage(EventHub.AUTOFILL_FORM,
7768 msg.arg1, /* unused */0);
7771 case EDIT_TEXT_SIZE_CHANGED:
7772 if (msg.arg1 == mFieldPointer) {
7773 mEditTextContent.set((Rect)msg.obj);
7777 case SHOW_CARET_HANDLE:
7778 if (!mSelectingText && mIsEditingText && mIsCaretSelection) {
7779 setupWebkitSelect();
7785 case UPDATE_CONTENT_BOUNDS:
7786 mEditTextContentBounds.set((Rect) msg.obj);
7789 case SCROLL_EDIT_TEXT:
7790 scrollEditWithCursor();
7794 super.handleMessage(msg);
7800 public Looper getUiLooper() {
7805 public void dispatchUiEvent(MotionEvent event, int eventType, int flags) {
7806 onHandleUiEvent(event, eventType, flags);
7810 public Context getContext() {
7811 return WebViewClassic.this.getContext();
7815 private void setHitTestTypeFromUrl(String url) {
7816 String substr = null;
7817 if (url.startsWith(SCHEME_GEO)) {
7818 mInitialHitTestResult.setType(HitTestResult.GEO_TYPE);
7819 substr = url.substring(SCHEME_GEO.length());
7820 } else if (url.startsWith(SCHEME_TEL)) {
7821 mInitialHitTestResult.setType(HitTestResult.PHONE_TYPE);
7822 substr = url.substring(SCHEME_TEL.length());
7823 } else if (url.startsWith(SCHEME_MAILTO)) {
7824 mInitialHitTestResult.setType(HitTestResult.EMAIL_TYPE);
7825 substr = url.substring(SCHEME_MAILTO.length());
7827 mInitialHitTestResult.setType(HitTestResult.SRC_ANCHOR_TYPE);
7828 mInitialHitTestResult.setExtra(url);
7832 mInitialHitTestResult.setExtra(URLDecoder.decode(substr, "UTF-8"));
7833 } catch (Throwable e) {
7834 Log.w(LOGTAG, "Failed to decode URL! " + substr, e);
7835 mInitialHitTestResult.setType(HitTestResult.UNKNOWN_TYPE);
7839 private void setHitTestResult(WebKitHitTest hit) {
7841 mInitialHitTestResult = null;
7844 mInitialHitTestResult = new HitTestResult();
7845 if (hit.mLinkUrl != null) {
7846 setHitTestTypeFromUrl(hit.mLinkUrl);
7847 if (hit.mImageUrl != null
7848 && mInitialHitTestResult.getType() == HitTestResult.SRC_ANCHOR_TYPE) {
7849 mInitialHitTestResult.setType(HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
7850 mInitialHitTestResult.setExtra(hit.mImageUrl);
7852 } else if (hit.mImageUrl != null) {
7853 mInitialHitTestResult.setType(HitTestResult.IMAGE_TYPE);
7854 mInitialHitTestResult.setExtra(hit.mImageUrl);
7855 } else if (hit.mEditable) {
7856 mInitialHitTestResult.setType(HitTestResult.EDIT_TEXT_TYPE);
7857 } else if (hit.mIntentUrl != null) {
7858 setHitTestTypeFromUrl(hit.mIntentUrl);
7862 private boolean shouldDrawHighlightRect() {
7863 if (mFocusedNode == null || mInitialHitTestResult == null) {
7866 if (mTouchHighlightRegion.isEmpty()) {
7869 if (mFocusedNode.mHasFocus && !mWebView.isInTouchMode()) {
7870 return mDrawCursorRing && !mFocusedNode.mEditable;
7872 if (mFocusedNode.mHasFocus && mFocusedNode.mEditable) {
7875 long delay = SystemClock.uptimeMillis() - mTouchHighlightRequested;
7876 if (delay < ViewConfiguration.getTapTimeout()) {
7877 Rect r = mTouchHighlightRegion.getBounds();
7878 mWebView.postInvalidateDelayed(delay, r.left, r.top, r.right, r.bottom);
7881 if (mInputDispatcher == null) {
7884 return mInputDispatcher.shouldShowTapHighlight();
7888 private FocusTransitionDrawable mFocusTransition = null;
7889 static class FocusTransitionDrawable extends Drawable {
7890 Region mPreviousRegion;
7892 float mProgress = 0;
7893 WebViewClassic mWebView;
7898 public FocusTransitionDrawable(WebViewClassic view) {
7900 mPaint = new Paint(mWebView.mTouchHightlightPaint);
7901 mMaxAlpha = mPaint.getAlpha();
7905 public void setColorFilter(ColorFilter cf) {
7909 public void setAlpha(int alpha) {
7913 public int getOpacity() {
7917 public void setProgress(float p) {
7919 if (mWebView.mFocusTransition == this) {
7920 if (mProgress == 1f)
7921 mWebView.mFocusTransition = null;
7922 mWebView.invalidate();
7926 public float getProgress() {
7931 public void draw(Canvas canvas) {
7932 if (mTranslate == null) {
7933 Rect bounds = mPreviousRegion.getBounds();
7934 Point from = new Point(bounds.centerX(), bounds.centerY());
7935 mNewRegion.getBounds(bounds);
7936 Point to = new Point(bounds.centerX(), bounds.centerY());
7937 mTranslate = new Point(from.x - to.x, from.y - to.y);
7939 int alpha = (int) (mProgress * mMaxAlpha);
7940 RegionIterator iter = new RegionIterator(mPreviousRegion);
7941 Rect r = new Rect();
7942 mPaint.setAlpha(mMaxAlpha - alpha);
7943 float tx = mTranslate.x * mProgress;
7944 float ty = mTranslate.y * mProgress;
7945 int save = canvas.save(Canvas.MATRIX_SAVE_FLAG);
7946 canvas.translate(-tx, -ty);
7947 while (iter.next(r)) {
7948 canvas.drawRect(r, mPaint);
7950 canvas.restoreToCount(save);
7951 iter = new RegionIterator(mNewRegion);
7953 mPaint.setAlpha(alpha);
7954 save = canvas.save(Canvas.MATRIX_SAVE_FLAG);
7955 tx = mTranslate.x - tx;
7956 ty = mTranslate.y - ty;
7957 canvas.translate(tx, ty);
7958 while (iter.next(r)) {
7959 canvas.drawRect(r, mPaint);
7961 canvas.restoreToCount(save);
7965 private boolean shouldAnimateTo(WebKitHitTest hit) {
7966 // TODO: Don't be annoying or throw out the animation entirely
7970 private void setTouchHighlightRects(WebKitHitTest hit) {
7971 FocusTransitionDrawable transition = null;
7972 if (shouldAnimateTo(hit)) {
7973 transition = new FocusTransitionDrawable(this);
7975 Rect[] rects = hit != null ? hit.mTouchRects : null;
7976 if (!mTouchHighlightRegion.isEmpty()) {
7977 mWebView.invalidate(mTouchHighlightRegion.getBounds());
7978 if (transition != null) {
7979 transition.mPreviousRegion = new Region(mTouchHighlightRegion);
7981 mTouchHighlightRegion.setEmpty();
7983 if (rects != null) {
7984 mTouchHightlightPaint.setColor(hit.mTapHighlightColor);
7985 for (Rect rect : rects) {
7986 Rect viewRect = contentToViewRect(rect);
7987 // some sites, like stories in nytimes.com, set
7988 // mouse event handler in the top div. It is not
7989 // user friendly to highlight the div if it covers
7990 // more than half of the screen.
7991 if (viewRect.width() < getWidth() >> 1
7992 || viewRect.height() < getHeight() >> 1) {
7993 mTouchHighlightRegion.union(viewRect);
7994 } else if (DebugFlags.WEB_VIEW) {
7995 Log.d(LOGTAG, "Skip the huge selection rect:"
7999 mWebView.invalidate(mTouchHighlightRegion.getBounds());
8000 if (transition != null && transition.mPreviousRegion != null) {
8001 transition.mNewRegion = new Region(mTouchHighlightRegion);
8002 mFocusTransition = transition;
8003 ObjectAnimator animator = ObjectAnimator.ofFloat(
8004 mFocusTransition, "progress", 1f);
8010 // Interface to allow the profiled WebView to hook the page swap notifications.
8011 public interface PageSwapDelegate {
8012 void onPageSwapOccurred(boolean notifyAnimationStarted);
8016 double mAverageSwapFps;
8018 /** Called by JNI when pages are swapped (only occurs with hardware
8020 protected void pageSwapCallback(boolean notifyAnimationStarted) {
8021 if (DebugFlags.MEASURE_PAGE_SWAP_FPS) {
8022 long now = System.currentTimeMillis();
8023 long diff = now - mLastSwapTime;
8024 mAverageSwapFps = ((1000.0 / diff) + mAverageSwapFps) / 2;
8025 Log.d(LOGTAG, "page swap fps: " + mAverageSwapFps);
8026 mLastSwapTime = now;
8028 mWebViewCore.resumeWebKitDraw();
8029 if (notifyAnimationStarted) {
8030 mWebViewCore.sendMessage(EventHub.NOTIFY_ANIMATION_STARTED);
8032 if (mWebView instanceof PageSwapDelegate) {
8033 // This provides a hook for ProfiledWebView to observe the tile page swaps.
8034 ((PageSwapDelegate) mWebView).onPageSwapOccurred(notifyAnimationStarted);
8038 void setNewPicture(final WebViewCore.DrawData draw, boolean updateBaseLayer) {
8039 if (mNativeClass == 0) {
8040 if (mDelaySetPicture != null) {
8041 throw new IllegalStateException("Tried to setNewPicture with"
8042 + " a delay picture already set! (memory leak)");
8044 // Not initialized yet, delay set
8045 mDelaySetPicture = draw;
8048 WebViewCore.ViewState viewState = draw.mViewState;
8049 boolean isPictureAfterFirstLayout = viewState != null;
8051 if (updateBaseLayer) {
8052 setBaseLayer(draw.mBaseLayer, draw.mInvalRegion,
8053 getSettings().getShowVisualIndicator(),
8054 isPictureAfterFirstLayout);
8056 final Point viewSize = draw.mViewSize;
8057 // We update the layout (i.e. request a layout from the
8058 // view system) if the last view size that we sent to
8059 // WebCore matches the view size of the picture we just
8060 // received in the fixed dimension.
8061 final boolean updateLayout = viewSize.x == mLastWidthSent
8062 && viewSize.y == mLastHeightSent;
8063 // Don't send scroll event for picture coming from webkit,
8064 // since the new picture may cause a scroll event to override
8065 // the saved history scroll position.
8066 mSendScrollEvent = false;
8067 recordNewContentSize(draw.mContentSize.x,
8068 draw.mContentSize.y, updateLayout);
8069 if (isPictureAfterFirstLayout) {
8070 // Reset the last sent data here since dealing with new page.
8072 mZoomManager.onFirstLayout(draw);
8073 int scrollX = viewState.mShouldStartScrolledRight
8074 ? getContentWidth() : viewState.mScrollX;
8075 int scrollY = viewState.mScrollY;
8076 setContentScrollTo(scrollX, scrollY);
8077 if (!mDrawHistory) {
8078 // As we are on a new page, hide the keyboard
8082 mSendScrollEvent = true;
8084 if (DebugFlags.WEB_VIEW) {
8085 Rect b = draw.mInvalRegion.getBounds();
8086 Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" +
8087 b.left+","+b.top+","+b.right+","+b.bottom+"}");
8089 Rect invalBounds = draw.mInvalRegion.getBounds();
8090 if (!invalBounds.isEmpty()) {
8091 invalidateContentRect(invalBounds);
8093 mWebView.invalidate();
8096 // update the zoom information based on the new picture
8097 mZoomManager.onNewPicture(draw);
8099 if (isPictureAfterFirstLayout) {
8100 mViewManager.postReadyToDrawAll();
8102 scrollEditWithCursor();
8104 if (mPictureListener != null) {
8105 mPictureListener.onNewPicture(getWebView(), capturePicture());
8110 * Used when receiving messages for REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID
8111 * and UPDATE_TEXT_SELECTION_MSG_ID.
8113 private void updateTextSelectionFromMessage(int nodePointer,
8114 int textGeneration, WebViewCore.TextSelectionData data) {
8115 if (textGeneration == mTextGeneration) {
8116 if (mInputConnection != null && mFieldPointer == nodePointer) {
8117 mInputConnection.setSelection(data.mStart, data.mEnd);
8120 nativeSetTextSelection(mNativeClass, data.mSelectTextPtr);
8122 if (data.mSelectTextPtr != 0 &&
8123 (data.mStart != data.mEnd ||
8124 (mFieldPointer == nodePointer && mFieldPointer != 0))) {
8125 mIsCaretSelection = (data.mStart == data.mEnd);
8126 if (mIsCaretSelection &&
8127 (mInputConnection == null ||
8128 mInputConnection.getEditable().length() == 0)) {
8129 // There's no text, don't show caret handle.
8132 if (!mSelectingText) {
8133 setupWebkitSelect();
8134 } else if (!mSelectionStarted) {
8135 syncSelectionCursors();
8137 adjustSelectionCursors();
8139 if (mIsCaretSelection) {
8149 private void scrollEditText(int scrollX, int scrollY) {
8150 // Scrollable edit text. Scroll it.
8151 float maxScrollX = getMaxTextScrollX();
8152 float scrollPercentX = ((float)scrollX)/maxScrollX;
8153 mEditTextContent.offsetTo(-scrollX, -scrollY);
8154 mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SCROLL_TEXT_INPUT, 0,
8155 scrollY, (Float)scrollPercentX);
8158 private void beginTextBatch() {
8159 mIsBatchingTextChanges = true;
8162 private void commitTextBatch() {
8163 if (mWebViewCore != null) {
8164 mWebViewCore.sendMessages(mBatchedTextChanges);
8166 mBatchedTextChanges.clear();
8167 mIsBatchingTextChanges = false;
8170 private void sendBatchableInputMessage(int what, int arg1, int arg2,
8172 if (mWebViewCore == null) {
8175 Message message = Message.obtain(null, what, arg1, arg2, obj);
8176 if (mIsBatchingTextChanges) {
8177 mBatchedTextChanges.add(message);
8179 mWebViewCore.sendMessage(message);
8183 // Class used to use a dropdown for a <select> element
8184 private class InvokeListBox implements Runnable {
8185 // Whether the listbox allows multiple selection.
8186 private boolean mMultiple;
8187 // Passed in to a list with multiple selection to tell
8188 // which items are selected.
8189 private int[] mSelectedArray;
8190 // Passed in to a list with single selection to tell
8191 // where the initial selection is.
8192 private int mSelection;
8194 private Container[] mContainers;
8196 // Need these to provide stable ids to my ArrayAdapter,
8197 // which normally does not have stable ids. (Bug 1250098)
8198 private class Container extends Object {
8200 * Possible values for mEnabled. Keep in sync with OptionStatus in
8203 final static int OPTGROUP = -1;
8204 final static int OPTION_DISABLED = 0;
8205 final static int OPTION_ENABLED = 1;
8212 public String toString() {
8218 * Subclass ArrayAdapter so we can disable OptionGroupLabels,
8219 * and allow filtering.
8221 private class MyArrayListAdapter extends ArrayAdapter<Container> {
8222 public MyArrayListAdapter() {
8223 super(WebViewClassic.this.mContext,
8224 mMultiple ? com.android.internal.R.layout.select_dialog_multichoice :
8225 com.android.internal.R.layout.webview_select_singlechoice,
8230 public View getView(int position, View convertView,
8232 // Always pass in null so that we will get a new CheckedTextView
8233 // Otherwise, an item which was previously used as an <optgroup>
8234 // element (i.e. has no check), could get used as an <option>
8235 // element, which needs a checkbox/radio, but it would not have
8237 convertView = super.getView(position, null, parent);
8238 Container c = item(position);
8239 if (c != null && Container.OPTION_ENABLED != c.mEnabled) {
8240 // ListView does not draw dividers between disabled and
8241 // enabled elements. Use a LinearLayout to provide dividers
8242 LinearLayout layout = new LinearLayout(mContext);
8243 layout.setOrientation(LinearLayout.VERTICAL);
8245 View dividerTop = new View(mContext);
8246 dividerTop.setBackgroundResource(
8247 android.R.drawable.divider_horizontal_bright);
8248 layout.addView(dividerTop);
8251 if (Container.OPTGROUP == c.mEnabled) {
8252 // Currently select_dialog_multichoice uses CheckedTextViews.
8253 // If that changes, the class cast will no longer be valid.
8255 Assert.assertTrue(convertView instanceof CheckedTextView);
8256 ((CheckedTextView) convertView).setCheckMarkDrawable(null);
8259 // c.mEnabled == Container.OPTION_DISABLED
8260 // Draw the disabled element in a disabled state.
8261 convertView.setEnabled(false);
8264 layout.addView(convertView);
8265 if (position < getCount() - 1) {
8266 View dividerBottom = new View(mContext);
8267 dividerBottom.setBackgroundResource(
8268 android.R.drawable.divider_horizontal_bright);
8269 layout.addView(dividerBottom);
8277 public boolean hasStableIds() {
8278 // AdapterView's onChanged method uses this to determine whether
8279 // to restore the old state. Return false so that the old (out
8280 // of date) state does not replace the new, valid state.
8284 private Container item(int position) {
8285 if (position < 0 || position >= getCount()) {
8288 return getItem(position);
8292 public long getItemId(int position) {
8293 Container item = item(position);
8301 public boolean areAllItemsEnabled() {
8306 public boolean isEnabled(int position) {
8307 Container item = item(position);
8311 return Container.OPTION_ENABLED == item.mEnabled;
8315 private InvokeListBox(String[] array, int[] enabled, int[] selected) {
8317 mSelectedArray = selected;
8319 int length = array.length;
8320 mContainers = new Container[length];
8321 for (int i = 0; i < length; i++) {
8322 mContainers[i] = new Container();
8323 mContainers[i].mString = array[i];
8324 mContainers[i].mEnabled = enabled[i];
8325 mContainers[i].mId = i;
8329 private InvokeListBox(String[] array, int[] enabled, int selection) {
8330 mSelection = selection;
8333 int length = array.length;
8334 mContainers = new Container[length];
8335 for (int i = 0; i < length; i++) {
8336 mContainers[i] = new Container();
8337 mContainers[i].mString = array[i];
8338 mContainers[i].mEnabled = enabled[i];
8339 mContainers[i].mId = i;
8344 * Whenever the data set changes due to filtering, this class ensures
8345 * that the checked item remains checked.
8347 private class SingleDataSetObserver extends DataSetObserver {
8348 private long mCheckedId;
8349 private ListView mListView;
8350 private Adapter mAdapter;
8353 * Create a new observer.
8354 * @param id The ID of the item to keep checked.
8355 * @param l ListView for getting and clearing the checked states
8356 * @param a Adapter for getting the IDs
8358 public SingleDataSetObserver(long id, ListView l, Adapter a) {
8365 public void onChanged() {
8366 // The filter may have changed which item is checked. Find the
8367 // item that the ListView thinks is checked.
8368 int position = mListView.getCheckedItemPosition();
8369 long id = mAdapter.getItemId(position);
8370 if (mCheckedId != id) {
8371 // Clear the ListView's idea of the checked item, since
8373 mListView.clearChoices();
8374 // Search for mCheckedId. If it is in the filtered list,
8375 // mark it as checked
8376 int count = mAdapter.getCount();
8377 for (int i = 0; i < count; i++) {
8378 if (mAdapter.getItemId(i) == mCheckedId) {
8379 mListView.setItemChecked(i, true);
8389 final ListView listView = (ListView) LayoutInflater.from(mContext)
8390 .inflate(com.android.internal.R.layout.select_dialog, null);
8391 final MyArrayListAdapter adapter = new MyArrayListAdapter();
8392 AlertDialog.Builder b = new AlertDialog.Builder(mContext)
8393 .setView(listView).setCancelable(true)
8394 .setInverseBackgroundForced(true);
8397 b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
8399 public void onClick(DialogInterface dialog, int which) {
8400 mWebViewCore.sendMessage(
8401 EventHub.LISTBOX_CHOICES,
8402 adapter.getCount(), 0,
8403 listView.getCheckedItemPositions());
8405 b.setNegativeButton(android.R.string.cancel,
8406 new DialogInterface.OnClickListener() {
8408 public void onClick(DialogInterface dialog, int which) {
8409 mWebViewCore.sendMessage(
8410 EventHub.SINGLE_LISTBOX_CHOICE, -2, 0);
8413 mListBoxDialog = b.create();
8414 listView.setAdapter(adapter);
8415 listView.setFocusableInTouchMode(true);
8416 // There is a bug (1250103) where the checks in a ListView with
8417 // multiple items selected are associated with the positions, not
8418 // the ids, so the items do not properly retain their checks when
8419 // filtered. Do not allow filtering on multiple lists until
8420 // that bug is fixed.
8422 listView.setTextFilterEnabled(!mMultiple);
8424 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
8425 int length = mSelectedArray.length;
8426 for (int i = 0; i < length; i++) {
8427 listView.setItemChecked(mSelectedArray[i], true);
8430 listView.setOnItemClickListener(new OnItemClickListener() {
8432 public void onItemClick(AdapterView<?> parent, View v,
8433 int position, long id) {
8434 // Rather than sending the message right away, send it
8435 // after the page regains focus.
8436 mListBoxMessage = Message.obtain(null,
8437 EventHub.SINGLE_LISTBOX_CHOICE, (int) id, 0);
8438 if (mListBoxDialog != null) {
8439 mListBoxDialog.dismiss();
8440 mListBoxDialog = null;
8444 if (mSelection != -1) {
8445 listView.setSelection(mSelection);
8446 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
8447 listView.setItemChecked(mSelection, true);
8448 DataSetObserver observer = new SingleDataSetObserver(
8449 adapter.getItemId(mSelection), listView, adapter);
8450 adapter.registerDataSetObserver(observer);
8453 mListBoxDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
8455 public void onCancel(DialogInterface dialog) {
8456 mWebViewCore.sendMessage(
8457 EventHub.SINGLE_LISTBOX_CHOICE, -2, 0);
8458 mListBoxDialog = null;
8461 mListBoxDialog.show();
8465 private Message mListBoxMessage;
8468 * Request a dropdown menu for a listbox with multiple selection.
8470 * @param array Labels for the listbox.
8471 * @param enabledArray State for each element in the list. See static
8472 * integers in Container class.
8473 * @param selectedArray Which positions are initally selected.
8475 void requestListBox(String[] array, int[] enabledArray, int[]
8477 mPrivateHandler.post(
8478 new InvokeListBox(array, enabledArray, selectedArray));
8482 * Request a dropdown menu for a listbox with single selection or a single
8485 * @param array Labels for the listbox.
8486 * @param enabledArray State for each element in the list. See static
8487 * integers in Container class.
8488 * @param selection Which position is initally selected.
8490 void requestListBox(String[] array, int[] enabledArray, int selection) {
8491 mPrivateHandler.post(
8492 new InvokeListBox(array, enabledArray, selection));
8495 private int getScaledMaxXScroll() {
8497 if (mHeightCanMeasure == false) {
8498 width = getViewWidth() / 4;
8500 Rect visRect = new Rect();
8501 calcOurVisibleRect(visRect);
8502 width = visRect.width() / 2;
8504 // FIXME the divisor should be retrieved from somewhere
8505 return viewToContentX(width);
8508 private int getScaledMaxYScroll() {
8510 if (mHeightCanMeasure == false) {
8511 height = getViewHeight() / 4;
8513 Rect visRect = new Rect();
8514 calcOurVisibleRect(visRect);
8515 height = visRect.height() / 2;
8517 // FIXME the divisor should be retrieved from somewhere
8518 // the closest thing today is hard-coded into ScrollView.java
8519 // (from ScrollView.java, line 363) int maxJump = height/2;
8520 return Math.round(height * mZoomManager.getInvScale());
8524 * Called by JNI to invalidate view
8526 private void viewInvalidate() {
8531 * Pass the key directly to the page. This assumes that
8532 * nativePageShouldHandleShiftAndArrows() returned true.
8534 private void letPageHandleNavKey(int keyCode, long time, boolean down, int metaState) {
8537 keyEventAction = KeyEvent.ACTION_DOWN;
8539 keyEventAction = KeyEvent.ACTION_UP;
8542 KeyEvent event = new KeyEvent(time, time, keyEventAction, keyCode,
8543 1, (metaState & KeyEvent.META_SHIFT_ON)
8544 | (metaState & KeyEvent.META_ALT_ON)
8545 | (metaState & KeyEvent.META_SYM_ON)
8546 , KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0);
8547 sendKeyEvent(event);
8550 private void sendKeyEvent(KeyEvent event) {
8552 switch (event.getKeyCode()) {
8553 case KeyEvent.KEYCODE_DPAD_DOWN:
8554 direction = View.FOCUS_DOWN;
8556 case KeyEvent.KEYCODE_DPAD_UP:
8557 direction = View.FOCUS_UP;
8559 case KeyEvent.KEYCODE_DPAD_LEFT:
8560 direction = View.FOCUS_LEFT;
8562 case KeyEvent.KEYCODE_DPAD_RIGHT:
8563 direction = View.FOCUS_RIGHT;
8565 case KeyEvent.KEYCODE_TAB:
8566 direction = event.isShiftPressed() ? View.FOCUS_BACKWARD : View.FOCUS_FORWARD;
8569 if (direction != 0 && mWebView.focusSearch(direction) == null) {
8570 // Can't take focus in that direction
8573 int eventHubAction = EventHub.KEY_UP;
8574 if (event.getAction() == KeyEvent.ACTION_DOWN) {
8575 eventHubAction = EventHub.KEY_DOWN;
8576 int sound = keyCodeToSoundsEffect(event.getKeyCode());
8578 mWebView.playSoundEffect(sound);
8581 sendBatchableInputMessage(eventHubAction, direction, 0, event);
8585 * @return Whether accessibility script has been injected.
8587 private boolean accessibilityScriptInjected() {
8588 // TODO: Maybe the injected script should announce its presence in
8589 // the page meta-tag so the nativePageShouldHandleShiftAndArrows
8590 // will check that as one of the conditions it looks for
8591 return mAccessibilityScriptInjected;
8595 * See {@link WebView#setBackgroundColor(int)}
8598 public void setBackgroundColor(int color) {
8599 mBackgroundColor = color;
8600 mWebViewCore.sendMessage(EventHub.SET_BACKGROUND_COLOR, color);
8604 * See {@link WebView#debugDump()}
8608 public void debugDump() {
8612 * Draw the HTML page into the specified canvas. This call ignores any
8613 * view-specific zoom, scroll offset, or other changes. It does not draw
8614 * any view-specific chrome, such as progress or URL bars.
8616 * only needs to be accessible to Browser and testing
8618 public void drawPage(Canvas canvas) {
8619 calcOurContentVisibleRectF(mVisibleContentRect);
8620 nativeDraw(canvas, mVisibleContentRect, 0, 0, false);
8624 * Enable the communication b/t the webView and VideoViewProxy
8626 * only used by the Browser
8628 public void setHTML5VideoViewProxy(HTML5VideoViewProxy proxy) {
8629 mHTML5VideoViewProxy = proxy;
8633 * Set the time to wait between passing touches to WebCore. See also the
8634 * TOUCH_SENT_INTERVAL member for further discussion.
8636 * This is only used by the DRT test application.
8638 public void setTouchInterval(int interval) {
8639 mCurrentTouchInterval = interval;
8643 * Copy text into the clipboard. This is called indirectly from
8645 * @param text The text to put into the clipboard.
8647 private void copyToClipboard(String text) {
8648 ClipboardManager cm = (ClipboardManager)mContext
8649 .getSystemService(Context.CLIPBOARD_SERVICE);
8650 ClipData clip = ClipData.newPlainText(getTitle(), text);
8651 cm.setPrimaryClip(clip);
8654 /*package*/ void autoFillForm(int autoFillQueryId) {
8655 mPrivateHandler.obtainMessage(AUTOFILL_FORM, autoFillQueryId, 0)
8659 /* package */ ViewManager getViewManager() {
8660 return mViewManager;
8663 /** send content invalidate */
8664 protected void contentInvalidateAll() {
8665 if (mWebViewCore != null && !mBlockWebkitViewMessages) {
8666 mWebViewCore.sendMessage(EventHub.CONTENT_INVALIDATE_ALL);
8670 /** discard all textures from tiles. Used in Profiled WebView */
8671 public void discardAllTextures() {
8672 nativeDiscardAllTextures();
8676 public void setLayerType(int layerType, Paint paint) {
8677 updateHwAccelerated();
8680 private void updateHwAccelerated() {
8681 if (mNativeClass == 0) {
8684 boolean hwAccelerated = false;
8685 if (mWebView.isHardwareAccelerated()
8686 && mWebView.getLayerType() != View.LAYER_TYPE_SOFTWARE) {
8687 hwAccelerated = true;
8690 // result is of type LayerAndroid::InvalidateFlags, non zero means invalidate/redraw
8691 int result = nativeSetHwAccelerated(mNativeClass, hwAccelerated);
8692 if (mWebViewCore != null && !mBlockWebkitViewMessages && result != 0) {
8693 mWebViewCore.contentDraw();
8698 * Begin collecting per-tile profiling data
8700 * only used by profiling tests
8702 public void tileProfilingStart() {
8703 nativeTileProfilingStart();
8706 * Return per-tile profiling data
8708 * only used by profiling tests
8710 public float tileProfilingStop() {
8711 return nativeTileProfilingStop();
8714 /** only used by profiling tests */
8715 public void tileProfilingClear() {
8716 nativeTileProfilingClear();
8718 /** only used by profiling tests */
8719 public int tileProfilingNumFrames() {
8720 return nativeTileProfilingNumFrames();
8722 /** only used by profiling tests */
8723 public int tileProfilingNumTilesInFrame(int frame) {
8724 return nativeTileProfilingNumTilesInFrame(frame);
8726 /** only used by profiling tests */
8727 public int tileProfilingGetInt(int frame, int tile, String key) {
8728 return nativeTileProfilingGetInt(frame, tile, key);
8730 /** only used by profiling tests */
8731 public float tileProfilingGetFloat(int frame, int tile, String key) {
8732 return nativeTileProfilingGetFloat(frame, tile, key);
8736 * Checks the focused content for an editable text field. This can be
8737 * text input or ContentEditable.
8738 * @return true if the focused item is an editable text field.
8740 boolean focusCandidateIsEditableText() {
8741 if (mFocusedNode != null) {
8742 return mFocusedNode.mEditable;
8748 private void postInvalidate() {
8749 mWebView.postInvalidate();
8752 private native void nativeCreate(int ptr, String drawableDir, boolean isHighEndGfx);
8753 private native void nativeDebugDump();
8754 private native void nativeDestroy();
8757 * Draw the picture set with a background color and extra. If
8758 * "splitIfNeeded" is true and the return value is not 0, the return value
8759 * MUST be passed to WebViewCore with SPLIT_PICTURE_SET message so that the
8760 * native allocation can be freed.
8762 private native int nativeDraw(Canvas canvas, RectF visibleRect,
8763 int color, int extra, boolean splitIfNeeded);
8764 private native void nativeDumpDisplayTree(String urlOrNull);
8765 private native boolean nativeEvaluateLayersAnimations(int nativeInstance);
8766 private native int nativeGetDrawGLFunction(int nativeInstance, Rect rect,
8767 Rect viewRect, RectF visibleRect, float scale, int extras);
8768 private native void nativeUpdateDrawGLFunction(Rect rect, Rect viewRect,
8769 RectF visibleRect, float scale);
8770 private native String nativeGetSelection();
8771 private native Rect nativeLayerBounds(int layer);
8772 private native void nativeSetHeightCanMeasure(boolean measure);
8773 private native boolean nativeSetBaseLayer(int nativeInstance,
8774 int layer, Region invalRegion,
8775 boolean showVisualIndicator, boolean isPictureAfterFirstLayout);
8776 private native int nativeGetBaseLayer();
8777 private native void nativeReplaceBaseContent(int content);
8778 private native void nativeCopyBaseContentToPicture(Picture pict);
8779 private native boolean nativeHasContent();
8780 private native void nativeStopGL();
8781 private native void nativeDiscardAllTextures();
8782 private native void nativeTileProfilingStart();
8783 private native float nativeTileProfilingStop();
8784 private native void nativeTileProfilingClear();
8785 private native int nativeTileProfilingNumFrames();
8786 private native int nativeTileProfilingNumTilesInFrame(int frame);
8787 private native int nativeTileProfilingGetInt(int frame, int tile, String key);
8788 private native float nativeTileProfilingGetFloat(int frame, int tile, String key);
8790 private native void nativeUseHardwareAccelSkia(boolean enabled);
8792 // Returns a pointer to the scrollable LayerAndroid at the given point.
8793 private native int nativeScrollableLayer(int x, int y, Rect scrollRect,
8796 * Scroll the specified layer.
8797 * @param layer Id of the layer to scroll, as determined by nativeScrollableLayer.
8798 * @param newX Destination x position to which to scroll.
8799 * @param newY Destination y position to which to scroll.
8800 * @return True if the layer is successfully scrolled.
8802 private native boolean nativeScrollLayer(int layer, int newX, int newY);
8803 private native void nativeSetIsScrolling(boolean isScrolling);
8804 private native int nativeGetBackgroundColor();
8805 native boolean nativeSetProperty(String key, String value);
8806 native String nativeGetProperty(String key);
8808 * See {@link ComponentCallbacks2} for the trim levels and descriptions
8810 private static native void nativeOnTrimMemory(int level);
8811 private static native void nativeSetPauseDrawing(int instance, boolean pause);
8812 private static native void nativeSetTextSelection(int instance, int selection);
8813 private static native int nativeGetHandleLayerId(int instance, int handle,
8814 Point cursorLocation, QuadF textQuad);
8815 private static native boolean nativeIsBaseFirst(int instance);
8816 private static native void nativeMapLayerRect(int instance, int layerId,
8818 // Returns 1 if a layer sync is needed, else 0
8819 private static native int nativeSetHwAccelerated(int instance, boolean hwAccelerated);