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