• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.browser;
18 
19 import android.app.Activity;
20 import android.app.DownloadManager;
21 import android.app.SearchManager;
22 import android.content.ClipboardManager;
23 import android.content.ContentProvider;
24 import android.content.ContentProviderClient;
25 import android.content.ContentResolver;
26 import android.content.ContentUris;
27 import android.content.ContentValues;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.pm.PackageManager;
31 import android.content.pm.ResolveInfo;
32 import android.content.res.Configuration;
33 import android.content.res.TypedArray;
34 import android.database.ContentObserver;
35 import android.database.Cursor;
36 import android.database.sqlite.SQLiteDatabase;
37 import android.graphics.Bitmap;
38 import android.graphics.Canvas;
39 import android.net.Uri;
40 import android.net.http.SslError;
41 import android.os.AsyncTask;
42 import android.os.Bundle;
43 import android.os.Environment;
44 import android.os.Handler;
45 import android.os.Message;
46 import android.os.PowerManager;
47 import android.os.PowerManager.WakeLock;
48 import android.preference.PreferenceActivity;
49 import android.provider.Browser;
50 import android.provider.BrowserContract;
51 import android.provider.BrowserContract.Images;
52 import android.provider.ContactsContract;
53 import android.provider.ContactsContract.Intents.Insert;
54 import android.speech.RecognizerIntent;
55 import android.text.TextUtils;
56 import android.util.Log;
57 import android.util.Patterns;
58 import android.view.ActionMode;
59 import android.view.ContextMenu;
60 import android.view.ContextMenu.ContextMenuInfo;
61 import android.view.Gravity;
62 import android.view.KeyEvent;
63 import android.view.Menu;
64 import android.view.MenuInflater;
65 import android.view.MenuItem;
66 import android.view.MenuItem.OnMenuItemClickListener;
67 import android.view.MotionEvent;
68 import android.view.View;
69 import android.webkit.CookieManager;
70 import android.webkit.CookieSyncManager;
71 import android.webkit.HttpAuthHandler;
72 import android.webkit.MimeTypeMap;
73 import android.webkit.SslErrorHandler;
74 import android.webkit.ValueCallback;
75 import android.webkit.WebChromeClient;
76 import android.webkit.WebIconDatabase;
77 import android.webkit.WebSettings;
78 import android.webkit.WebView;
79 import android.widget.Toast;
80 
81 import com.android.browser.IntentHandler.UrlData;
82 import com.android.browser.UI.ComboViews;
83 import com.android.browser.UI.DropdownChangeListener;
84 import com.android.browser.provider.BrowserProvider;
85 import com.android.browser.provider.BrowserProvider2.Thumbnails;
86 import com.android.browser.provider.SnapshotProvider.Snapshots;
87 import com.android.browser.search.SearchEngine;
88 import com.android.common.Search;
89 
90 import java.io.ByteArrayOutputStream;
91 import java.io.File;
92 import java.io.FileOutputStream;
93 import java.io.IOException;
94 import java.net.URLEncoder;
95 import java.text.DateFormat;
96 import java.text.SimpleDateFormat;
97 import java.util.ArrayList;
98 import java.util.Calendar;
99 import java.util.Date;
100 import java.util.HashMap;
101 import java.util.List;
102 import java.util.Map;
103 
104 /**
105  * Controller for browser
106  */
107 public class Controller
108         implements WebViewController, UiController {
109 
110     private static final String LOGTAG = "Controller";
111     private static final String SEND_APP_ID_EXTRA =
112         "android.speech.extras.SEND_APPLICATION_ID_EXTRA";
113     private static final String INCOGNITO_URI = "browser:incognito";
114 
115 
116     // public message ids
117     public final static int LOAD_URL = 1001;
118     public final static int STOP_LOAD = 1002;
119 
120     // Message Ids
121     private static final int FOCUS_NODE_HREF = 102;
122     private static final int RELEASE_WAKELOCK = 107;
123 
124     static final int UPDATE_BOOKMARK_THUMBNAIL = 108;
125 
126     private static final int OPEN_BOOKMARKS = 201;
127 
128     private static final int EMPTY_MENU = -1;
129 
130     // activity requestCode
131     final static int COMBO_VIEW = 1;
132     final static int PREFERENCES_PAGE = 3;
133     final static int FILE_SELECTED = 4;
134     final static int AUTOFILL_SETUP = 5;
135 
136     private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
137 
138     // As the ids are dynamically created, we can't guarantee that they will
139     // be in sequence, so this static array maps ids to a window number.
140     final static private int[] WINDOW_SHORTCUT_ID_ARRAY =
141     { R.id.window_one_menu_id, R.id.window_two_menu_id,
142       R.id.window_three_menu_id, R.id.window_four_menu_id,
143       R.id.window_five_menu_id, R.id.window_six_menu_id,
144       R.id.window_seven_menu_id, R.id.window_eight_menu_id };
145 
146     // "source" parameter for Google search through search key
147     final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key";
148     // "source" parameter for Google search through simplily type
149     final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type";
150 
151     // "no-crash-recovery" parameter in intent to suppress crash recovery
152     final static String NO_CRASH_RECOVERY = "no-crash-recovery";
153 
154     // A bitmap that is re-used in createScreenshot as scratch space
155     private static Bitmap sThumbnailBitmap;
156 
157     private Activity mActivity;
158     private UI mUi;
159     private TabControl mTabControl;
160     private BrowserSettings mSettings;
161     private WebViewFactory mFactory;
162 
163     private WakeLock mWakeLock;
164 
165     private UrlHandler mUrlHandler;
166     private UploadHandler mUploadHandler;
167     private IntentHandler mIntentHandler;
168     private PageDialogsHandler mPageDialogsHandler;
169     private NetworkStateHandler mNetworkHandler;
170 
171     private Message mAutoFillSetupMessage;
172 
173     private boolean mShouldShowErrorConsole;
174 
175     private SystemAllowGeolocationOrigins mSystemAllowGeolocationOrigins;
176 
177     // FIXME, temp address onPrepareMenu performance problem.
178     // When we move everything out of view, we should rewrite this.
179     private int mCurrentMenuState = 0;
180     private int mMenuState = R.id.MAIN_MENU;
181     private int mOldMenuState = EMPTY_MENU;
182     private Menu mCachedMenu;
183 
184     private boolean mMenuIsDown;
185 
186     // For select and find, we keep track of the ActionMode so that
187     // finish() can be called as desired.
188     private ActionMode mActionMode;
189 
190     /**
191      * Only meaningful when mOptionsMenuOpen is true.  This variable keeps track
192      * of whether the configuration has changed.  The first onMenuOpened call
193      * after a configuration change is simply a reopening of the same menu
194      * (i.e. mIconView did not change).
195      */
196     private boolean mConfigChanged;
197 
198     /**
199      * Keeps track of whether the options menu is open. This is important in
200      * determining whether to show or hide the title bar overlay
201      */
202     private boolean mOptionsMenuOpen;
203 
204     /**
205      * Whether or not the options menu is in its bigger, popup menu form. When
206      * true, we want the title bar overlay to be gone. When false, we do not.
207      * Only meaningful if mOptionsMenuOpen is true.
208      */
209     private boolean mExtendedMenuOpen;
210 
211     private boolean mInLoad;
212 
213     private boolean mActivityPaused = true;
214     private boolean mLoadStopped;
215 
216     private Handler mHandler;
217     // Checks to see when the bookmarks database has changed, and updates the
218     // Tabs' notion of whether they represent bookmarked sites.
219     private ContentObserver mBookmarksObserver;
220     private CrashRecoveryHandler mCrashRecoveryHandler;
221 
222     private boolean mBlockEvents;
223 
Controller(Activity browser, boolean preloadCrashState)224     public Controller(Activity browser, boolean preloadCrashState) {
225         mActivity = browser;
226         mSettings = BrowserSettings.getInstance();
227         mTabControl = new TabControl(this);
228         mSettings.setController(this);
229         mCrashRecoveryHandler = CrashRecoveryHandler.initialize(this);
230         if (preloadCrashState) {
231             mCrashRecoveryHandler.preloadCrashState();
232         }
233         mFactory = new BrowserWebViewFactory(browser);
234 
235         mUrlHandler = new UrlHandler(this);
236         mIntentHandler = new IntentHandler(mActivity, this);
237         mPageDialogsHandler = new PageDialogsHandler(mActivity, this);
238 
239         startHandler();
240         mBookmarksObserver = new ContentObserver(mHandler) {
241             @Override
242             public void onChange(boolean selfChange) {
243                 int size = mTabControl.getTabCount();
244                 for (int i = 0; i < size; i++) {
245                     mTabControl.getTab(i).updateBookmarkedStatus();
246                 }
247             }
248 
249         };
250         browser.getContentResolver().registerContentObserver(
251                 BrowserContract.Bookmarks.CONTENT_URI, true, mBookmarksObserver);
252 
253         mNetworkHandler = new NetworkStateHandler(mActivity, this);
254         // Start watching the default geolocation permissions
255         mSystemAllowGeolocationOrigins =
256                 new SystemAllowGeolocationOrigins(mActivity.getApplicationContext());
257         mSystemAllowGeolocationOrigins.start();
258 
259         openIconDatabase();
260     }
261 
start(final Bundle icicle, final Intent intent)262     void start(final Bundle icicle, final Intent intent) {
263         boolean noCrashRecovery = intent.getBooleanExtra(NO_CRASH_RECOVERY, false);
264         if (icicle != null || noCrashRecovery) {
265             doStart(icicle, intent, false);
266         } else {
267             mCrashRecoveryHandler.startRecovery(intent);
268         }
269     }
270 
doStart(final Bundle icicle, final Intent intent, final boolean fromCrash)271     void doStart(final Bundle icicle, final Intent intent, final boolean fromCrash) {
272         // Unless the last browser usage was within 24 hours, destroy any
273         // remaining incognito tabs.
274 
275         Calendar lastActiveDate = icicle != null ?
276                 (Calendar) icicle.getSerializable("lastActiveDate") : null;
277         Calendar today = Calendar.getInstance();
278         Calendar yesterday = Calendar.getInstance();
279         yesterday.add(Calendar.DATE, -1);
280 
281         final boolean restoreIncognitoTabs = !(lastActiveDate == null
282             || lastActiveDate.before(yesterday)
283             || lastActiveDate.after(today));
284 
285         // Find out if we will restore any state and remember the tab.
286         final long currentTabId =
287                 mTabControl.canRestoreState(icicle, restoreIncognitoTabs);
288 
289         if (currentTabId == -1) {
290             // Not able to restore so we go ahead and clear session cookies.  We
291             // must do this before trying to login the user as we don't want to
292             // clear any session cookies set during login.
293             CookieManager.getInstance().removeSessionCookie();
294         }
295 
296         GoogleAccountLogin.startLoginIfNeeded(mActivity,
297                 new Runnable() {
298                     @Override public void run() {
299                         onPreloginFinished(icicle, intent, currentTabId, restoreIncognitoTabs,
300                                 fromCrash);
301                     }
302                 });
303     }
304 
onPreloginFinished(Bundle icicle, Intent intent, long currentTabId, boolean restoreIncognitoTabs, boolean fromCrash)305     private void onPreloginFinished(Bundle icicle, Intent intent, long currentTabId,
306             boolean restoreIncognitoTabs, boolean fromCrash) {
307         if (currentTabId == -1) {
308             BackgroundHandler.execute(new PruneThumbnails(mActivity, null));
309             final Bundle extra = intent.getExtras();
310             // Create an initial tab.
311             // If the intent is ACTION_VIEW and data is not null, the Browser is
312             // invoked to view the content by another application. In this case,
313             // the tab will be close when exit.
314             UrlData urlData = IntentHandler.getUrlDataFromIntent(intent);
315             Tab t = null;
316             if (urlData.isEmpty()) {
317                 t = openTabToHomePage();
318             } else {
319                 t = openTab(urlData);
320             }
321             if (t != null) {
322                 t.setAppId(intent.getStringExtra(Browser.EXTRA_APPLICATION_ID));
323             }
324             WebView webView = t.getWebView();
325             if (extra != null) {
326                 int scale = extra.getInt(Browser.INITIAL_ZOOM_LEVEL, 0);
327                 if (scale > 0 && scale <= 1000) {
328                     webView.setInitialScale(scale);
329                 }
330             }
331             mUi.updateTabs(mTabControl.getTabs());
332         } else {
333             mTabControl.restoreState(icicle, currentTabId, restoreIncognitoTabs,
334                     mUi.needsRestoreAllTabs());
335             List<Tab> tabs = mTabControl.getTabs();
336             ArrayList<Long> restoredTabs = new ArrayList<Long>(tabs.size());
337             for (Tab t : tabs) {
338                 restoredTabs.add(t.getId());
339             }
340             BackgroundHandler.execute(new PruneThumbnails(mActivity, restoredTabs));
341             if (tabs.size() == 0) {
342                 openTabToHomePage();
343             }
344             mUi.updateTabs(tabs);
345             // TabControl.restoreState() will create a new tab even if
346             // restoring the state fails.
347             setActiveTab(mTabControl.getCurrentTab());
348             // Handle the intent if needed. If icicle != null, we are restoring
349             // and the intent will be stale - ignore it.
350             if (icicle == null || fromCrash) {
351                 mIntentHandler.onNewIntent(intent);
352             }
353         }
354         // Read JavaScript flags if it exists.
355         String jsFlags = getSettings().getJsEngineFlags();
356         if (jsFlags.trim().length() != 0) {
357             getCurrentWebView().setJsFlags(jsFlags);
358         }
359         if (BrowserActivity.ACTION_SHOW_BOOKMARKS.equals(intent.getAction())) {
360             bookmarksOrHistoryPicker(ComboViews.Bookmarks);
361         }
362     }
363 
364     private static class PruneThumbnails implements Runnable {
365         private Context mContext;
366         private List<Long> mIds;
367 
PruneThumbnails(Context context, List<Long> preserveIds)368         PruneThumbnails(Context context, List<Long> preserveIds) {
369             mContext = context.getApplicationContext();
370             mIds = preserveIds;
371         }
372 
373         @Override
run()374         public void run() {
375             ContentResolver cr = mContext.getContentResolver();
376             if (mIds == null || mIds.size() == 0) {
377                 cr.delete(Thumbnails.CONTENT_URI, null, null);
378             } else {
379                 int length = mIds.size();
380                 StringBuilder where = new StringBuilder();
381                 where.append(Thumbnails._ID);
382                 where.append(" not in (");
383                 for (int i = 0; i < length; i++) {
384                     where.append(mIds.get(i));
385                     if (i < (length - 1)) {
386                         where.append(",");
387                     }
388                 }
389                 where.append(")");
390                 cr.delete(Thumbnails.CONTENT_URI, where.toString(), null);
391             }
392         }
393 
394     }
395 
396     @Override
getWebViewFactory()397     public WebViewFactory getWebViewFactory() {
398         return mFactory;
399     }
400 
401     @Override
onSetWebView(Tab tab, WebView view)402     public void onSetWebView(Tab tab, WebView view) {
403         mUi.onSetWebView(tab, view);
404     }
405 
406     @Override
createSubWindow(Tab tab)407     public void createSubWindow(Tab tab) {
408         endActionMode();
409         WebView mainView = tab.getWebView();
410         WebView subView = mFactory.createWebView((mainView == null)
411                 ? false
412                 : mainView.isPrivateBrowsingEnabled());
413         mUi.createSubWindow(tab, subView);
414     }
415 
416     @Override
getContext()417     public Context getContext() {
418         return mActivity;
419     }
420 
421     @Override
getActivity()422     public Activity getActivity() {
423         return mActivity;
424     }
425 
setUi(UI ui)426     void setUi(UI ui) {
427         mUi = ui;
428     }
429 
getSettings()430     BrowserSettings getSettings() {
431         return mSettings;
432     }
433 
getIntentHandler()434     IntentHandler getIntentHandler() {
435         return mIntentHandler;
436     }
437 
438     @Override
getUi()439     public UI getUi() {
440         return mUi;
441     }
442 
getMaxTabs()443     int getMaxTabs() {
444         return mActivity.getResources().getInteger(R.integer.max_tabs);
445     }
446 
447     @Override
getTabControl()448     public TabControl getTabControl() {
449         return mTabControl;
450     }
451 
452     @Override
getTabs()453     public List<Tab> getTabs() {
454         return mTabControl.getTabs();
455     }
456 
457     // Open the icon database.
openIconDatabase()458     private void openIconDatabase() {
459         // We have to call getInstance on the UI thread
460         final WebIconDatabase instance = WebIconDatabase.getInstance();
461         BackgroundHandler.execute(new Runnable() {
462 
463             @Override
464             public void run() {
465                 instance.open(mActivity.getDir("icons", 0).getPath());
466             }
467         });
468     }
469 
startHandler()470     private void startHandler() {
471         mHandler = new Handler() {
472 
473             @Override
474             public void handleMessage(Message msg) {
475                 switch (msg.what) {
476                     case OPEN_BOOKMARKS:
477                         bookmarksOrHistoryPicker(ComboViews.Bookmarks);
478                         break;
479                     case FOCUS_NODE_HREF:
480                     {
481                         String url = (String) msg.getData().get("url");
482                         String title = (String) msg.getData().get("title");
483                         String src = (String) msg.getData().get("src");
484                         if (url == "") url = src; // use image if no anchor
485                         if (TextUtils.isEmpty(url)) {
486                             break;
487                         }
488                         HashMap focusNodeMap = (HashMap) msg.obj;
489                         WebView view = (WebView) focusNodeMap.get("webview");
490                         // Only apply the action if the top window did not change.
491                         if (getCurrentTopWebView() != view) {
492                             break;
493                         }
494                         switch (msg.arg1) {
495                             case R.id.open_context_menu_id:
496                                 loadUrlFromContext(url);
497                                 break;
498                             case R.id.view_image_context_menu_id:
499                                 loadUrlFromContext(src);
500                                 break;
501                             case R.id.open_newtab_context_menu_id:
502                                 final Tab parent = mTabControl.getCurrentTab();
503                                 openTab(url, parent,
504                                         !mSettings.openInBackground(), true);
505                                 break;
506                             case R.id.copy_link_context_menu_id:
507                                 copy(url);
508                                 break;
509                             case R.id.save_link_context_menu_id:
510                             case R.id.download_context_menu_id:
511                                 DownloadHandler.onDownloadStartNoStream(
512                                         mActivity, url, null, null, null,
513                                         view.isPrivateBrowsingEnabled());
514                                 break;
515                         }
516                         break;
517                     }
518 
519                     case LOAD_URL:
520                         loadUrlFromContext((String) msg.obj);
521                         break;
522 
523                     case STOP_LOAD:
524                         stopLoading();
525                         break;
526 
527                     case RELEASE_WAKELOCK:
528                         if (mWakeLock != null && mWakeLock.isHeld()) {
529                             mWakeLock.release();
530                             // if we reach here, Browser should be still in the
531                             // background loading after WAKELOCK_TIMEOUT (5-min).
532                             // To avoid burning the battery, stop loading.
533                             mTabControl.stopAllLoading();
534                         }
535                         break;
536 
537                     case UPDATE_BOOKMARK_THUMBNAIL:
538                         Tab tab = (Tab) msg.obj;
539                         if (tab != null) {
540                             updateScreenshot(tab);
541                         }
542                         break;
543                 }
544             }
545         };
546 
547     }
548 
549     @Override
getCurrentTab()550     public Tab getCurrentTab() {
551         return mTabControl.getCurrentTab();
552     }
553 
554     @Override
shareCurrentPage()555     public void shareCurrentPage() {
556         shareCurrentPage(mTabControl.getCurrentTab());
557     }
558 
shareCurrentPage(Tab tab)559     private void shareCurrentPage(Tab tab) {
560         if (tab != null) {
561             sharePage(mActivity, tab.getTitle(),
562                     tab.getUrl(), tab.getFavicon(),
563                     createScreenshot(tab.getWebView(),
564                             getDesiredThumbnailWidth(mActivity),
565                             getDesiredThumbnailHeight(mActivity)));
566         }
567     }
568 
569     /**
570      * Share a page, providing the title, url, favicon, and a screenshot.  Uses
571      * an {@link Intent} to launch the Activity chooser.
572      * @param c Context used to launch a new Activity.
573      * @param title Title of the page.  Stored in the Intent with
574      *          {@link Intent#EXTRA_SUBJECT}
575      * @param url URL of the page.  Stored in the Intent with
576      *          {@link Intent#EXTRA_TEXT}
577      * @param favicon Bitmap of the favicon for the page.  Stored in the Intent
578      *          with {@link Browser#EXTRA_SHARE_FAVICON}
579      * @param screenshot Bitmap of a screenshot of the page.  Stored in the
580      *          Intent with {@link Browser#EXTRA_SHARE_SCREENSHOT}
581      */
sharePage(Context c, String title, String url, Bitmap favicon, Bitmap screenshot)582     static final void sharePage(Context c, String title, String url,
583             Bitmap favicon, Bitmap screenshot) {
584         Intent send = new Intent(Intent.ACTION_SEND);
585         send.setType("text/plain");
586         send.putExtra(Intent.EXTRA_TEXT, url);
587         send.putExtra(Intent.EXTRA_SUBJECT, title);
588         send.putExtra(Browser.EXTRA_SHARE_FAVICON, favicon);
589         send.putExtra(Browser.EXTRA_SHARE_SCREENSHOT, screenshot);
590         try {
591             c.startActivity(Intent.createChooser(send, c.getString(
592                     R.string.choosertitle_sharevia)));
593         } catch(android.content.ActivityNotFoundException ex) {
594             // if no app handles it, do nothing
595         }
596     }
597 
copy(CharSequence text)598     private void copy(CharSequence text) {
599         ClipboardManager cm = (ClipboardManager) mActivity
600                 .getSystemService(Context.CLIPBOARD_SERVICE);
601         cm.setText(text);
602     }
603 
604     // lifecycle
605 
onConfgurationChanged(Configuration config)606     protected void onConfgurationChanged(Configuration config) {
607         mConfigChanged = true;
608         if (mPageDialogsHandler != null) {
609             mPageDialogsHandler.onConfigurationChanged(config);
610         }
611         mUi.onConfigurationChanged(config);
612     }
613 
614     @Override
handleNewIntent(Intent intent)615     public void handleNewIntent(Intent intent) {
616         if (!mUi.isWebShowing()) {
617             mUi.showWeb(false);
618         }
619         mIntentHandler.onNewIntent(intent);
620     }
621 
onPause()622     protected void onPause() {
623         if (mUi.isCustomViewShowing()) {
624             hideCustomView();
625         }
626         if (mActivityPaused) {
627             Log.e(LOGTAG, "BrowserActivity is already paused.");
628             return;
629         }
630         mActivityPaused = true;
631         Tab tab = mTabControl.getCurrentTab();
632         if (tab != null) {
633             tab.pause();
634             if (!pauseWebViewTimers(tab)) {
635                 if (mWakeLock == null) {
636                     PowerManager pm = (PowerManager) mActivity
637                             .getSystemService(Context.POWER_SERVICE);
638                     mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Browser");
639                 }
640                 mWakeLock.acquire();
641                 mHandler.sendMessageDelayed(mHandler
642                         .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT);
643             }
644         }
645         mUi.onPause();
646         mNetworkHandler.onPause();
647 
648         WebView.disablePlatformNotifications();
649         NfcHandler.unregister(mActivity);
650         if (sThumbnailBitmap != null) {
651             sThumbnailBitmap.recycle();
652             sThumbnailBitmap = null;
653         }
654     }
655 
onSaveInstanceState(Bundle outState)656     void onSaveInstanceState(Bundle outState) {
657         // the default implementation requires each view to have an id. As the
658         // browser handles the state itself and it doesn't use id for the views,
659         // don't call the default implementation. Otherwise it will trigger the
660         // warning like this, "couldn't save which view has focus because the
661         // focused view XXX has no id".
662 
663         // Save all the tabs
664         mTabControl.saveState(outState);
665         if (!outState.isEmpty()) {
666             // Save time so that we know how old incognito tabs (if any) are.
667             outState.putSerializable("lastActiveDate", Calendar.getInstance());
668         }
669     }
670 
onResume()671     void onResume() {
672         if (!mActivityPaused) {
673             Log.e(LOGTAG, "BrowserActivity is already resumed.");
674             return;
675         }
676         mActivityPaused = false;
677         Tab current = mTabControl.getCurrentTab();
678         if (current != null) {
679             current.resume();
680             resumeWebViewTimers(current);
681         }
682         releaseWakeLock();
683 
684         mUi.onResume();
685         mNetworkHandler.onResume();
686         WebView.enablePlatformNotifications();
687         NfcHandler.register(mActivity, this);
688     }
689 
releaseWakeLock()690     private void releaseWakeLock() {
691         if (mWakeLock != null && mWakeLock.isHeld()) {
692             mHandler.removeMessages(RELEASE_WAKELOCK);
693             mWakeLock.release();
694         }
695     }
696 
697     /**
698      * resume all WebView timers using the WebView instance of the given tab
699      * @param tab guaranteed non-null
700      */
resumeWebViewTimers(Tab tab)701     private void resumeWebViewTimers(Tab tab) {
702         boolean inLoad = tab.inPageLoad();
703         if ((!mActivityPaused && !inLoad) || (mActivityPaused && inLoad)) {
704             CookieSyncManager.getInstance().startSync();
705             WebView w = tab.getWebView();
706             WebViewTimersControl.getInstance().onBrowserActivityResume(w);
707         }
708     }
709 
710     /**
711      * Pause all WebView timers using the WebView of the given tab
712      * @param tab
713      * @return true if the timers are paused or tab is null
714      */
pauseWebViewTimers(Tab tab)715     private boolean pauseWebViewTimers(Tab tab) {
716         if (tab == null) {
717             return true;
718         } else if (!tab.inPageLoad()) {
719             CookieSyncManager.getInstance().stopSync();
720             WebViewTimersControl.getInstance().onBrowserActivityPause(getCurrentWebView());
721             return true;
722         }
723         return false;
724     }
725 
onDestroy()726     void onDestroy() {
727         if (mUploadHandler != null && !mUploadHandler.handled()) {
728             mUploadHandler.onResult(Activity.RESULT_CANCELED, null);
729             mUploadHandler = null;
730         }
731         if (mTabControl == null) return;
732         mUi.onDestroy();
733         // Remove the current tab and sub window
734         Tab t = mTabControl.getCurrentTab();
735         if (t != null) {
736             dismissSubWindow(t);
737             removeTab(t);
738         }
739         mActivity.getContentResolver().unregisterContentObserver(mBookmarksObserver);
740         // Destroy all the tabs
741         mTabControl.destroy();
742         WebIconDatabase.getInstance().close();
743         // Stop watching the default geolocation permissions
744         mSystemAllowGeolocationOrigins.stop();
745         mSystemAllowGeolocationOrigins = null;
746     }
747 
isActivityPaused()748     protected boolean isActivityPaused() {
749         return mActivityPaused;
750     }
751 
onLowMemory()752     protected void onLowMemory() {
753         mTabControl.freeMemory();
754     }
755 
756     @Override
shouldShowErrorConsole()757     public boolean shouldShowErrorConsole() {
758         return mShouldShowErrorConsole;
759     }
760 
setShouldShowErrorConsole(boolean show)761     protected void setShouldShowErrorConsole(boolean show) {
762         if (show == mShouldShowErrorConsole) {
763             // Nothing to do.
764             return;
765         }
766         mShouldShowErrorConsole = show;
767         Tab t = mTabControl.getCurrentTab();
768         if (t == null) {
769             // There is no current tab so we cannot toggle the error console
770             return;
771         }
772         mUi.setShouldShowErrorConsole(t, show);
773     }
774 
775     @Override
stopLoading()776     public void stopLoading() {
777         mLoadStopped = true;
778         Tab tab = mTabControl.getCurrentTab();
779         WebView w = getCurrentTopWebView();
780         w.stopLoading();
781         mUi.onPageStopped(tab);
782     }
783 
didUserStopLoading()784     boolean didUserStopLoading() {
785         return mLoadStopped;
786     }
787 
788     // WebViewController
789 
790     @Override
onPageStarted(Tab tab, WebView view, Bitmap favicon)791     public void onPageStarted(Tab tab, WebView view, Bitmap favicon) {
792 
793         // We've started to load a new page. If there was a pending message
794         // to save a screenshot then we will now take the new page and save
795         // an incorrect screenshot. Therefore, remove any pending thumbnail
796         // messages from the queue.
797         mHandler.removeMessages(Controller.UPDATE_BOOKMARK_THUMBNAIL,
798                 tab);
799 
800         // reset sync timer to avoid sync starts during loading a page
801         CookieSyncManager.getInstance().resetSync();
802 
803         if (!mNetworkHandler.isNetworkUp()) {
804             view.setNetworkAvailable(false);
805         }
806 
807         // when BrowserActivity just starts, onPageStarted may be called before
808         // onResume as it is triggered from onCreate. Call resumeWebViewTimers
809         // to start the timer. As we won't switch tabs while an activity is in
810         // pause state, we can ensure calling resume and pause in pair.
811         if (mActivityPaused) {
812             resumeWebViewTimers(tab);
813         }
814         mLoadStopped = false;
815         endActionMode();
816 
817         mUi.onTabDataChanged(tab);
818 
819         String url = tab.getUrl();
820         // update the bookmark database for favicon
821         maybeUpdateFavicon(tab, null, url, favicon);
822 
823         Performance.tracePageStart(url);
824 
825         // Performance probe
826         if (false) {
827             Performance.onPageStarted();
828         }
829 
830     }
831 
832     @Override
onPageFinished(Tab tab)833     public void onPageFinished(Tab tab) {
834         mUi.onTabDataChanged(tab);
835         if (!tab.isPrivateBrowsingEnabled()
836                 && !TextUtils.isEmpty(tab.getUrl())
837                 && !tab.isSnapshot()) {
838             // Only update the bookmark screenshot if the user did not
839             // cancel the load early and there is not already
840             // a pending update for the tab.
841             if (tab.inForeground() && !didUserStopLoading()
842                     || !tab.inForeground()) {
843                 if (!mHandler.hasMessages(UPDATE_BOOKMARK_THUMBNAIL, tab)) {
844                     mHandler.sendMessageDelayed(mHandler.obtainMessage(
845                             UPDATE_BOOKMARK_THUMBNAIL, 0, 0, tab),
846                             500);
847                 }
848             }
849         }
850         // pause the WebView timer and release the wake lock if it is finished
851         // while BrowserActivity is in pause state.
852         if (mActivityPaused && pauseWebViewTimers(tab)) {
853             releaseWakeLock();
854         }
855 
856         // Performance probe
857         if (false) {
858             Performance.onPageFinished(tab.getUrl());
859          }
860 
861         Performance.tracePageFinished();
862     }
863 
864     @Override
onProgressChanged(Tab tab)865     public void onProgressChanged(Tab tab) {
866         mCrashRecoveryHandler.backupState();
867         int newProgress = tab.getLoadProgress();
868 
869         if (newProgress == 100) {
870             CookieSyncManager.getInstance().sync();
871             // onProgressChanged() may continue to be called after the main
872             // frame has finished loading, as any remaining sub frames continue
873             // to load. We'll only get called once though with newProgress as
874             // 100 when everything is loaded. (onPageFinished is called once
875             // when the main frame completes loading regardless of the state of
876             // any sub frames so calls to onProgressChanges may continue after
877             // onPageFinished has executed)
878             if (mInLoad) {
879                 mInLoad = false;
880                 updateInLoadMenuItems(mCachedMenu);
881             }
882         } else {
883             if (!mInLoad) {
884                 // onPageFinished may have already been called but a subframe is
885                 // still loading and updating the progress. Reset mInLoad and
886                 // update the menu items.
887                 mInLoad = true;
888                 updateInLoadMenuItems(mCachedMenu);
889             }
890         }
891         mUi.onProgressChanged(tab);
892     }
893 
894     @Override
onUpdatedSecurityState(Tab tab)895     public void onUpdatedSecurityState(Tab tab) {
896         mUi.onTabDataChanged(tab);
897     }
898 
899     @Override
onReceivedTitle(Tab tab, final String title)900     public void onReceivedTitle(Tab tab, final String title) {
901         mUi.onTabDataChanged(tab);
902         final String pageUrl = tab.getOriginalUrl();
903         if (TextUtils.isEmpty(pageUrl) || pageUrl.length()
904                 >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
905             return;
906         }
907         // Update the title in the history database if not in private browsing mode
908         if (!tab.isPrivateBrowsingEnabled()) {
909             DataController.getInstance(mActivity).updateHistoryTitle(pageUrl, title);
910         }
911     }
912 
913     @Override
onFavicon(Tab tab, WebView view, Bitmap icon)914     public void onFavicon(Tab tab, WebView view, Bitmap icon) {
915         mUi.onTabDataChanged(tab);
916         maybeUpdateFavicon(tab, view.getOriginalUrl(), view.getUrl(), icon);
917     }
918 
919     @Override
shouldOverrideUrlLoading(Tab tab, WebView view, String url)920     public boolean shouldOverrideUrlLoading(Tab tab, WebView view, String url) {
921         return mUrlHandler.shouldOverrideUrlLoading(tab, view, url);
922     }
923 
924     @Override
shouldOverrideKeyEvent(KeyEvent event)925     public boolean shouldOverrideKeyEvent(KeyEvent event) {
926         if (mMenuIsDown) {
927             // only check shortcut key when MENU is held
928             return mActivity.getWindow().isShortcutKey(event.getKeyCode(),
929                     event);
930         } else {
931             return false;
932         }
933     }
934 
935     @Override
onUnhandledKeyEvent(KeyEvent event)936     public void onUnhandledKeyEvent(KeyEvent event) {
937         if (!isActivityPaused()) {
938             if (event.getAction() == KeyEvent.ACTION_DOWN) {
939                 mActivity.onKeyDown(event.getKeyCode(), event);
940             } else {
941                 mActivity.onKeyUp(event.getKeyCode(), event);
942             }
943         }
944     }
945 
946     @Override
doUpdateVisitedHistory(Tab tab, boolean isReload)947     public void doUpdateVisitedHistory(Tab tab, boolean isReload) {
948         // Don't save anything in private browsing mode
949         if (tab.isPrivateBrowsingEnabled()) return;
950         String url = tab.getOriginalUrl();
951 
952         if (TextUtils.isEmpty(url)
953                 || url.regionMatches(true, 0, "about:", 0, 6)) {
954             return;
955         }
956         DataController.getInstance(mActivity).updateVisitedHistory(url);
957         mCrashRecoveryHandler.backupState();
958     }
959 
960     @Override
getVisitedHistory(final ValueCallback<String[]> callback)961     public void getVisitedHistory(final ValueCallback<String[]> callback) {
962         AsyncTask<Void, Void, String[]> task =
963                 new AsyncTask<Void, Void, String[]>() {
964             @Override
965             public String[] doInBackground(Void... unused) {
966                 return Browser.getVisitedHistory(mActivity.getContentResolver());
967             }
968             @Override
969             public void onPostExecute(String[] result) {
970                 callback.onReceiveValue(result);
971             }
972         };
973         task.execute();
974     }
975 
976     @Override
onReceivedHttpAuthRequest(Tab tab, WebView view, final HttpAuthHandler handler, final String host, final String realm)977     public void onReceivedHttpAuthRequest(Tab tab, WebView view,
978             final HttpAuthHandler handler, final String host,
979             final String realm) {
980         String username = null;
981         String password = null;
982 
983         boolean reuseHttpAuthUsernamePassword
984                 = handler.useHttpAuthUsernamePassword();
985 
986         if (reuseHttpAuthUsernamePassword && view != null) {
987             String[] credentials = view.getHttpAuthUsernamePassword(host, realm);
988             if (credentials != null && credentials.length == 2) {
989                 username = credentials[0];
990                 password = credentials[1];
991             }
992         }
993 
994         if (username != null && password != null) {
995             handler.proceed(username, password);
996         } else {
997             if (tab.inForeground() && !handler.suppressDialog()) {
998                 mPageDialogsHandler.showHttpAuthentication(tab, handler, host, realm);
999             } else {
1000                 handler.cancel();
1001             }
1002         }
1003     }
1004 
1005     @Override
onDownloadStart(Tab tab, String url, String userAgent, String contentDisposition, String mimetype, long contentLength)1006     public void onDownloadStart(Tab tab, String url, String userAgent,
1007             String contentDisposition, String mimetype, long contentLength) {
1008         WebView w = tab.getWebView();
1009         DownloadHandler.onDownloadStart(mActivity, url, userAgent,
1010                 contentDisposition, mimetype, w.isPrivateBrowsingEnabled());
1011         if (w.copyBackForwardList().getSize() == 0) {
1012             // This Tab was opened for the sole purpose of downloading a
1013             // file. Remove it.
1014             if (tab == mTabControl.getCurrentTab()) {
1015                 // In this case, the Tab is still on top.
1016                 goBackOnePageOrQuit();
1017             } else {
1018                 // In this case, it is not.
1019                 closeTab(tab);
1020             }
1021         }
1022     }
1023 
1024     @Override
getDefaultVideoPoster()1025     public Bitmap getDefaultVideoPoster() {
1026         return mUi.getDefaultVideoPoster();
1027     }
1028 
1029     @Override
getVideoLoadingProgressView()1030     public View getVideoLoadingProgressView() {
1031         return mUi.getVideoLoadingProgressView();
1032     }
1033 
1034     @Override
showSslCertificateOnError(WebView view, SslErrorHandler handler, SslError error)1035     public void showSslCertificateOnError(WebView view, SslErrorHandler handler,
1036             SslError error) {
1037         mPageDialogsHandler.showSSLCertificateOnError(view, handler, error);
1038     }
1039 
1040     @Override
showAutoLogin(Tab tab)1041     public void showAutoLogin(Tab tab) {
1042         assert tab.inForeground();
1043         // Update the title bar to show the auto-login request.
1044         mUi.showAutoLogin(tab);
1045     }
1046 
1047     @Override
hideAutoLogin(Tab tab)1048     public void hideAutoLogin(Tab tab) {
1049         assert tab.inForeground();
1050         mUi.hideAutoLogin(tab);
1051     }
1052 
1053     // helper method
1054 
1055     /*
1056      * Update the favorites icon if the private browsing isn't enabled and the
1057      * icon is valid.
1058      */
maybeUpdateFavicon(Tab tab, final String originalUrl, final String url, Bitmap favicon)1059     private void maybeUpdateFavicon(Tab tab, final String originalUrl,
1060             final String url, Bitmap favicon) {
1061         if (favicon == null) {
1062             return;
1063         }
1064         if (!tab.isPrivateBrowsingEnabled()) {
1065             Bookmarks.updateFavicon(mActivity
1066                     .getContentResolver(), originalUrl, url, favicon);
1067         }
1068     }
1069 
1070     @Override
bookmarkedStatusHasChanged(Tab tab)1071     public void bookmarkedStatusHasChanged(Tab tab) {
1072         // TODO: Switch to using onTabDataChanged after b/3262950 is fixed
1073         mUi.bookmarkedStatusHasChanged(tab);
1074     }
1075 
1076     // end WebViewController
1077 
pageUp()1078     protected void pageUp() {
1079         getCurrentTopWebView().pageUp(false);
1080     }
1081 
pageDown()1082     protected void pageDown() {
1083         getCurrentTopWebView().pageDown(false);
1084     }
1085 
1086     // callback from phone title bar
editUrl()1087     public void editUrl() {
1088         if (mOptionsMenuOpen) mActivity.closeOptionsMenu();
1089         mUi.editUrl(false);
1090     }
1091 
startVoiceSearch()1092     public void startVoiceSearch() {
1093         Intent intent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
1094         intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
1095                 RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
1096         intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE,
1097                 mActivity.getComponentName().flattenToString());
1098         intent.putExtra(SEND_APP_ID_EXTRA, false);
1099         intent.putExtra(RecognizerIntent.EXTRA_WEB_SEARCH_ONLY, true);
1100         mActivity.startActivity(intent);
1101     }
1102 
1103     @Override
activateVoiceSearchMode(String title, List<String> results)1104     public void activateVoiceSearchMode(String title, List<String> results) {
1105         mUi.showVoiceTitleBar(title, results);
1106     }
1107 
revertVoiceSearchMode(Tab tab)1108     public void revertVoiceSearchMode(Tab tab) {
1109         mUi.revertVoiceTitleBar(tab);
1110     }
1111 
supportsVoiceSearch()1112     public boolean supportsVoiceSearch() {
1113         SearchEngine searchEngine = getSettings().getSearchEngine();
1114         return (searchEngine != null && searchEngine.supportsVoiceSearch());
1115     }
1116 
showCustomView(Tab tab, View view, int requestedOrientation, WebChromeClient.CustomViewCallback callback)1117     public void showCustomView(Tab tab, View view, int requestedOrientation,
1118             WebChromeClient.CustomViewCallback callback) {
1119         if (tab.inForeground()) {
1120             if (mUi.isCustomViewShowing()) {
1121                 callback.onCustomViewHidden();
1122                 return;
1123             }
1124             mUi.showCustomView(view, requestedOrientation, callback);
1125             // Save the menu state and set it to empty while the custom
1126             // view is showing.
1127             mOldMenuState = mMenuState;
1128             mMenuState = EMPTY_MENU;
1129             mActivity.invalidateOptionsMenu();
1130         }
1131     }
1132 
1133     @Override
hideCustomView()1134     public void hideCustomView() {
1135         if (mUi.isCustomViewShowing()) {
1136             mUi.onHideCustomView();
1137             // Reset the old menu state.
1138             mMenuState = mOldMenuState;
1139             mOldMenuState = EMPTY_MENU;
1140             mActivity.invalidateOptionsMenu();
1141         }
1142     }
1143 
onActivityResult(int requestCode, int resultCode, Intent intent)1144     protected void onActivityResult(int requestCode, int resultCode,
1145             Intent intent) {
1146         if (getCurrentTopWebView() == null) return;
1147         switch (requestCode) {
1148             case PREFERENCES_PAGE:
1149                 if (resultCode == Activity.RESULT_OK && intent != null) {
1150                     String action = intent.getStringExtra(Intent.EXTRA_TEXT);
1151                     if (PreferenceKeys.PREF_PRIVACY_CLEAR_HISTORY.equals(action)) {
1152                         mTabControl.removeParentChildRelationShips();
1153                     }
1154                 }
1155                 break;
1156             case FILE_SELECTED:
1157                 // Chose a file from the file picker.
1158                 if (null == mUploadHandler) break;
1159                 mUploadHandler.onResult(resultCode, intent);
1160                 break;
1161             case AUTOFILL_SETUP:
1162                 // Determine whether a profile was actually set up or not
1163                 // and if so, send the message back to the WebTextView to
1164                 // fill the form with the new profile.
1165                 if (getSettings().getAutoFillProfile() != null) {
1166                     mAutoFillSetupMessage.sendToTarget();
1167                     mAutoFillSetupMessage = null;
1168                 }
1169                 break;
1170             case COMBO_VIEW:
1171                 if (intent == null || resultCode != Activity.RESULT_OK) {
1172                     break;
1173                 }
1174                 mUi.showWeb(false);
1175                 if (Intent.ACTION_VIEW.equals(intent.getAction())) {
1176                     Tab t = getCurrentTab();
1177                     Uri uri = intent.getData();
1178                     loadUrl(t, uri.toString());
1179                 } else if (intent.hasExtra(ComboViewActivity.EXTRA_OPEN_ALL)) {
1180                     String[] urls = intent.getStringArrayExtra(
1181                             ComboViewActivity.EXTRA_OPEN_ALL);
1182                     Tab parent = getCurrentTab();
1183                     for (String url : urls) {
1184                         parent = openTab(url, parent,
1185                                 !mSettings.openInBackground(), true);
1186                     }
1187                 } else if (intent.hasExtra(ComboViewActivity.EXTRA_OPEN_SNAPSHOT)) {
1188                     long id = intent.getLongExtra(
1189                             ComboViewActivity.EXTRA_OPEN_SNAPSHOT, -1);
1190                     if (id >= 0) {
1191                         createNewSnapshotTab(id, true);
1192                     }
1193                 }
1194                 break;
1195             default:
1196                 break;
1197         }
1198         getCurrentTopWebView().requestFocus();
1199     }
1200 
1201     /**
1202      * Open the Go page.
1203      * @param startWithHistory If true, open starting on the history tab.
1204      *                         Otherwise, start with the bookmarks tab.
1205      */
1206     @Override
bookmarksOrHistoryPicker(ComboViews startView)1207     public void bookmarksOrHistoryPicker(ComboViews startView) {
1208         if (mTabControl.getCurrentWebView() == null) {
1209             return;
1210         }
1211         // clear action mode
1212         if (isInCustomActionMode()) {
1213             endActionMode();
1214         }
1215         Bundle extras = new Bundle();
1216         // Disable opening in a new window if we have maxed out the windows
1217         extras.putBoolean(BrowserBookmarksPage.EXTRA_DISABLE_WINDOW,
1218                 !mTabControl.canCreateNewTab());
1219         mUi.showComboView(startView, extras);
1220     }
1221 
1222     // combo view callbacks
1223 
1224     // key handling
onBackKey()1225     protected void onBackKey() {
1226         if (!mUi.onBackKey()) {
1227             WebView subwindow = mTabControl.getCurrentSubWindow();
1228             if (subwindow != null) {
1229                 if (subwindow.canGoBack()) {
1230                     subwindow.goBack();
1231                 } else {
1232                     dismissSubWindow(mTabControl.getCurrentTab());
1233                 }
1234             } else {
1235                 goBackOnePageOrQuit();
1236             }
1237         }
1238     }
1239 
onMenuKey()1240     protected boolean onMenuKey() {
1241         return mUi.onMenuKey();
1242     }
1243 
1244     // menu handling and state
1245     // TODO: maybe put into separate handler
1246 
onCreateOptionsMenu(Menu menu)1247     protected boolean onCreateOptionsMenu(Menu menu) {
1248         if (mMenuState == EMPTY_MENU) {
1249             return false;
1250         }
1251         MenuInflater inflater = mActivity.getMenuInflater();
1252         inflater.inflate(R.menu.browser, menu);
1253         return true;
1254     }
1255 
onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)1256     protected void onCreateContextMenu(ContextMenu menu, View v,
1257             ContextMenuInfo menuInfo) {
1258         if (v instanceof TitleBar) {
1259             return;
1260         }
1261         if (!(v instanceof WebView)) {
1262             return;
1263         }
1264         final WebView webview = (WebView) v;
1265         WebView.HitTestResult result = webview.getHitTestResult();
1266         if (result == null) {
1267             return;
1268         }
1269 
1270         int type = result.getType();
1271         if (type == WebView.HitTestResult.UNKNOWN_TYPE) {
1272             Log.w(LOGTAG,
1273                     "We should not show context menu when nothing is touched");
1274             return;
1275         }
1276         if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) {
1277             // let TextView handles context menu
1278             return;
1279         }
1280 
1281         // Note, http://b/issue?id=1106666 is requesting that
1282         // an inflated menu can be used again. This is not available
1283         // yet, so inflate each time (yuk!)
1284         MenuInflater inflater = mActivity.getMenuInflater();
1285         inflater.inflate(R.menu.browsercontext, menu);
1286 
1287         // Show the correct menu group
1288         final String extra = result.getExtra();
1289         menu.setGroupVisible(R.id.PHONE_MENU,
1290                 type == WebView.HitTestResult.PHONE_TYPE);
1291         menu.setGroupVisible(R.id.EMAIL_MENU,
1292                 type == WebView.HitTestResult.EMAIL_TYPE);
1293         menu.setGroupVisible(R.id.GEO_MENU,
1294                 type == WebView.HitTestResult.GEO_TYPE);
1295         menu.setGroupVisible(R.id.IMAGE_MENU,
1296                 type == WebView.HitTestResult.IMAGE_TYPE
1297                 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1298         menu.setGroupVisible(R.id.ANCHOR_MENU,
1299                 type == WebView.HitTestResult.SRC_ANCHOR_TYPE
1300                 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1301         boolean hitText = type == WebView.HitTestResult.SRC_ANCHOR_TYPE
1302                 || type == WebView.HitTestResult.PHONE_TYPE
1303                 || type == WebView.HitTestResult.EMAIL_TYPE
1304                 || type == WebView.HitTestResult.GEO_TYPE;
1305         menu.setGroupVisible(R.id.SELECT_TEXT_MENU, hitText);
1306         if (hitText) {
1307             menu.findItem(R.id.select_text_menu_id)
1308                     .setOnMenuItemClickListener(new SelectText(webview));
1309         }
1310         // Setup custom handling depending on the type
1311         switch (type) {
1312             case WebView.HitTestResult.PHONE_TYPE:
1313                 menu.setHeaderTitle(Uri.decode(extra));
1314                 menu.findItem(R.id.dial_context_menu_id).setIntent(
1315                         new Intent(Intent.ACTION_VIEW, Uri
1316                                 .parse(WebView.SCHEME_TEL + extra)));
1317                 Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
1318                 addIntent.putExtra(Insert.PHONE, Uri.decode(extra));
1319                 addIntent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
1320                 menu.findItem(R.id.add_contact_context_menu_id).setIntent(
1321                         addIntent);
1322                 menu.findItem(R.id.copy_phone_context_menu_id)
1323                         .setOnMenuItemClickListener(
1324                         new Copy(extra));
1325                 break;
1326 
1327             case WebView.HitTestResult.EMAIL_TYPE:
1328                 menu.setHeaderTitle(extra);
1329                 menu.findItem(R.id.email_context_menu_id).setIntent(
1330                         new Intent(Intent.ACTION_VIEW, Uri
1331                                 .parse(WebView.SCHEME_MAILTO + extra)));
1332                 menu.findItem(R.id.copy_mail_context_menu_id)
1333                         .setOnMenuItemClickListener(
1334                         new Copy(extra));
1335                 break;
1336 
1337             case WebView.HitTestResult.GEO_TYPE:
1338                 menu.setHeaderTitle(extra);
1339                 menu.findItem(R.id.map_context_menu_id).setIntent(
1340                         new Intent(Intent.ACTION_VIEW, Uri
1341                                 .parse(WebView.SCHEME_GEO
1342                                         + URLEncoder.encode(extra))));
1343                 menu.findItem(R.id.copy_geo_context_menu_id)
1344                         .setOnMenuItemClickListener(
1345                         new Copy(extra));
1346                 break;
1347 
1348             case WebView.HitTestResult.SRC_ANCHOR_TYPE:
1349             case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
1350                 menu.setHeaderTitle(extra);
1351                 // decide whether to show the open link in new tab option
1352                 boolean showNewTab = mTabControl.canCreateNewTab();
1353                 MenuItem newTabItem
1354                         = menu.findItem(R.id.open_newtab_context_menu_id);
1355                 newTabItem.setTitle(getSettings().openInBackground()
1356                         ? R.string.contextmenu_openlink_newwindow_background
1357                         : R.string.contextmenu_openlink_newwindow);
1358                 newTabItem.setVisible(showNewTab);
1359                 if (showNewTab) {
1360                     if (WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE == type) {
1361                         newTabItem.setOnMenuItemClickListener(
1362                                 new MenuItem.OnMenuItemClickListener() {
1363                                     @Override
1364                                     public boolean onMenuItemClick(MenuItem item) {
1365                                         final HashMap<String, WebView> hrefMap =
1366                                                 new HashMap<String, WebView>();
1367                                         hrefMap.put("webview", webview);
1368                                         final Message msg = mHandler.obtainMessage(
1369                                                 FOCUS_NODE_HREF,
1370                                                 R.id.open_newtab_context_menu_id,
1371                                                 0, hrefMap);
1372                                         webview.requestFocusNodeHref(msg);
1373                                         return true;
1374                                     }
1375                                 });
1376                     } else {
1377                         newTabItem.setOnMenuItemClickListener(
1378                                 new MenuItem.OnMenuItemClickListener() {
1379                                     @Override
1380                                     public boolean onMenuItemClick(MenuItem item) {
1381                                         final Tab parent = mTabControl.getCurrentTab();
1382                                         openTab(extra, parent,
1383                                                 !mSettings.openInBackground(),
1384                                                 true);
1385                                         return true;
1386                                     }
1387                                 });
1388                     }
1389                 }
1390                 if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
1391                     break;
1392                 }
1393                 // otherwise fall through to handle image part
1394             case WebView.HitTestResult.IMAGE_TYPE:
1395                 if (type == WebView.HitTestResult.IMAGE_TYPE) {
1396                     menu.setHeaderTitle(extra);
1397                 }
1398                 menu.findItem(R.id.view_image_context_menu_id)
1399                         .setOnMenuItemClickListener(new OnMenuItemClickListener() {
1400                     @Override
1401                     public boolean onMenuItemClick(MenuItem item) {
1402                         openTab(extra, mTabControl.getCurrentTab(), true, true);
1403                         return false;
1404                     }
1405                 });
1406                 menu.findItem(R.id.download_context_menu_id).
1407                         setOnMenuItemClickListener(
1408                                 new Download(mActivity, extra, webview.isPrivateBrowsingEnabled()));
1409                 menu.findItem(R.id.set_wallpaper_context_menu_id).
1410                         setOnMenuItemClickListener(new WallpaperHandler(mActivity,
1411                                 extra));
1412                 break;
1413 
1414             default:
1415                 Log.w(LOGTAG, "We should not get here.");
1416                 break;
1417         }
1418         //update the ui
1419         mUi.onContextMenuCreated(menu);
1420     }
1421 
1422     /**
1423      * As the menu can be open when loading state changes
1424      * we must manually update the state of the stop/reload menu
1425      * item
1426      */
updateInLoadMenuItems(Menu menu)1427     private void updateInLoadMenuItems(Menu menu) {
1428         if (menu == null) {
1429             return;
1430         }
1431         MenuItem dest = menu.findItem(R.id.stop_reload_menu_id);
1432         MenuItem src = mInLoad ?
1433                 menu.findItem(R.id.stop_menu_id):
1434                 menu.findItem(R.id.reload_menu_id);
1435         if (src != null) {
1436             dest.setIcon(src.getIcon());
1437             dest.setTitle(src.getTitle());
1438         }
1439     }
1440 
onPrepareOptionsMenu(Menu menu)1441     boolean onPrepareOptionsMenu(Menu menu) {
1442         updateInLoadMenuItems(menu);
1443         // hold on to the menu reference here; it is used by the page callbacks
1444         // to update the menu based on loading state
1445         mCachedMenu = menu;
1446         // Note: setVisible will decide whether an item is visible; while
1447         // setEnabled() will decide whether an item is enabled, which also means
1448         // whether the matching shortcut key will function.
1449         switch (mMenuState) {
1450             case EMPTY_MENU:
1451                 if (mCurrentMenuState != mMenuState) {
1452                     menu.setGroupVisible(R.id.MAIN_MENU, false);
1453                     menu.setGroupEnabled(R.id.MAIN_MENU, false);
1454                     menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false);
1455                 }
1456                 break;
1457             default:
1458                 if (mCurrentMenuState != mMenuState) {
1459                     menu.setGroupVisible(R.id.MAIN_MENU, true);
1460                     menu.setGroupEnabled(R.id.MAIN_MENU, true);
1461                     menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true);
1462                 }
1463                 updateMenuState(getCurrentTab(), menu);
1464                 break;
1465         }
1466         mCurrentMenuState = mMenuState;
1467         return mUi.onPrepareOptionsMenu(menu);
1468     }
1469 
1470     @Override
updateMenuState(Tab tab, Menu menu)1471     public void updateMenuState(Tab tab, Menu menu) {
1472         boolean canGoBack = false;
1473         boolean canGoForward = false;
1474         boolean isHome = false;
1475         boolean isDesktopUa = false;
1476         boolean isLive = false;
1477         if (tab != null) {
1478             canGoBack = tab.canGoBack();
1479             canGoForward = tab.canGoForward();
1480             isHome = mSettings.getHomePage().equals(tab.getUrl());
1481             isDesktopUa = mSettings.hasDesktopUseragent(tab.getWebView());
1482             isLive = !tab.isSnapshot();
1483         }
1484         final MenuItem back = menu.findItem(R.id.back_menu_id);
1485         back.setEnabled(canGoBack);
1486 
1487         final MenuItem home = menu.findItem(R.id.homepage_menu_id);
1488         home.setEnabled(!isHome);
1489 
1490         final MenuItem forward = menu.findItem(R.id.forward_menu_id);
1491         forward.setEnabled(canGoForward);
1492 
1493         final MenuItem source = menu.findItem(mInLoad ? R.id.stop_menu_id : R.id.reload_menu_id);
1494         final MenuItem dest = menu.findItem(R.id.stop_reload_menu_id);
1495         if (source != null && dest != null) {
1496             dest.setTitle(source.getTitle());
1497             dest.setIcon(source.getIcon());
1498         }
1499         menu.setGroupVisible(R.id.NAV_MENU, isLive);
1500 
1501         // decide whether to show the share link option
1502         PackageManager pm = mActivity.getPackageManager();
1503         Intent send = new Intent(Intent.ACTION_SEND);
1504         send.setType("text/plain");
1505         ResolveInfo ri = pm.resolveActivity(send,
1506                 PackageManager.MATCH_DEFAULT_ONLY);
1507         menu.findItem(R.id.share_page_menu_id).setVisible(ri != null);
1508 
1509         boolean isNavDump = mSettings.enableNavDump();
1510         final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id);
1511         nav.setVisible(isNavDump);
1512         nav.setEnabled(isNavDump);
1513 
1514         boolean showDebugSettings = mSettings.isDebugEnabled();
1515         final MenuItem counter = menu.findItem(R.id.dump_counters_menu_id);
1516         counter.setVisible(showDebugSettings);
1517         counter.setEnabled(showDebugSettings);
1518         final MenuItem uaSwitcher = menu.findItem(R.id.ua_desktop_menu_id);
1519         uaSwitcher.setChecked(isDesktopUa);
1520         menu.setGroupVisible(R.id.LIVE_MENU, isLive);
1521         menu.setGroupVisible(R.id.SNAPSHOT_MENU, !isLive);
1522         menu.setGroupVisible(R.id.COMBO_MENU, false);
1523 
1524         mUi.updateMenuState(tab, menu);
1525     }
1526 
onOptionsItemSelected(MenuItem item)1527     public boolean onOptionsItemSelected(MenuItem item) {
1528         if (null == getCurrentTopWebView()) {
1529             return false;
1530         }
1531         if (mMenuIsDown) {
1532             // The shortcut action consumes the MENU. Even if it is still down,
1533             // it won't trigger the next shortcut action. In the case of the
1534             // shortcut action triggering a new activity, like Bookmarks, we
1535             // won't get onKeyUp for MENU. So it is important to reset it here.
1536             mMenuIsDown = false;
1537         }
1538         if (mUi.onOptionsItemSelected(item)) {
1539             // ui callback handled it
1540             return true;
1541         }
1542         switch (item.getItemId()) {
1543             // -- Main menu
1544             case R.id.new_tab_menu_id:
1545                 openTabToHomePage();
1546                 break;
1547 
1548             case R.id.incognito_menu_id:
1549                 openIncognitoTab();
1550                 break;
1551 
1552             case R.id.goto_menu_id:
1553                 editUrl();
1554                 break;
1555 
1556             case R.id.bookmarks_menu_id:
1557                 bookmarksOrHistoryPicker(ComboViews.Bookmarks);
1558                 break;
1559 
1560             case R.id.history_menu_id:
1561                 bookmarksOrHistoryPicker(ComboViews.History);
1562                 break;
1563 
1564             case R.id.snapshots_menu_id:
1565                 bookmarksOrHistoryPicker(ComboViews.Snapshots);
1566                 break;
1567 
1568             case R.id.add_bookmark_menu_id:
1569                 Intent bookmarkIntent = createBookmarkCurrentPageIntent(false);
1570                 if (bookmarkIntent != null) {
1571                     mActivity.startActivity(bookmarkIntent);
1572                 }
1573                 break;
1574 
1575             case R.id.stop_reload_menu_id:
1576                 if (mInLoad) {
1577                     stopLoading();
1578                 } else {
1579                     getCurrentTopWebView().reload();
1580                 }
1581                 break;
1582 
1583             case R.id.back_menu_id:
1584                 getCurrentTab().goBack();
1585                 break;
1586 
1587             case R.id.forward_menu_id:
1588                 getCurrentTab().goForward();
1589                 break;
1590 
1591             case R.id.close_menu_id:
1592                 // Close the subwindow if it exists.
1593                 if (mTabControl.getCurrentSubWindow() != null) {
1594                     dismissSubWindow(mTabControl.getCurrentTab());
1595                     break;
1596                 }
1597                 closeCurrentTab();
1598                 break;
1599 
1600             case R.id.homepage_menu_id:
1601                 Tab current = mTabControl.getCurrentTab();
1602                 loadUrl(current, mSettings.getHomePage());
1603                 break;
1604 
1605             case R.id.preferences_menu_id:
1606                 Intent intent = new Intent(mActivity, BrowserPreferencesPage.class);
1607                 intent.putExtra(BrowserPreferencesPage.CURRENT_PAGE,
1608                         getCurrentTopWebView().getUrl());
1609                 mActivity.startActivityForResult(intent, PREFERENCES_PAGE);
1610                 break;
1611 
1612             case R.id.find_menu_id:
1613                 getCurrentTopWebView().showFindDialog(null, true);
1614                 break;
1615 
1616             case R.id.save_snapshot_menu_id:
1617                 final Tab source = getTabControl().getCurrentTab();
1618                 if (source == null) break;
1619                 final ContentResolver cr = mActivity.getContentResolver();
1620                 final ContentValues values = source.createSnapshotValues();
1621                 if (values != null) {
1622                     new AsyncTask<Tab, Void, Long>() {
1623 
1624                         @Override
1625                         protected Long doInBackground(Tab... params) {
1626                             Uri result = cr.insert(Snapshots.CONTENT_URI, values);
1627                             long id = ContentUris.parseId(result);
1628                             return id;
1629                         }
1630 
1631                         @Override
1632                         protected void onPostExecute(Long id) {
1633                             Bundle b = new Bundle();
1634                             b.putLong(BrowserSnapshotPage.EXTRA_ANIMATE_ID, id);
1635                             mUi.showComboView(ComboViews.Snapshots, b);
1636                         };
1637                     }.execute(source);
1638                 } else {
1639                     Toast.makeText(mActivity, R.string.snapshot_failed,
1640                             Toast.LENGTH_SHORT).show();
1641                 }
1642                 break;
1643 
1644             case R.id.page_info_menu_id:
1645                 showPageInfo();
1646                 break;
1647 
1648             case R.id.snapshot_go_live:
1649                 goLive();
1650                 return true;
1651 
1652             case R.id.share_page_menu_id:
1653                 Tab currentTab = mTabControl.getCurrentTab();
1654                 if (null == currentTab) {
1655                     return false;
1656                 }
1657                 shareCurrentPage(currentTab);
1658                 break;
1659 
1660             case R.id.dump_nav_menu_id:
1661                 getCurrentTopWebView().debugDump();
1662                 break;
1663 
1664             case R.id.dump_counters_menu_id:
1665                 getCurrentTopWebView().dumpV8Counters();
1666                 break;
1667 
1668             case R.id.zoom_in_menu_id:
1669                 getCurrentTopWebView().zoomIn();
1670                 break;
1671 
1672             case R.id.zoom_out_menu_id:
1673                 getCurrentTopWebView().zoomOut();
1674                 break;
1675 
1676             case R.id.view_downloads_menu_id:
1677                 viewDownloads();
1678                 break;
1679 
1680             case R.id.ua_desktop_menu_id:
1681                 WebView web = getCurrentWebView();
1682                 mSettings.toggleDesktopUseragent(web);
1683                 web.loadUrl(web.getOriginalUrl());
1684                 break;
1685 
1686             case R.id.window_one_menu_id:
1687             case R.id.window_two_menu_id:
1688             case R.id.window_three_menu_id:
1689             case R.id.window_four_menu_id:
1690             case R.id.window_five_menu_id:
1691             case R.id.window_six_menu_id:
1692             case R.id.window_seven_menu_id:
1693             case R.id.window_eight_menu_id:
1694                 {
1695                     int menuid = item.getItemId();
1696                     for (int id = 0; id < WINDOW_SHORTCUT_ID_ARRAY.length; id++) {
1697                         if (WINDOW_SHORTCUT_ID_ARRAY[id] == menuid) {
1698                             Tab desiredTab = mTabControl.getTab(id);
1699                             if (desiredTab != null &&
1700                                     desiredTab != mTabControl.getCurrentTab()) {
1701                                 switchToTab(desiredTab);
1702                             }
1703                             break;
1704                         }
1705                     }
1706                 }
1707                 break;
1708 
1709             default:
1710                 return false;
1711         }
1712         return true;
1713     }
1714 
goLive()1715     private void goLive() {
1716         Tab t = getCurrentTab();
1717         t.loadUrl(t.getUrl(), null);
1718     }
1719 
1720     @Override
showPageInfo()1721     public void showPageInfo() {
1722         mPageDialogsHandler.showPageInfo(mTabControl.getCurrentTab(), false, null);
1723     }
1724 
onContextItemSelected(MenuItem item)1725     public boolean onContextItemSelected(MenuItem item) {
1726         // Let the History and Bookmark fragments handle menus they created.
1727         if (item.getGroupId() == R.id.CONTEXT_MENU) {
1728             return false;
1729         }
1730 
1731         int id = item.getItemId();
1732         boolean result = true;
1733         switch (id) {
1734             // -- Browser context menu
1735             case R.id.open_context_menu_id:
1736             case R.id.save_link_context_menu_id:
1737             case R.id.copy_link_context_menu_id:
1738                 final WebView webView = getCurrentTopWebView();
1739                 if (null == webView) {
1740                     result = false;
1741                     break;
1742                 }
1743                 final HashMap<String, WebView> hrefMap =
1744                         new HashMap<String, WebView>();
1745                 hrefMap.put("webview", webView);
1746                 final Message msg = mHandler.obtainMessage(
1747                         FOCUS_NODE_HREF, id, 0, hrefMap);
1748                 webView.requestFocusNodeHref(msg);
1749                 break;
1750 
1751             default:
1752                 // For other context menus
1753                 result = onOptionsItemSelected(item);
1754         }
1755         return result;
1756     }
1757 
1758     /**
1759      * support programmatically opening the context menu
1760      */
openContextMenu(View view)1761     public void openContextMenu(View view) {
1762         mActivity.openContextMenu(view);
1763     }
1764 
1765     /**
1766      * programmatically open the options menu
1767      */
openOptionsMenu()1768     public void openOptionsMenu() {
1769         mActivity.openOptionsMenu();
1770     }
1771 
onMenuOpened(int featureId, Menu menu)1772     public boolean onMenuOpened(int featureId, Menu menu) {
1773         if (mOptionsMenuOpen) {
1774             if (mConfigChanged) {
1775                 // We do not need to make any changes to the state of the
1776                 // title bar, since the only thing that happened was a
1777                 // change in orientation
1778                 mConfigChanged = false;
1779             } else {
1780                 if (!mExtendedMenuOpen) {
1781                     mExtendedMenuOpen = true;
1782                     mUi.onExtendedMenuOpened();
1783                 } else {
1784                     // Switching the menu back to icon view, so show the
1785                     // title bar once again.
1786                     mExtendedMenuOpen = false;
1787                     mUi.onExtendedMenuClosed(mInLoad);
1788                 }
1789             }
1790         } else {
1791             // The options menu is closed, so open it, and show the title
1792             mOptionsMenuOpen = true;
1793             mConfigChanged = false;
1794             mExtendedMenuOpen = false;
1795             mUi.onOptionsMenuOpened();
1796         }
1797         return true;
1798     }
1799 
onOptionsMenuClosed(Menu menu)1800     public void onOptionsMenuClosed(Menu menu) {
1801         mOptionsMenuOpen = false;
1802         mUi.onOptionsMenuClosed(mInLoad);
1803     }
1804 
onContextMenuClosed(Menu menu)1805     public void onContextMenuClosed(Menu menu) {
1806         mUi.onContextMenuClosed(menu, mInLoad);
1807     }
1808 
1809     // Helper method for getting the top window.
1810     @Override
getCurrentTopWebView()1811     public WebView getCurrentTopWebView() {
1812         return mTabControl.getCurrentTopWebView();
1813     }
1814 
1815     @Override
getCurrentWebView()1816     public WebView getCurrentWebView() {
1817         return mTabControl.getCurrentWebView();
1818     }
1819 
1820     /*
1821      * This method is called as a result of the user selecting the options
1822      * menu to see the download window. It shows the download window on top of
1823      * the current window.
1824      */
viewDownloads()1825     void viewDownloads() {
1826         Intent intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
1827         mActivity.startActivity(intent);
1828     }
1829 
getActionModeHeight()1830     int getActionModeHeight() {
1831         TypedArray actionBarSizeTypedArray = mActivity.obtainStyledAttributes(
1832                     new int[] { android.R.attr.actionBarSize });
1833         int size = (int) actionBarSizeTypedArray.getDimension(0, 0f);
1834         actionBarSizeTypedArray.recycle();
1835         return size;
1836     }
1837 
1838     // action mode
1839 
onActionModeStarted(ActionMode mode)1840     void onActionModeStarted(ActionMode mode) {
1841         mUi.onActionModeStarted(mode);
1842         mActionMode = mode;
1843     }
1844 
1845     /*
1846      * True if a custom ActionMode (i.e. find or select) is in use.
1847      */
1848     @Override
isInCustomActionMode()1849     public boolean isInCustomActionMode() {
1850         return mActionMode != null;
1851     }
1852 
1853     /*
1854      * End the current ActionMode.
1855      */
1856     @Override
endActionMode()1857     public void endActionMode() {
1858         if (mActionMode != null) {
1859             mActionMode.finish();
1860         }
1861     }
1862 
1863     /*
1864      * Called by find and select when they are finished.  Replace title bars
1865      * as necessary.
1866      */
onActionModeFinished(ActionMode mode)1867     public void onActionModeFinished(ActionMode mode) {
1868         if (!isInCustomActionMode()) return;
1869         mUi.onActionModeFinished(mInLoad);
1870         mActionMode = null;
1871     }
1872 
isInLoad()1873     boolean isInLoad() {
1874         return mInLoad;
1875     }
1876 
1877     // bookmark handling
1878 
1879     /**
1880      * add the current page as a bookmark to the given folder id
1881      * @param folderId use -1 for the default folder
1882      * @param editExisting If true, check to see whether the site is already
1883      *          bookmarked, and if it is, edit that bookmark.  If false, and
1884      *          the site is already bookmarked, do not attempt to edit the
1885      *          existing bookmark.
1886      */
1887     @Override
createBookmarkCurrentPageIntent(boolean editExisting)1888     public Intent createBookmarkCurrentPageIntent(boolean editExisting) {
1889         WebView w = getCurrentTopWebView();
1890         if (w == null) {
1891             return null;
1892         }
1893         Intent i = new Intent(mActivity,
1894                 AddBookmarkPage.class);
1895         i.putExtra(BrowserContract.Bookmarks.URL, w.getUrl());
1896         i.putExtra(BrowserContract.Bookmarks.TITLE, w.getTitle());
1897         String touchIconUrl = w.getTouchIconUrl();
1898         if (touchIconUrl != null) {
1899             i.putExtra(AddBookmarkPage.TOUCH_ICON_URL, touchIconUrl);
1900             WebSettings settings = w.getSettings();
1901             if (settings != null) {
1902                 i.putExtra(AddBookmarkPage.USER_AGENT,
1903                         settings.getUserAgentString());
1904             }
1905         }
1906         i.putExtra(BrowserContract.Bookmarks.THUMBNAIL,
1907                 createScreenshot(w, getDesiredThumbnailWidth(mActivity),
1908                 getDesiredThumbnailHeight(mActivity)));
1909         i.putExtra(BrowserContract.Bookmarks.FAVICON, w.getFavicon());
1910         if (editExisting) {
1911             i.putExtra(AddBookmarkPage.CHECK_FOR_DUPE, true);
1912         }
1913         // Put the dialog at the upper right of the screen, covering the
1914         // star on the title bar.
1915         i.putExtra("gravity", Gravity.RIGHT | Gravity.TOP);
1916         return i;
1917     }
1918 
1919     // file chooser
openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType)1920     public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
1921         mUploadHandler = new UploadHandler(this);
1922         mUploadHandler.openFileChooser(uploadMsg, acceptType);
1923     }
1924 
1925     // thumbnails
1926 
1927     /**
1928      * Return the desired width for thumbnail screenshots, which are stored in
1929      * the database, and used on the bookmarks screen.
1930      * @param context Context for finding out the density of the screen.
1931      * @return desired width for thumbnail screenshot.
1932      */
getDesiredThumbnailWidth(Context context)1933     static int getDesiredThumbnailWidth(Context context) {
1934         return context.getResources().getDimensionPixelOffset(
1935                 R.dimen.bookmarkThumbnailWidth);
1936     }
1937 
1938     /**
1939      * Return the desired height for thumbnail screenshots, which are stored in
1940      * the database, and used on the bookmarks screen.
1941      * @param context Context for finding out the density of the screen.
1942      * @return desired height for thumbnail screenshot.
1943      */
getDesiredThumbnailHeight(Context context)1944     static int getDesiredThumbnailHeight(Context context) {
1945         return context.getResources().getDimensionPixelOffset(
1946                 R.dimen.bookmarkThumbnailHeight);
1947     }
1948 
createScreenshot(WebView view, int width, int height)1949     static Bitmap createScreenshot(WebView view, int width, int height) {
1950         if (view == null || view.getContentHeight() == 0
1951                 || view.getContentWidth() == 0) {
1952             return null;
1953         }
1954         // We render to a bitmap 2x the desired size so that we can then
1955         // re-scale it with filtering since canvas.scale doesn't filter
1956         // This helps reduce aliasing at the cost of being slightly blurry
1957         final int filter_scale = 2;
1958         int scaledWidth = width * filter_scale;
1959         int scaledHeight = height * filter_scale;
1960         if (sThumbnailBitmap == null || sThumbnailBitmap.getWidth() != scaledWidth
1961                 || sThumbnailBitmap.getHeight() != scaledHeight) {
1962             if (sThumbnailBitmap != null) {
1963                 sThumbnailBitmap.recycle();
1964                 sThumbnailBitmap = null;
1965             }
1966             sThumbnailBitmap =
1967                     Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.RGB_565);
1968         }
1969         Canvas canvas = new Canvas(sThumbnailBitmap);
1970         int contentWidth = view.getContentWidth();
1971         float overviewScale = scaledWidth / (view.getScale() * contentWidth);
1972         if (view instanceof BrowserWebView) {
1973             int dy = -((BrowserWebView)view).getTitleHeight();
1974             canvas.translate(0, dy * overviewScale);
1975         }
1976 
1977         canvas.scale(overviewScale, overviewScale);
1978 
1979         if (view instanceof BrowserWebView) {
1980             ((BrowserWebView)view).drawContent(canvas);
1981         } else {
1982             view.draw(canvas);
1983         }
1984         Bitmap ret = Bitmap.createScaledBitmap(sThumbnailBitmap,
1985                 width, height, true);
1986         canvas.setBitmap(null);
1987         return ret;
1988     }
1989 
updateScreenshot(Tab tab)1990     private void updateScreenshot(Tab tab) {
1991         // If this is a bookmarked site, add a screenshot to the database.
1992         // FIXME: Would like to make sure there is actually something to
1993         // draw, but the API for that (WebViewCore.pictureReady()) is not
1994         // currently accessible here.
1995 
1996         WebView view = tab.getWebView();
1997         if (view == null) {
1998             // Tab was destroyed
1999             return;
2000         }
2001         final String url = tab.getUrl();
2002         final String originalUrl = view.getOriginalUrl();
2003 
2004         if (TextUtils.isEmpty(url)) {
2005             return;
2006         }
2007 
2008         // Only update thumbnails for web urls (http(s)://), not for
2009         // about:, javascript:, data:, etc...
2010         // Unless it is a bookmarked site, then always update
2011         if (!Patterns.WEB_URL.matcher(url).matches() && !tab.isBookmarkedSite()) {
2012             return;
2013         }
2014 
2015         final Bitmap bm = createScreenshot(view, getDesiredThumbnailWidth(mActivity),
2016                 getDesiredThumbnailHeight(mActivity));
2017         if (bm == null) {
2018             return;
2019         }
2020 
2021         final ContentResolver cr = mActivity.getContentResolver();
2022         new AsyncTask<Void, Void, Void>() {
2023             @Override
2024             protected Void doInBackground(Void... unused) {
2025                 Cursor cursor = null;
2026                 try {
2027                     // TODO: Clean this up
2028                     cursor = Bookmarks.queryCombinedForUrl(cr, originalUrl, url);
2029                     if (cursor != null && cursor.moveToFirst()) {
2030                         final ByteArrayOutputStream os =
2031                                 new ByteArrayOutputStream();
2032                         bm.compress(Bitmap.CompressFormat.PNG, 100, os);
2033 
2034                         ContentValues values = new ContentValues();
2035                         values.put(Images.THUMBNAIL, os.toByteArray());
2036 
2037                         do {
2038                             values.put(Images.URL, cursor.getString(0));
2039                             cr.update(Images.CONTENT_URI, values, null, null);
2040                         } while (cursor.moveToNext());
2041                     }
2042                 } catch (IllegalStateException e) {
2043                     // Ignore
2044                 } finally {
2045                     if (cursor != null) cursor.close();
2046                 }
2047                 return null;
2048             }
2049         }.execute();
2050     }
2051 
2052     private class Copy implements OnMenuItemClickListener {
2053         private CharSequence mText;
2054 
onMenuItemClick(MenuItem item)2055         public boolean onMenuItemClick(MenuItem item) {
2056             copy(mText);
2057             return true;
2058         }
2059 
Copy(CharSequence toCopy)2060         public Copy(CharSequence toCopy) {
2061             mText = toCopy;
2062         }
2063     }
2064 
2065     private static class Download implements OnMenuItemClickListener {
2066         private Activity mActivity;
2067         private String mText;
2068         private boolean mPrivateBrowsing;
2069         private static final String FALLBACK_EXTENSION = "dat";
2070         private static final String IMAGE_BASE_FORMAT = "yyyy-MM-dd-HH-mm-ss-";
2071 
onMenuItemClick(MenuItem item)2072         public boolean onMenuItemClick(MenuItem item) {
2073             if (DataUri.isDataUri(mText)) {
2074                 saveDataUri();
2075             } else {
2076                 DownloadHandler.onDownloadStartNoStream(mActivity, mText, null,
2077                         null, null, mPrivateBrowsing);
2078             }
2079             return true;
2080         }
2081 
Download(Activity activity, String toDownload, boolean privateBrowsing)2082         public Download(Activity activity, String toDownload, boolean privateBrowsing) {
2083             mActivity = activity;
2084             mText = toDownload;
2085             mPrivateBrowsing = privateBrowsing;
2086         }
2087 
2088         /**
2089          * Treats mText as a data URI and writes its contents to a file
2090          * based on the current time.
2091          */
saveDataUri()2092         private void saveDataUri() {
2093             FileOutputStream outputStream = null;
2094             try {
2095                 DataUri uri = new DataUri(mText);
2096                 File target = getTarget(uri);
2097                 outputStream = new FileOutputStream(target);
2098                 outputStream.write(uri.getData());
2099                 final DownloadManager manager =
2100                         (DownloadManager) mActivity.getSystemService(Context.DOWNLOAD_SERVICE);
2101                  manager.addCompletedDownload(target.getName(),
2102                         mActivity.getTitle().toString(), false,
2103                         uri.getMimeType(), target.getAbsolutePath(),
2104                         (long)uri.getData().length, false);
2105             } catch (IOException e) {
2106                 Log.e(LOGTAG, "Could not save data URL");
2107             } finally {
2108                 if (outputStream != null) {
2109                     try {
2110                         outputStream.close();
2111                     } catch (IOException e) {
2112                         // ignore close errors
2113                     }
2114                 }
2115             }
2116         }
2117 
2118         /**
2119          * Creates a File based on the current time stamp and uses
2120          * the mime type of the DataUri to get the extension.
2121          */
getTarget(DataUri uri)2122         private File getTarget(DataUri uri) throws IOException {
2123             File dir = mActivity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
2124             DateFormat format = new SimpleDateFormat(IMAGE_BASE_FORMAT);
2125             String nameBase = format.format(new Date());
2126             String mimeType = uri.getMimeType();
2127             MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
2128             String extension = mimeTypeMap.getExtensionFromMimeType(mimeType);
2129             if (extension == null) {
2130                 Log.w(LOGTAG, "Unknown mime type in data URI" + mimeType);
2131                 extension = FALLBACK_EXTENSION;
2132             }
2133             extension = "." + extension; // createTempFile needs the '.'
2134             File targetFile = File.createTempFile(nameBase, extension, dir);
2135             return targetFile;
2136         }
2137     }
2138 
2139     private static class SelectText implements OnMenuItemClickListener {
2140         private WebView mWebView;
2141 
onMenuItemClick(MenuItem item)2142         public boolean onMenuItemClick(MenuItem item) {
2143             if (mWebView != null) {
2144                 return mWebView.selectText();
2145             }
2146             return false;
2147         }
2148 
SelectText(WebView webView)2149         public SelectText(WebView webView) {
2150             mWebView = webView;
2151         }
2152 
2153     }
2154 
2155     /********************** TODO: UI stuff *****************************/
2156 
2157     // these methods have been copied, they still need to be cleaned up
2158 
2159     /****************** tabs ***************************************************/
2160 
2161     // basic tab interactions:
2162 
2163     // it is assumed that tabcontrol already knows about the tab
addTab(Tab tab)2164     protected void addTab(Tab tab) {
2165         mUi.addTab(tab);
2166     }
2167 
removeTab(Tab tab)2168     protected void removeTab(Tab tab) {
2169         mUi.removeTab(tab);
2170         mTabControl.removeTab(tab);
2171         mCrashRecoveryHandler.backupState();
2172     }
2173 
2174     @Override
setActiveTab(Tab tab)2175     public void setActiveTab(Tab tab) {
2176         // monkey protection against delayed start
2177         if (tab != null) {
2178             mTabControl.setCurrentTab(tab);
2179             // the tab is guaranteed to have a webview after setCurrentTab
2180             mUi.setActiveTab(tab);
2181         }
2182     }
2183 
closeEmptyTab()2184     protected void closeEmptyTab() {
2185         Tab current = mTabControl.getCurrentTab();
2186         if (current != null
2187                 && current.getWebView().copyBackForwardList().getSize() == 0) {
2188             closeCurrentTab();
2189         }
2190     }
2191 
reuseTab(Tab appTab, UrlData urlData)2192     protected void reuseTab(Tab appTab, UrlData urlData) {
2193         // Dismiss the subwindow if applicable.
2194         dismissSubWindow(appTab);
2195         // Since we might kill the WebView, remove it from the
2196         // content view first.
2197         mUi.detachTab(appTab);
2198         // Recreate the main WebView after destroying the old one.
2199         mTabControl.recreateWebView(appTab);
2200         // TODO: analyze why the remove and add are necessary
2201         mUi.attachTab(appTab);
2202         if (mTabControl.getCurrentTab() != appTab) {
2203             switchToTab(appTab);
2204             loadUrlDataIn(appTab, urlData);
2205         } else {
2206             // If the tab was the current tab, we have to attach
2207             // it to the view system again.
2208             setActiveTab(appTab);
2209             loadUrlDataIn(appTab, urlData);
2210         }
2211     }
2212 
2213     // Remove the sub window if it exists. Also called by TabControl when the
2214     // user clicks the 'X' to dismiss a sub window.
dismissSubWindow(Tab tab)2215     public void dismissSubWindow(Tab tab) {
2216         removeSubWindow(tab);
2217         // dismiss the subwindow. This will destroy the WebView.
2218         tab.dismissSubWindow();
2219         getCurrentTopWebView().requestFocus();
2220     }
2221 
2222     @Override
removeSubWindow(Tab t)2223     public void removeSubWindow(Tab t) {
2224         if (t.getSubWebView() != null) {
2225             mUi.removeSubWindow(t.getSubViewContainer());
2226         }
2227     }
2228 
2229     @Override
attachSubWindow(Tab tab)2230     public void attachSubWindow(Tab tab) {
2231         if (tab.getSubWebView() != null) {
2232             mUi.attachSubWindow(tab.getSubViewContainer());
2233             getCurrentTopWebView().requestFocus();
2234         }
2235     }
2236 
showPreloadedTab(final UrlData urlData)2237     private Tab showPreloadedTab(final UrlData urlData) {
2238         if (!urlData.isPreloaded()) {
2239             return null;
2240         }
2241         final PreloadedTabControl tabControl = urlData.getPreloadedTab();
2242         final String sbQuery = urlData.getSearchBoxQueryToSubmit();
2243         if (sbQuery != null) {
2244             if (!tabControl.searchBoxSubmit(sbQuery, urlData.mUrl, urlData.mHeaders)) {
2245                 // Could not submit query. Fallback to regular tab creation
2246                 tabControl.destroy();
2247                 return null;
2248             }
2249         }
2250         // check tab count and make room for new tab
2251         if (!mTabControl.canCreateNewTab()) {
2252             Tab leastUsed = mTabControl.getLeastUsedTab(getCurrentTab());
2253             if (leastUsed != null) {
2254                 closeTab(leastUsed);
2255             }
2256         }
2257         Tab t = tabControl.getTab();
2258         t.refreshIdAfterPreload();
2259         mTabControl.addPreloadedTab(t);
2260         addTab(t);
2261         setActiveTab(t);
2262         return t;
2263     }
2264 
2265     // open a non inconito tab with the given url data
2266     // and set as active tab
openTab(UrlData urlData)2267     public Tab openTab(UrlData urlData) {
2268         Tab tab = showPreloadedTab(urlData);
2269         if (tab == null) {
2270             tab = createNewTab(false, true, true);
2271             if ((tab != null) && !urlData.isEmpty()) {
2272                 loadUrlDataIn(tab, urlData);
2273             }
2274         }
2275         return tab;
2276     }
2277 
2278     @Override
openTabToHomePage()2279     public Tab openTabToHomePage() {
2280         return openTab(mSettings.getHomePage(), false, true, false);
2281     }
2282 
2283     @Override
openIncognitoTab()2284     public Tab openIncognitoTab() {
2285         return openTab(INCOGNITO_URI, true, true, false);
2286     }
2287 
2288     @Override
openTab(String url, boolean incognito, boolean setActive, boolean useCurrent)2289     public Tab openTab(String url, boolean incognito, boolean setActive,
2290             boolean useCurrent) {
2291         return openTab(url, incognito, setActive, useCurrent, null);
2292     }
2293 
2294     @Override
openTab(String url, Tab parent, boolean setActive, boolean useCurrent)2295     public Tab openTab(String url, Tab parent, boolean setActive,
2296             boolean useCurrent) {
2297         return openTab(url, (parent != null) && parent.isPrivateBrowsingEnabled(),
2298                 setActive, useCurrent, parent);
2299     }
2300 
openTab(String url, boolean incognito, boolean setActive, boolean useCurrent, Tab parent)2301     public Tab openTab(String url, boolean incognito, boolean setActive,
2302             boolean useCurrent, Tab parent) {
2303         Tab tab = createNewTab(incognito, setActive, useCurrent);
2304         if (tab != null) {
2305             if (parent != null && parent != tab) {
2306                 parent.addChildTab(tab);
2307             }
2308             if (url != null) {
2309                 loadUrl(tab, url);
2310             }
2311         }
2312         return tab;
2313     }
2314 
2315     // this method will attempt to create a new tab
2316     // incognito: private browsing tab
2317     // setActive: ste tab as current tab
2318     // useCurrent: if no new tab can be created, return current tab
createNewTab(boolean incognito, boolean setActive, boolean useCurrent)2319     private Tab createNewTab(boolean incognito, boolean setActive,
2320             boolean useCurrent) {
2321         Tab tab = null;
2322         if (mTabControl.canCreateNewTab()) {
2323             tab = mTabControl.createNewTab(incognito);
2324             addTab(tab);
2325             if (setActive) {
2326                 setActiveTab(tab);
2327             }
2328         } else {
2329             if (useCurrent) {
2330                 tab = mTabControl.getCurrentTab();
2331                 reuseTab(tab, null);
2332             } else {
2333                 mUi.showMaxTabsWarning();
2334             }
2335         }
2336         return tab;
2337     }
2338 
2339     @Override
createNewSnapshotTab(long snapshotId, boolean setActive)2340     public SnapshotTab createNewSnapshotTab(long snapshotId, boolean setActive) {
2341         SnapshotTab tab = null;
2342         if (mTabControl.canCreateNewTab()) {
2343             tab = mTabControl.createSnapshotTab(snapshotId);
2344             addTab(tab);
2345             if (setActive) {
2346                 setActiveTab(tab);
2347             }
2348         } else {
2349             mUi.showMaxTabsWarning();
2350         }
2351         return tab;
2352     }
2353 
2354     /**
2355      * @param tab the tab to switch to
2356      * @return boolean True if we successfully switched to a different tab.  If
2357      *                 the indexth tab is null, or if that tab is the same as
2358      *                 the current one, return false.
2359      */
2360     @Override
switchToTab(Tab tab)2361     public boolean switchToTab(Tab tab) {
2362         Tab currentTab = mTabControl.getCurrentTab();
2363         if (tab == null || tab == currentTab) {
2364             return false;
2365         }
2366         setActiveTab(tab);
2367         return true;
2368     }
2369 
2370     @Override
closeCurrentTab()2371     public void closeCurrentTab() {
2372         closeCurrentTab(false);
2373     }
2374 
closeCurrentTab(boolean andQuit)2375     protected void closeCurrentTab(boolean andQuit) {
2376         if (mTabControl.getTabCount() == 1) {
2377             mCrashRecoveryHandler.clearState();
2378             mTabControl.removeTab(getCurrentTab());
2379             mActivity.finish();
2380             return;
2381         }
2382         final Tab current = mTabControl.getCurrentTab();
2383         final int pos = mTabControl.getCurrentPosition();
2384         Tab newTab = current.getParent();
2385         if (newTab == null) {
2386             newTab = mTabControl.getTab(pos + 1);
2387             if (newTab == null) {
2388                 newTab = mTabControl.getTab(pos - 1);
2389             }
2390         }
2391         if (andQuit) {
2392             mTabControl.setCurrentTab(newTab);
2393             closeTab(current);
2394         } else if (switchToTab(newTab)) {
2395             // Close window
2396             closeTab(current);
2397         }
2398     }
2399 
2400     /**
2401      * Close the tab, remove its associated title bar, and adjust mTabControl's
2402      * current tab to a valid value.
2403      */
2404     @Override
closeTab(Tab tab)2405     public void closeTab(Tab tab) {
2406         if (tab == mTabControl.getCurrentTab()) {
2407             closeCurrentTab();
2408         } else {
2409             removeTab(tab);
2410         }
2411     }
2412 
2413     // Called when loading from context menu or LOAD_URL message
loadUrlFromContext(String url)2414     protected void loadUrlFromContext(String url) {
2415         Tab tab = getCurrentTab();
2416         WebView view = tab != null ? tab.getWebView() : null;
2417         // In case the user enters nothing.
2418         if (url != null && url.length() != 0 && tab != null && view != null) {
2419             url = UrlUtils.smartUrlFilter(url);
2420             if (!view.getWebViewClient().shouldOverrideUrlLoading(view, url)) {
2421                 loadUrl(tab, url);
2422             }
2423         }
2424     }
2425 
2426     /**
2427      * Load the URL into the given WebView and update the title bar
2428      * to reflect the new load.  Call this instead of WebView.loadUrl
2429      * directly.
2430      * @param view The WebView used to load url.
2431      * @param url The URL to load.
2432      */
2433     @Override
loadUrl(Tab tab, String url)2434     public void loadUrl(Tab tab, String url) {
2435         loadUrl(tab, url, null);
2436     }
2437 
loadUrl(Tab tab, String url, Map<String, String> headers)2438     protected void loadUrl(Tab tab, String url, Map<String, String> headers) {
2439         if (tab != null) {
2440             dismissSubWindow(tab);
2441             tab.loadUrl(url, headers);
2442             mUi.onProgressChanged(tab);
2443         }
2444     }
2445 
2446     /**
2447      * Load UrlData into a Tab and update the title bar to reflect the new
2448      * load.  Call this instead of UrlData.loadIn directly.
2449      * @param t The Tab used to load.
2450      * @param data The UrlData being loaded.
2451      */
loadUrlDataIn(Tab t, UrlData data)2452     protected void loadUrlDataIn(Tab t, UrlData data) {
2453         if (data != null) {
2454             if (data.mVoiceIntent != null) {
2455                 t.activateVoiceSearchMode(data.mVoiceIntent);
2456             } else if (data.isPreloaded()) {
2457                 // this isn't called for preloaded tabs
2458             } else {
2459                 loadUrl(t, data.mUrl, data.mHeaders);
2460             }
2461         }
2462     }
2463 
2464     @Override
onUserCanceledSsl(Tab tab)2465     public void onUserCanceledSsl(Tab tab) {
2466         // TODO: Figure out the "right" behavior
2467         if (tab.canGoBack()) {
2468             tab.goBack();
2469         } else {
2470             tab.loadUrl(mSettings.getHomePage(), null);
2471         }
2472     }
2473 
goBackOnePageOrQuit()2474     void goBackOnePageOrQuit() {
2475         Tab current = mTabControl.getCurrentTab();
2476         if (current == null) {
2477             /*
2478              * Instead of finishing the activity, simply push this to the back
2479              * of the stack and let ActivityManager to choose the foreground
2480              * activity. As BrowserActivity is singleTask, it will be always the
2481              * root of the task. So we can use either true or false for
2482              * moveTaskToBack().
2483              */
2484             mActivity.moveTaskToBack(true);
2485             return;
2486         }
2487         if (current.canGoBack()) {
2488             current.goBack();
2489         } else {
2490             // Check to see if we are closing a window that was created by
2491             // another window. If so, we switch back to that window.
2492             Tab parent = current.getParent();
2493             if (parent != null) {
2494                 switchToTab(parent);
2495                 // Now we close the other tab
2496                 closeTab(current);
2497             } else {
2498                 if ((current.getAppId() != null) || current.closeOnBack()) {
2499                     closeCurrentTab(true);
2500                 }
2501                 /*
2502                  * Instead of finishing the activity, simply push this to the back
2503                  * of the stack and let ActivityManager to choose the foreground
2504                  * activity. As BrowserActivity is singleTask, it will be always the
2505                  * root of the task. So we can use either true or false for
2506                  * moveTaskToBack().
2507                  */
2508                 mActivity.moveTaskToBack(true);
2509             }
2510         }
2511     }
2512 
2513     /**
2514      * Feed the previously stored results strings to the BrowserProvider so that
2515      * the SearchDialog will show them instead of the standard searches.
2516      * @param result String to show on the editable line of the SearchDialog.
2517      */
2518     @Override
showVoiceSearchResults(String result)2519     public void showVoiceSearchResults(String result) {
2520         ContentProviderClient client = mActivity.getContentResolver()
2521                 .acquireContentProviderClient(Browser.BOOKMARKS_URI);
2522         ContentProvider prov = client.getLocalContentProvider();
2523         BrowserProvider bp = (BrowserProvider) prov;
2524         bp.setQueryResults(mTabControl.getCurrentTab().getVoiceSearchResults());
2525         client.release();
2526 
2527         Bundle bundle = createGoogleSearchSourceBundle(
2528                 GOOGLE_SEARCH_SOURCE_SEARCHKEY);
2529         bundle.putBoolean(SearchManager.CONTEXT_IS_VOICE, true);
2530         startSearch(result, false, bundle, false);
2531     }
2532 
startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, boolean globalSearch)2533     private void startSearch(String initialQuery, boolean selectInitialQuery,
2534             Bundle appSearchData, boolean globalSearch) {
2535         if (appSearchData == null) {
2536             appSearchData = createGoogleSearchSourceBundle(
2537                     GOOGLE_SEARCH_SOURCE_TYPE);
2538         }
2539 
2540         SearchEngine searchEngine = mSettings.getSearchEngine();
2541         if (searchEngine != null && !searchEngine.supportsVoiceSearch()) {
2542             appSearchData.putBoolean(SearchManager.DISABLE_VOICE_SEARCH, true);
2543         }
2544         mActivity.startSearch(initialQuery, selectInitialQuery, appSearchData,
2545                 globalSearch);
2546     }
2547 
createGoogleSearchSourceBundle(String source)2548     private Bundle createGoogleSearchSourceBundle(String source) {
2549         Bundle bundle = new Bundle();
2550         bundle.putString(Search.SOURCE, source);
2551         return bundle;
2552     }
2553 
2554     /**
2555      * helper method for key handler
2556      * returns the current tab if it can't advance
2557      */
getNextTab()2558     private Tab getNextTab() {
2559         return mTabControl.getTab(Math.min(mTabControl.getTabCount() - 1,
2560                 mTabControl.getCurrentPosition() + 1));
2561     }
2562 
2563     /**
2564      * helper method for key handler
2565      * returns the current tab if it can't advance
2566      */
getPrevTab()2567     private Tab getPrevTab() {
2568         return  mTabControl.getTab(Math.max(0,
2569                 mTabControl.getCurrentPosition() - 1));
2570     }
2571 
2572     /**
2573      * handle key events in browser
2574      *
2575      * @param keyCode
2576      * @param event
2577      * @return true if handled, false to pass to super
2578      */
onKeyDown(int keyCode, KeyEvent event)2579     boolean onKeyDown(int keyCode, KeyEvent event) {
2580         boolean noModifiers = event.hasNoModifiers();
2581         // Even if MENU is already held down, we need to call to super to open
2582         // the IME on long press.
2583         if (!noModifiers
2584                 && ((KeyEvent.KEYCODE_MENU == keyCode)
2585                         || (KeyEvent.KEYCODE_CTRL_LEFT == keyCode)
2586                         || (KeyEvent.KEYCODE_CTRL_RIGHT == keyCode))) {
2587             mMenuIsDown = true;
2588             return false;
2589         }
2590 
2591         WebView webView = getCurrentTopWebView();
2592         Tab tab = getCurrentTab();
2593         if (webView == null || tab == null) return false;
2594 
2595         boolean ctrl = event.hasModifiers(KeyEvent.META_CTRL_ON);
2596         boolean shift = event.hasModifiers(KeyEvent.META_SHIFT_ON);
2597 
2598         switch(keyCode) {
2599             case KeyEvent.KEYCODE_TAB:
2600                 if (event.isCtrlPressed()) {
2601                     if (event.isShiftPressed()) {
2602                         // prev tab
2603                         switchToTab(getPrevTab());
2604                     } else {
2605                         // next tab
2606                         switchToTab(getNextTab());
2607                     }
2608                     return true;
2609                 }
2610                 break;
2611             case KeyEvent.KEYCODE_SPACE:
2612                 // WebView/WebTextView handle the keys in the KeyDown. As
2613                 // the Activity's shortcut keys are only handled when WebView
2614                 // doesn't, have to do it in onKeyDown instead of onKeyUp.
2615                 if (shift) {
2616                     pageUp();
2617                 } else if (noModifiers) {
2618                     pageDown();
2619                 }
2620                 return true;
2621             case KeyEvent.KEYCODE_BACK:
2622                 if (!noModifiers) break;
2623                 event.startTracking();
2624                 return true;
2625             case KeyEvent.KEYCODE_FORWARD:
2626                 if (!noModifiers) break;
2627                 tab.goForward();
2628                 return true;
2629             case KeyEvent.KEYCODE_DPAD_LEFT:
2630                 if (ctrl) {
2631                     tab.goBack();
2632                     return true;
2633                 }
2634                 break;
2635             case KeyEvent.KEYCODE_DPAD_RIGHT:
2636                 if (ctrl) {
2637                     tab.goForward();
2638                     return true;
2639                 }
2640                 break;
2641             case KeyEvent.KEYCODE_A:
2642                 if (ctrl) {
2643                     webView.selectAll();
2644                     return true;
2645                 }
2646                 break;
2647 //          case KeyEvent.KEYCODE_B:    // menu
2648             case KeyEvent.KEYCODE_C:
2649                 if (ctrl) {
2650                     webView.copySelection();
2651                     return true;
2652                 }
2653                 break;
2654 //          case KeyEvent.KEYCODE_D:    // menu
2655 //          case KeyEvent.KEYCODE_E:    // in Chrome: puts '?' in URL bar
2656 //          case KeyEvent.KEYCODE_F:    // menu
2657 //          case KeyEvent.KEYCODE_G:    // in Chrome: finds next match
2658 //          case KeyEvent.KEYCODE_H:    // menu
2659 //          case KeyEvent.KEYCODE_I:    // unused
2660 //          case KeyEvent.KEYCODE_J:    // menu
2661 //          case KeyEvent.KEYCODE_K:    // in Chrome: puts '?' in URL bar
2662 //          case KeyEvent.KEYCODE_L:    // menu
2663 //          case KeyEvent.KEYCODE_M:    // unused
2664 //          case KeyEvent.KEYCODE_N:    // in Chrome: new window
2665 //          case KeyEvent.KEYCODE_O:    // in Chrome: open file
2666 //          case KeyEvent.KEYCODE_P:    // in Chrome: print page
2667 //          case KeyEvent.KEYCODE_Q:    // unused
2668 //          case KeyEvent.KEYCODE_R:
2669 //          case KeyEvent.KEYCODE_S:    // in Chrome: saves page
2670             case KeyEvent.KEYCODE_T:
2671                 // we can't use the ctrl/shift flags, they check for
2672                 // exclusive use of a modifier
2673                 if (event.isCtrlPressed()) {
2674                     if (event.isShiftPressed()) {
2675                         openIncognitoTab();
2676                     } else {
2677                         openTabToHomePage();
2678                     }
2679                     return true;
2680                 }
2681                 break;
2682 //          case KeyEvent.KEYCODE_U:    // in Chrome: opens source of page
2683 //          case KeyEvent.KEYCODE_V:    // text view intercepts to paste
2684 //          case KeyEvent.KEYCODE_W:    // menu
2685 //          case KeyEvent.KEYCODE_X:    // text view intercepts to cut
2686 //          case KeyEvent.KEYCODE_Y:    // unused
2687 //          case KeyEvent.KEYCODE_Z:    // unused
2688         }
2689         // it is a regular key and webview is not null
2690          return mUi.dispatchKey(keyCode, event);
2691     }
2692 
onKeyLongPress(int keyCode, KeyEvent event)2693     boolean onKeyLongPress(int keyCode, KeyEvent event) {
2694         switch(keyCode) {
2695         case KeyEvent.KEYCODE_BACK:
2696             if (mUi.isWebShowing()) {
2697                 bookmarksOrHistoryPicker(ComboViews.History);
2698                 return true;
2699             }
2700             break;
2701         }
2702         return false;
2703     }
2704 
onKeyUp(int keyCode, KeyEvent event)2705     boolean onKeyUp(int keyCode, KeyEvent event) {
2706         if (KeyEvent.KEYCODE_MENU == keyCode) {
2707             mMenuIsDown = false;
2708             if (event.isTracking() && !event.isCanceled()) {
2709                 return onMenuKey();
2710             }
2711         }
2712         if (!event.hasNoModifiers()) return false;
2713         switch(keyCode) {
2714             case KeyEvent.KEYCODE_BACK:
2715                 if (event.isTracking() && !event.isCanceled()) {
2716                     onBackKey();
2717                     return true;
2718                 }
2719                 break;
2720         }
2721         return false;
2722     }
2723 
isMenuDown()2724     public boolean isMenuDown() {
2725         return mMenuIsDown;
2726     }
2727 
setupAutoFill(Message message)2728     public void setupAutoFill(Message message) {
2729         // Open the settings activity at the AutoFill profile fragment so that
2730         // the user can create a new profile. When they return, we will dispatch
2731         // the message so that we can autofill the form using their new profile.
2732         Intent intent = new Intent(mActivity, BrowserPreferencesPage.class);
2733         intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT,
2734                 AutoFillSettingsFragment.class.getName());
2735         mAutoFillSetupMessage = message;
2736         mActivity.startActivityForResult(intent, AUTOFILL_SETUP);
2737     }
2738 
2739     @Override
registerDropdownChangeListener(DropdownChangeListener d)2740     public void registerDropdownChangeListener(DropdownChangeListener d) {
2741         mUi.registerDropdownChangeListener(d);
2742     }
2743 
onSearchRequested()2744     public boolean onSearchRequested() {
2745         mUi.editUrl(false);
2746         return true;
2747     }
2748 
2749     @Override
shouldCaptureThumbnails()2750     public boolean shouldCaptureThumbnails() {
2751         return mUi.shouldCaptureThumbnails();
2752     }
2753 
2754     @Override
setBlockEvents(boolean block)2755     public void setBlockEvents(boolean block) {
2756         mBlockEvents = block;
2757     }
2758 
dispatchKeyEvent(KeyEvent event)2759     public boolean dispatchKeyEvent(KeyEvent event) {
2760         return mBlockEvents;
2761     }
2762 
dispatchKeyShortcutEvent(KeyEvent event)2763     public boolean dispatchKeyShortcutEvent(KeyEvent event) {
2764         return mBlockEvents;
2765     }
2766 
dispatchTouchEvent(MotionEvent ev)2767     public boolean dispatchTouchEvent(MotionEvent ev) {
2768         return mBlockEvents;
2769     }
2770 
dispatchTrackballEvent(MotionEvent ev)2771     public boolean dispatchTrackballEvent(MotionEvent ev) {
2772         return mBlockEvents;
2773     }
2774 
dispatchGenericMotionEvent(MotionEvent ev)2775     public boolean dispatchGenericMotionEvent(MotionEvent ev) {
2776         return mBlockEvents;
2777     }
2778 
2779 }
2780