• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 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.AlertDialog;
21 import android.app.DownloadManager;
22 import android.app.ProgressDialog;
23 import android.app.SearchManager;
24 import android.content.ActivityNotFoundException;
25 import android.content.BroadcastReceiver;
26 import android.content.ComponentName;
27 import android.content.ContentProvider;
28 import android.content.ContentProviderClient;
29 import android.content.ContentResolver;
30 import android.content.ContentUris;
31 import android.content.ContentValues;
32 import android.content.Context;
33 import android.content.DialogInterface;
34 import android.content.Intent;
35 import android.content.IntentFilter;
36 import android.content.pm.PackageInfo;
37 import android.content.pm.PackageManager;
38 import android.content.pm.ResolveInfo;
39 import android.content.res.Configuration;
40 import android.content.res.Resources;
41 import android.database.Cursor;
42 import android.database.DatabaseUtils;
43 import android.graphics.Bitmap;
44 import android.graphics.BitmapFactory;
45 import android.graphics.Canvas;
46 import android.graphics.Picture;
47 import android.graphics.PixelFormat;
48 import android.graphics.Rect;
49 import android.graphics.drawable.Drawable;
50 import android.net.ConnectivityManager;
51 import android.net.NetworkInfo;
52 import android.net.Uri;
53 import android.net.WebAddress;
54 import android.net.http.SslCertificate;
55 import android.net.http.SslError;
56 import android.os.AsyncTask;
57 import android.os.Bundle;
58 import android.os.Debug;
59 import android.os.Environment;
60 import android.os.Handler;
61 import android.os.Message;
62 import android.os.PowerManager;
63 import android.os.Process;
64 import android.os.ServiceManager;
65 import android.os.SystemClock;
66 import android.provider.Browser;
67 import android.provider.ContactsContract;
68 import android.provider.ContactsContract.Intents.Insert;
69 import android.provider.Downloads;
70 import android.provider.MediaStore;
71 import android.speech.RecognizerResultsIntent;
72 import android.text.IClipboard;
73 import android.text.TextUtils;
74 import android.text.format.DateFormat;
75 import android.util.AttributeSet;
76 import android.util.Log;
77 import android.util.Patterns;
78 import android.view.ContextMenu;
79 import android.view.Gravity;
80 import android.view.KeyEvent;
81 import android.view.LayoutInflater;
82 import android.view.Menu;
83 import android.view.MenuInflater;
84 import android.view.MenuItem;
85 import android.view.View;
86 import android.view.ViewGroup;
87 import android.view.Window;
88 import android.view.WindowManager;
89 import android.view.ContextMenu.ContextMenuInfo;
90 import android.view.MenuItem.OnMenuItemClickListener;
91 import android.webkit.CookieManager;
92 import android.webkit.CookieSyncManager;
93 import android.webkit.DownloadListener;
94 import android.webkit.HttpAuthHandler;
95 import android.webkit.PluginManager;
96 import android.webkit.SslErrorHandler;
97 import android.webkit.URLUtil;
98 import android.webkit.ValueCallback;
99 import android.webkit.WebChromeClient;
100 import android.webkit.WebHistoryItem;
101 import android.webkit.WebIconDatabase;
102 import android.webkit.WebView;
103 import android.widget.EditText;
104 import android.widget.FrameLayout;
105 import android.widget.LinearLayout;
106 import android.widget.TextView;
107 import android.widget.Toast;
108 import android.accounts.Account;
109 import android.accounts.AccountManager;
110 import android.accounts.AccountManagerFuture;
111 import android.accounts.AuthenticatorException;
112 import android.accounts.OperationCanceledException;
113 import android.accounts.AccountManagerCallback;
114 
115 import com.android.browser.search.SearchEngine;
116 import com.android.common.Search;
117 import com.android.common.speech.LoggingEvents;
118 
119 import java.io.ByteArrayOutputStream;
120 import java.io.File;
121 import java.io.IOException;
122 import java.io.InputStream;
123 import java.net.MalformedURLException;
124 import java.net.URI;
125 import java.net.URISyntaxException;
126 import java.net.URL;
127 import java.net.URLEncoder;
128 import java.text.ParseException;
129 import java.util.Date;
130 import java.util.HashMap;
131 import java.util.HashSet;
132 import java.util.Iterator;
133 import java.util.List;
134 import java.util.Map;
135 import java.util.Set;
136 import java.util.regex.Matcher;
137 import java.util.regex.Pattern;
138 
139 public class BrowserActivity extends Activity
140     implements View.OnCreateContextMenuListener, DownloadListener {
141 
142     /* Define some aliases to make these debugging flags easier to refer to.
143      * This file imports android.provider.Browser, so we can't just refer to "Browser.DEBUG".
144      */
145     private final static boolean DEBUG = com.android.browser.Browser.DEBUG;
146     private final static boolean LOGV_ENABLED = com.android.browser.Browser.LOGV_ENABLED;
147     private final static boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED;
148 
149     private static class ClearThumbnails extends AsyncTask<File, Void, Void> {
150         @Override
doInBackground(File... files)151         public Void doInBackground(File... files) {
152             if (files != null) {
153                 for (File f : files) {
154                     if (!f.delete()) {
155                       Log.e(LOGTAG, f.getPath() + " was not deleted");
156                     }
157                 }
158             }
159             return null;
160         }
161     }
162 
163     /**
164      * This layout holds everything you see below the status bar, including the
165      * error console, the custom view container, and the webviews.
166      */
167     private FrameLayout mBrowserFrameLayout;
168 
169     @Override
onCreate(Bundle icicle)170     public void onCreate(Bundle icicle) {
171         if (LOGV_ENABLED) {
172             Log.v(LOGTAG, this + " onStart");
173         }
174         super.onCreate(icicle);
175         // test the browser in OpenGL
176         // requestWindowFeature(Window.FEATURE_OPENGL);
177 
178         // enable this to test the browser in 32bit
179         if (false) {
180             getWindow().setFormat(PixelFormat.RGBX_8888);
181             BitmapFactory.setDefaultConfig(Bitmap.Config.ARGB_8888);
182         }
183 
184         setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
185 
186         mResolver = getContentResolver();
187 
188         // Keep a settings instance handy.
189         mSettings = BrowserSettings.getInstance();
190 
191         mSettings.updateRlzValues(this);
192 
193         // If this was a web search request, pass it on to the default web
194         // search provider and finish this activity.
195         if (handleWebSearchIntent(getIntent())) {
196             finish();
197             return;
198         }
199 
200         mSecLockIcon = Resources.getSystem().getDrawable(
201                 android.R.drawable.ic_secure);
202         mMixLockIcon = Resources.getSystem().getDrawable(
203                 android.R.drawable.ic_partial_secure);
204 
205         FrameLayout frameLayout = (FrameLayout) getWindow().getDecorView()
206                 .findViewById(com.android.internal.R.id.content);
207         mBrowserFrameLayout = (FrameLayout) LayoutInflater.from(this)
208                 .inflate(R.layout.custom_screen, null);
209         mContentView = (FrameLayout) mBrowserFrameLayout.findViewById(
210                 R.id.main_content);
211         mErrorConsoleContainer = (LinearLayout) mBrowserFrameLayout
212                 .findViewById(R.id.error_console);
213         mCustomViewContainer = (FrameLayout) mBrowserFrameLayout
214                 .findViewById(R.id.fullscreen_custom_content);
215         frameLayout.addView(mBrowserFrameLayout, COVER_SCREEN_PARAMS);
216         mTitleBar = new TitleBar(this);
217         // mTitleBar will be always shown in the fully loaded mode
218         mTitleBar.setProgress(100);
219         mFakeTitleBar = new TitleBar(this);
220 
221         // Create the tab control and our initial tab
222         mTabControl = new TabControl(this);
223 
224         // Open the icon database and retain all the bookmark urls for favicons
225         retainIconsOnStartup();
226 
227         mSettings.setTabControl(mTabControl);
228 
229         PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
230         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Browser");
231 
232         // Find out if the network is currently up.
233         ConnectivityManager cm = (ConnectivityManager) getSystemService(
234                 Context.CONNECTIVITY_SERVICE);
235         NetworkInfo info = cm.getActiveNetworkInfo();
236         if (info != null) {
237             mIsNetworkUp = info.isAvailable();
238         }
239 
240         /* enables registration for changes in network status from
241            http stack */
242         mNetworkStateChangedFilter = new IntentFilter();
243         mNetworkStateChangedFilter.addAction(
244                 ConnectivityManager.CONNECTIVITY_ACTION);
245         mNetworkStateIntentReceiver = new BroadcastReceiver() {
246                 @Override
247                 public void onReceive(Context context, Intent intent) {
248                     if (intent.getAction().equals(
249                             ConnectivityManager.CONNECTIVITY_ACTION)) {
250 
251                         NetworkInfo info = intent.getParcelableExtra(
252                                 ConnectivityManager.EXTRA_NETWORK_INFO);
253                         String typeName = info.getTypeName();
254                         String subtypeName = info.getSubtypeName();
255                         sendNetworkType(typeName.toLowerCase(),
256                                 (subtypeName != null ? subtypeName.toLowerCase() : ""));
257 
258                         onNetworkToggle(info.isAvailable());
259                     }
260                 }
261             };
262 
263         IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
264         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
265         filter.addDataScheme("package");
266         mPackageInstallationReceiver = new BroadcastReceiver() {
267             @Override
268             public void onReceive(Context context, Intent intent) {
269                 final String action = intent.getAction();
270                 final String packageName = intent.getData()
271                         .getSchemeSpecificPart();
272                 final boolean replacing = intent.getBooleanExtra(
273                         Intent.EXTRA_REPLACING, false);
274                 if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) {
275                     // if it is replacing, refreshPlugins() when adding
276                     return;
277                 }
278 
279                 if (sGoogleApps.contains(packageName)) {
280                     BrowserActivity.this.packageChanged(packageName,
281                             Intent.ACTION_PACKAGE_ADDED.equals(action));
282                 }
283 
284                 PackageManager pm = BrowserActivity.this.getPackageManager();
285                 PackageInfo pkgInfo = null;
286                 try {
287                     pkgInfo = pm.getPackageInfo(packageName,
288                             PackageManager.GET_PERMISSIONS);
289                 } catch (PackageManager.NameNotFoundException e) {
290                     return;
291                 }
292                 if (pkgInfo != null) {
293                     String permissions[] = pkgInfo.requestedPermissions;
294                     if (permissions == null) {
295                         return;
296                     }
297                     boolean permissionOk = false;
298                     for (String permit : permissions) {
299                         if (PluginManager.PLUGIN_PERMISSION.equals(permit)) {
300                             permissionOk = true;
301                             break;
302                         }
303                     }
304                     if (permissionOk) {
305                         PluginManager.getInstance(BrowserActivity.this)
306                                 .refreshPlugins(
307                                         Intent.ACTION_PACKAGE_ADDED
308                                                 .equals(action));
309                     }
310                 }
311             }
312         };
313         registerReceiver(mPackageInstallationReceiver, filter);
314 
315         if (!mTabControl.restoreState(icicle)) {
316             // clear up the thumbnail directory if we can't restore the state as
317             // none of the files in the directory are referenced any more.
318             new ClearThumbnails().execute(
319                     mTabControl.getThumbnailDir().listFiles());
320             // there is no quit on Android. But if we can't restore the state,
321             // we can treat it as a new Browser, remove the old session cookies.
322             CookieManager.getInstance().removeSessionCookie();
323             final Intent intent = getIntent();
324             final Bundle extra = intent.getExtras();
325             // Create an initial tab.
326             // If the intent is ACTION_VIEW and data is not null, the Browser is
327             // invoked to view the content by another application. In this case,
328             // the tab will be close when exit.
329             UrlData urlData = getUrlDataFromIntent(intent);
330 
331             String action = intent.getAction();
332             final Tab t = mTabControl.createNewTab(
333                     (Intent.ACTION_VIEW.equals(action) &&
334                     intent.getData() != null)
335                     || RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS
336                     .equals(action),
337                     intent.getStringExtra(Browser.EXTRA_APPLICATION_ID), urlData.mUrl);
338             mTabControl.setCurrentTab(t);
339             attachTabToContentView(t);
340             WebView webView = t.getWebView();
341             if (extra != null) {
342                 int scale = extra.getInt(Browser.INITIAL_ZOOM_LEVEL, 0);
343                 if (scale > 0 && scale <= 1000) {
344                     webView.setInitialScale(scale);
345                 }
346             }
347 
348             if (urlData.isEmpty()) {
349                 loadUrl(webView, mSettings.getHomePage());
350             } else {
351                 loadUrlDataIn(t, urlData);
352             }
353         } else {
354             // TabControl.restoreState() will create a new tab even if
355             // restoring the state fails.
356             attachTabToContentView(mTabControl.getCurrentTab());
357         }
358 
359         // Delete old thumbnails to save space
360         File dir = mTabControl.getThumbnailDir();
361         if (dir.exists()) {
362             for (String child : dir.list()) {
363                 File f = new File(dir, child);
364                 f.delete();
365             }
366         }
367 
368         // Read JavaScript flags if it exists.
369         String jsFlags = mSettings.getJsFlags();
370         if (jsFlags.trim().length() != 0) {
371             mTabControl.getCurrentWebView().setJsFlags(jsFlags);
372         }
373         // Work out which packages are installed on the system.
374         getInstalledPackages();
375 
376         // Start watching the default geolocation permissions
377         mSystemAllowGeolocationOrigins
378                 = new SystemAllowGeolocationOrigins(getApplicationContext());
379         mSystemAllowGeolocationOrigins.start();
380     }
381 
382     /**
383      * Feed the previously stored results strings to the BrowserProvider so that
384      * the SearchDialog will show them instead of the standard searches.
385      * @param result String to show on the editable line of the SearchDialog.
386      */
showVoiceSearchResults(String result)387     /* package */ void showVoiceSearchResults(String result) {
388         ContentProviderClient client = mResolver.acquireContentProviderClient(
389                 Browser.BOOKMARKS_URI);
390         ContentProvider prov = client.getLocalContentProvider();
391         BrowserProvider bp = (BrowserProvider) prov;
392         bp.setQueryResults(mTabControl.getCurrentTab().getVoiceSearchResults());
393         client.release();
394 
395         Bundle bundle = createGoogleSearchSourceBundle(
396                 GOOGLE_SEARCH_SOURCE_SEARCHKEY);
397         bundle.putBoolean(SearchManager.CONTEXT_IS_VOICE, true);
398         startSearch(result, false, bundle, false);
399     }
400 
401     @Override
onNewIntent(Intent intent)402     protected void onNewIntent(Intent intent) {
403         Tab current = mTabControl.getCurrentTab();
404         // When a tab is closed on exit, the current tab index is set to -1.
405         // Reset before proceed as Browser requires the current tab to be set.
406         if (current == null) {
407             // Try to reset the tab in case the index was incorrect.
408             current = mTabControl.getTab(0);
409             if (current == null) {
410                 // No tabs at all so just ignore this intent.
411                 return;
412             }
413             mTabControl.setCurrentTab(current);
414             attachTabToContentView(current);
415             resetTitleAndIcon(current.getWebView());
416         }
417         final String action = intent.getAction();
418         final int flags = intent.getFlags();
419         if (Intent.ACTION_MAIN.equals(action) ||
420                 (flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
421             // just resume the browser
422             return;
423         }
424         // In case the SearchDialog is open.
425         ((SearchManager) getSystemService(Context.SEARCH_SERVICE))
426                 .stopSearch();
427         boolean activateVoiceSearch = RecognizerResultsIntent
428                 .ACTION_VOICE_SEARCH_RESULTS.equals(action);
429         if (Intent.ACTION_VIEW.equals(action)
430                 || Intent.ACTION_SEARCH.equals(action)
431                 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
432                 || Intent.ACTION_WEB_SEARCH.equals(action)
433                 || activateVoiceSearch) {
434             if (current.isInVoiceSearchMode()) {
435                 String title = current.getVoiceDisplayTitle();
436                 if (title != null && title.equals(intent.getStringExtra(
437                         SearchManager.QUERY))) {
438                     // The user submitted the same search as the last voice
439                     // search, so do nothing.
440                     return;
441                 }
442                 if (Intent.ACTION_SEARCH.equals(action)
443                         && current.voiceSearchSourceIsGoogle()) {
444                     Intent logIntent = new Intent(
445                             LoggingEvents.ACTION_LOG_EVENT);
446                     logIntent.putExtra(LoggingEvents.EXTRA_EVENT,
447                             LoggingEvents.VoiceSearch.QUERY_UPDATED);
448                     logIntent.putExtra(
449                             LoggingEvents.VoiceSearch.EXTRA_QUERY_UPDATED_VALUE,
450                             intent.getDataString());
451                     sendBroadcast(logIntent);
452                     // Note, onPageStarted will revert the voice title bar
453                     // When http://b/issue?id=2379215 is fixed, we should update
454                     // the title bar here.
455                 }
456             }
457             // If this was a search request (e.g. search query directly typed into the address bar),
458             // pass it on to the default web search provider.
459             if (handleWebSearchIntent(intent)) {
460                 return;
461             }
462 
463             UrlData urlData = getUrlDataFromIntent(intent);
464             if (urlData.isEmpty()) {
465                 urlData = new UrlData(mSettings.getHomePage());
466             }
467 
468             final String appId = intent
469                     .getStringExtra(Browser.EXTRA_APPLICATION_ID);
470             if (!TextUtils.isEmpty(urlData.mUrl) &&
471                     urlData.mUrl.startsWith("javascript:")) {
472                 // Always open javascript: URIs in new tabs
473                 openTabAndShow(urlData, true, appId);
474                 return;
475             }
476             if ((Intent.ACTION_VIEW.equals(action)
477                     // If a voice search has no appId, it means that it came
478                     // from the browser.  In that case, reuse the current tab.
479                     || (activateVoiceSearch && appId != null))
480                     && !getPackageName().equals(appId)
481                     && (flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
482                 Tab appTab = mTabControl.getTabFromId(appId);
483                 if (appTab != null) {
484                     Log.i(LOGTAG, "Reusing tab for " + appId);
485                     // Dismiss the subwindow if applicable.
486                     dismissSubWindow(appTab);
487                     // Since we might kill the WebView, remove it from the
488                     // content view first.
489                     removeTabFromContentView(appTab);
490                     // Recreate the main WebView after destroying the old one.
491                     // If the WebView has the same original url and is on that
492                     // page, it can be reused.
493                     boolean needsLoad =
494                             mTabControl.recreateWebView(appTab, urlData);
495 
496                     if (current != appTab) {
497                         switchToTab(mTabControl.getTabIndex(appTab));
498                         if (needsLoad) {
499                             loadUrlDataIn(appTab, urlData);
500                         }
501                     } else {
502                         // If the tab was the current tab, we have to attach
503                         // it to the view system again.
504                         attachTabToContentView(appTab);
505                         if (needsLoad) {
506                             loadUrlDataIn(appTab, urlData);
507                         }
508                     }
509                     return;
510                 } else {
511                     // No matching application tab, try to find a regular tab
512                     // with a matching url.
513                     appTab = mTabControl.findUnusedTabWithUrl(urlData.mUrl);
514                     if (appTab != null) {
515                         if (current != appTab) {
516                             switchToTab(mTabControl.getTabIndex(appTab));
517                         }
518                         // Otherwise, we are already viewing the correct tab.
519                     } else {
520                         // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url
521                         // will be opened in a new tab unless we have reached
522                         // MAX_TABS. Then the url will be opened in the current
523                         // tab. If a new tab is created, it will have "true" for
524                         // exit on close.
525                         openTabAndShow(urlData, true, appId);
526                     }
527                 }
528             } else {
529                 if (!urlData.isEmpty()
530                         && urlData.mUrl.startsWith("about:debug")) {
531                     if ("about:debug.dom".equals(urlData.mUrl)) {
532                         current.getWebView().dumpDomTree(false);
533                     } else if ("about:debug.dom.file".equals(urlData.mUrl)) {
534                         current.getWebView().dumpDomTree(true);
535                     } else if ("about:debug.render".equals(urlData.mUrl)) {
536                         current.getWebView().dumpRenderTree(false);
537                     } else if ("about:debug.render.file".equals(urlData.mUrl)) {
538                         current.getWebView().dumpRenderTree(true);
539                     } else if ("about:debug.display".equals(urlData.mUrl)) {
540                         current.getWebView().dumpDisplayTree();
541                     } else if (urlData.mUrl.startsWith("about:debug.drag")) {
542                         int index = urlData.mUrl.codePointAt(16) - '0';
543                         if (index <= 0 || index > 9) {
544                             current.getWebView().setDragTracker(null);
545                         } else {
546                             current.getWebView().setDragTracker(new MeshTracker(index));
547                         }
548                     } else {
549                         mSettings.toggleDebugSettings();
550                     }
551                     return;
552                 }
553                 // Get rid of the subwindow if it exists
554                 dismissSubWindow(current);
555                 // If the current Tab is being used as an application tab,
556                 // remove the association, since the new Intent means that it is
557                 // no longer associated with that application.
558                 current.setAppId(null);
559                 loadUrlDataIn(current, urlData);
560             }
561         }
562     }
563 
564     /**
565      * Launches the default web search activity with the query parameters if the given intent's data
566      * are identified as plain search terms and not URLs/shortcuts.
567      * @return true if the intent was handled and web search activity was launched, false if not.
568      */
handleWebSearchIntent(Intent intent)569     private boolean handleWebSearchIntent(Intent intent) {
570         if (intent == null) return false;
571 
572         String url = null;
573         final String action = intent.getAction();
574         if (RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS.equals(
575                 action)) {
576             return false;
577         }
578         if (Intent.ACTION_VIEW.equals(action)) {
579             Uri data = intent.getData();
580             if (data != null) url = data.toString();
581         } else if (Intent.ACTION_SEARCH.equals(action)
582                 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
583                 || Intent.ACTION_WEB_SEARCH.equals(action)) {
584             url = intent.getStringExtra(SearchManager.QUERY);
585         }
586         return handleWebSearchRequest(url, intent.getBundleExtra(SearchManager.APP_DATA),
587                 intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));
588     }
589 
590     /**
591      * Launches the default web search activity with the query parameters if the given url string
592      * was identified as plain search terms and not URL/shortcut.
593      * @return true if the request was handled and web search activity was launched, false if not.
594      */
handleWebSearchRequest(String inUrl, Bundle appData, String extraData)595     private boolean handleWebSearchRequest(String inUrl, Bundle appData, String extraData) {
596         if (inUrl == null) return false;
597 
598         // In general, we shouldn't modify URL from Intent.
599         // But currently, we get the user-typed URL from search box as well.
600         String url = fixUrl(inUrl).trim();
601 
602         // URLs are handled by the regular flow of control, so
603         // return early.
604         if (Patterns.WEB_URL.matcher(url).matches()
605                 || ACCEPTED_URI_SCHEMA.matcher(url).matches()) {
606             return false;
607         }
608 
609         final ContentResolver cr = mResolver;
610         final String newUrl = url;
611         new AsyncTask<Void, Void, Void>() {
612             protected Void doInBackground(Void... unused) {
613                 Browser.updateVisitedHistory(cr, newUrl, false);
614                 Browser.addSearchUrl(cr, newUrl);
615                 return null;
616             }
617         }.execute();
618 
619         SearchEngine searchEngine = mSettings.getSearchEngine();
620         if (searchEngine == null) return false;
621         searchEngine.startSearch(this, url, appData, extraData);
622 
623         return true;
624     }
625 
getUrlDataFromIntent(Intent intent)626     private UrlData getUrlDataFromIntent(Intent intent) {
627         String url = "";
628         Map<String, String> headers = null;
629         if (intent != null) {
630             final String action = intent.getAction();
631             if (Intent.ACTION_VIEW.equals(action)) {
632                 url = smartUrlFilter(intent.getData());
633                 if (url != null && url.startsWith("http")) {
634                     final Bundle pairs = intent
635                             .getBundleExtra(Browser.EXTRA_HEADERS);
636                     if (pairs != null && !pairs.isEmpty()) {
637                         Iterator<String> iter = pairs.keySet().iterator();
638                         headers = new HashMap<String, String>();
639                         while (iter.hasNext()) {
640                             String key = iter.next();
641                             headers.put(key, pairs.getString(key));
642                         }
643                     }
644 
645                     // AppId will be set to the Browser for Search Bar initiated searches
646                     final String appId = intent.getStringExtra(Browser.EXTRA_APPLICATION_ID);
647                     if (getPackageName().equals(appId)) {
648                         String rlz = mSettings.getRlzValue();
649                         Uri uri = Uri.parse(url);
650                         if (!rlz.isEmpty() && needsRlz(uri)) {
651                             Uri rlzUri = addRlzParameter(uri, rlz);
652                             url = rlzUri.toString();
653                         }
654                     }
655                 }
656             } else if (Intent.ACTION_SEARCH.equals(action)
657                     || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
658                     || Intent.ACTION_WEB_SEARCH.equals(action)) {
659                 url = intent.getStringExtra(SearchManager.QUERY);
660                 if (url != null) {
661                     mLastEnteredUrl = url;
662                     // In general, we shouldn't modify URL from Intent.
663                     // But currently, we get the user-typed URL from search box as well.
664                     url = fixUrl(url);
665                     url = smartUrlFilter(url);
666                     final ContentResolver cr = mResolver;
667                     final String newUrl = url;
668                     new AsyncTask<Void, Void, Void>() {
669                         protected Void doInBackground(Void... unused) {
670                             Browser.updateVisitedHistory(cr, newUrl, false);
671                             return null;
672                         }
673                     }.execute();
674                     String searchSource = "&source=android-" + GOOGLE_SEARCH_SOURCE_SUGGEST + "&";
675                     if (url.contains(searchSource)) {
676                         String source = null;
677                         final Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA);
678                         if (appData != null) {
679                             source = appData.getString(Search.SOURCE);
680                         }
681                         if (TextUtils.isEmpty(source)) {
682                             source = GOOGLE_SEARCH_SOURCE_UNKNOWN;
683                         }
684                         url = url.replace(searchSource, "&source=android-"+source+"&");
685                     }
686                 }
687             }
688         }
689         return new UrlData(url, headers, intent);
690     }
showVoiceTitleBar(String title)691     /* package */ void showVoiceTitleBar(String title) {
692         mTitleBar.setInVoiceMode(true);
693         mFakeTitleBar.setInVoiceMode(true);
694 
695         mTitleBar.setDisplayTitle(title);
696         mFakeTitleBar.setDisplayTitle(title);
697     }
revertVoiceTitleBar()698     /* package */ void revertVoiceTitleBar() {
699         mTitleBar.setInVoiceMode(false);
700         mFakeTitleBar.setInVoiceMode(false);
701 
702         mTitleBar.setDisplayTitle(mUrl);
703         mFakeTitleBar.setDisplayTitle(mUrl);
704     }
fixUrl(String inUrl)705     /* package */ static String fixUrl(String inUrl) {
706         // FIXME: Converting the url to lower case
707         // duplicates functionality in smartUrlFilter().
708         // However, changing all current callers of fixUrl to
709         // call smartUrlFilter in addition may have unwanted
710         // consequences, and is deferred for now.
711         int colon = inUrl.indexOf(':');
712         boolean allLower = true;
713         for (int index = 0; index < colon; index++) {
714             char ch = inUrl.charAt(index);
715             if (!Character.isLetter(ch)) {
716                 break;
717             }
718             allLower &= Character.isLowerCase(ch);
719             if (index == colon - 1 && !allLower) {
720                 inUrl = inUrl.substring(0, colon).toLowerCase()
721                         + inUrl.substring(colon);
722             }
723         }
724         if (inUrl.startsWith("http://") || inUrl.startsWith("https://"))
725             return inUrl;
726         if (inUrl.startsWith("http:") ||
727                 inUrl.startsWith("https:")) {
728             if (inUrl.startsWith("http:/") || inUrl.startsWith("https:/")) {
729                 inUrl = inUrl.replaceFirst("/", "//");
730             } else inUrl = inUrl.replaceFirst(":", "://");
731         }
732         return inUrl;
733     }
734 
735     @Override
onResume()736     protected void onResume() {
737         super.onResume();
738         if (LOGV_ENABLED) {
739             Log.v(LOGTAG, "BrowserActivity.onResume: this=" + this);
740         }
741 
742         if (!mActivityInPause) {
743             Log.e(LOGTAG, "BrowserActivity is already resumed.");
744             return;
745         }
746 
747         mTabControl.resumeCurrentTab();
748         mActivityInPause = false;
749         resumeWebViewTimers();
750 
751         if (mWakeLock.isHeld()) {
752             mHandler.removeMessages(RELEASE_WAKELOCK);
753             mWakeLock.release();
754         }
755 
756         registerReceiver(mNetworkStateIntentReceiver,
757                          mNetworkStateChangedFilter);
758         WebView.enablePlatformNotifications();
759     }
760 
761     /**
762      * Since the actual title bar is embedded in the WebView, and removing it
763      * would change its appearance, use a different TitleBar to show overlayed
764      * at the top of the screen, when the menu is open or the page is loading.
765      */
766     private TitleBar mFakeTitleBar;
767 
768     /**
769      * Keeps track of whether the options menu is open.  This is important in
770      * determining whether to show or hide the title bar overlay.
771      */
772     private boolean mOptionsMenuOpen;
773 
774     /**
775      * Only meaningful when mOptionsMenuOpen is true.  This variable keeps track
776      * of whether the configuration has changed.  The first onMenuOpened call
777      * after a configuration change is simply a reopening of the same menu
778      * (i.e. mIconView did not change).
779      */
780     private boolean mConfigChanged;
781 
782     /**
783      * Whether or not the options menu is in its smaller, icon menu form.  When
784      * true, we want the title bar overlay to be up.  When false, we do not.
785      * Only meaningful if mOptionsMenuOpen is true.
786      */
787     private boolean mIconView;
788 
789     @Override
onMenuOpened(int featureId, Menu menu)790     public boolean onMenuOpened(int featureId, Menu menu) {
791         if (Window.FEATURE_OPTIONS_PANEL == featureId) {
792             if (mOptionsMenuOpen) {
793                 if (mConfigChanged) {
794                     // We do not need to make any changes to the state of the
795                     // title bar, since the only thing that happened was a
796                     // change in orientation
797                     mConfigChanged = false;
798                 } else {
799                     if (mIconView) {
800                         // Switching the menu to expanded view, so hide the
801                         // title bar.
802                         hideFakeTitleBar();
803                         mIconView = false;
804                     } else {
805                         // Switching the menu back to icon view, so show the
806                         // title bar once again.
807                         showFakeTitleBar();
808                         mIconView = true;
809                     }
810                 }
811             } else {
812                 // The options menu is closed, so open it, and show the title
813                 showFakeTitleBar();
814                 mOptionsMenuOpen = true;
815                 mConfigChanged = false;
816                 mIconView = true;
817             }
818         }
819         return true;
820     }
821 
showFakeTitleBar()822     private void showFakeTitleBar() {
823         if (mFakeTitleBar.getParent() == null && mActiveTabsPage == null
824                 && !mActivityInPause) {
825             WebView mainView = mTabControl.getCurrentWebView();
826             // if there is no current WebView, don't show the faked title bar;
827             if (mainView == null) {
828                 return;
829             }
830             // Do not need to check for null, since the current tab will have
831             // at least a main WebView, or we would have returned above.
832             if (dialogIsUp()) {
833                 // Do not show the fake title bar, which would cover up the
834                 // find or select dialog.
835                 return;
836             }
837 
838             WindowManager manager
839                     = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
840 
841             // Add the title bar to the window manager so it can receive touches
842             // while the menu is up
843             WindowManager.LayoutParams params
844                     = new WindowManager.LayoutParams(
845                     ViewGroup.LayoutParams.MATCH_PARENT,
846                     ViewGroup.LayoutParams.WRAP_CONTENT,
847                     WindowManager.LayoutParams.TYPE_APPLICATION,
848                     WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
849                     PixelFormat.TRANSLUCENT);
850             params.gravity = Gravity.TOP;
851             boolean atTop = mainView.getScrollY() == 0;
852             params.windowAnimations = atTop ? 0 : R.style.TitleBar;
853             manager.addView(mFakeTitleBar, params);
854         }
855     }
856 
857     @Override
onOptionsMenuClosed(Menu menu)858     public void onOptionsMenuClosed(Menu menu) {
859         mOptionsMenuOpen = false;
860         if (!mInLoad) {
861             hideFakeTitleBar();
862         } else if (!mIconView) {
863             // The page is currently loading, and we are in expanded mode, so
864             // we were not showing the menu.  Show it once again.  It will be
865             // removed when the page finishes.
866             showFakeTitleBar();
867         }
868     }
869 
hideFakeTitleBar()870     private void hideFakeTitleBar() {
871         if (mFakeTitleBar.getParent() == null) return;
872         WindowManager.LayoutParams params = (WindowManager.LayoutParams)
873                 mFakeTitleBar.getLayoutParams();
874         WebView mainView = mTabControl.getCurrentWebView();
875         // Although we decided whether or not to animate based on the current
876         // scroll position, the scroll position may have changed since the
877         // fake title bar was displayed.  Make sure it has the appropriate
878         // animation/lack thereof before removing.
879         params.windowAnimations = mainView != null && mainView.getScrollY() == 0
880                 ? 0 : R.style.TitleBar;
881         WindowManager manager
882                     = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
883         manager.updateViewLayout(mFakeTitleBar, params);
884         manager.removeView(mFakeTitleBar);
885     }
886 
887     /**
888      * Special method for the fake title bar to call when displaying its context
889      * menu, since it is in its own Window, and its parent does not show a
890      * context menu.
891      */
showTitleBarContextMenu()892     /* package */ void showTitleBarContextMenu() {
893         if (null == mTitleBar.getParent()) {
894             return;
895         }
896         openContextMenu(mTitleBar);
897     }
898 
899     @Override
onContextMenuClosed(Menu menu)900     public void onContextMenuClosed(Menu menu) {
901         super.onContextMenuClosed(menu);
902         if (mInLoad) {
903             showFakeTitleBar();
904         }
905     }
906 
907     /**
908      *  onSaveInstanceState(Bundle map)
909      *  onSaveInstanceState is called right before onStop(). The map contains
910      *  the saved state.
911      */
912     @Override
onSaveInstanceState(Bundle outState)913     protected void onSaveInstanceState(Bundle outState) {
914         if (LOGV_ENABLED) {
915             Log.v(LOGTAG, "BrowserActivity.onSaveInstanceState: this=" + this);
916         }
917         // the default implementation requires each view to have an id. As the
918         // browser handles the state itself and it doesn't use id for the views,
919         // don't call the default implementation. Otherwise it will trigger the
920         // warning like this, "couldn't save which view has focus because the
921         // focused view XXX has no id".
922 
923         // Save all the tabs
924         mTabControl.saveState(outState);
925     }
926 
927     @Override
onPause()928     protected void onPause() {
929         super.onPause();
930 
931         if (mActivityInPause) {
932             Log.e(LOGTAG, "BrowserActivity is already paused.");
933             return;
934         }
935 
936         mTabControl.pauseCurrentTab();
937         mActivityInPause = true;
938         if (mTabControl.getCurrentIndex() >= 0 && !pauseWebViewTimers()) {
939             mWakeLock.acquire();
940             mHandler.sendMessageDelayed(mHandler
941                     .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT);
942         }
943 
944         // FIXME: This removes the active tabs page and resets the menu to
945         // MAIN_MENU.  A better solution might be to do this work in onNewIntent
946         // but then we would need to save it in onSaveInstanceState and restore
947         // it in onCreate/onRestoreInstanceState
948         if (mActiveTabsPage != null) {
949             removeActiveTabPage(true);
950         }
951 
952         cancelStopToast();
953 
954         // unregister network state listener
955         unregisterReceiver(mNetworkStateIntentReceiver);
956         WebView.disablePlatformNotifications();
957 
958         if (mCustomView != null) {
959             mTabControl.getCurrentWebView().getWebChromeClient().onHideCustomView();
960         }
961     }
962 
963     @Override
onDestroy()964     protected void onDestroy() {
965         if (LOGV_ENABLED) {
966             Log.v(LOGTAG, "BrowserActivity.onDestroy: this=" + this);
967         }
968         super.onDestroy();
969 
970         if (mUploadMessage != null) {
971             mUploadMessage.onReceiveValue(null);
972             mUploadMessage = null;
973         }
974 
975         if (mTabControl == null) return;
976 
977         // Remove the fake title bar if it is there
978         hideFakeTitleBar();
979 
980         // Remove the current tab and sub window
981         Tab t = mTabControl.getCurrentTab();
982         if (t != null) {
983             dismissSubWindow(t);
984             removeTabFromContentView(t);
985         }
986         // Destroy all the tabs
987         mTabControl.destroy();
988         WebIconDatabase.getInstance().close();
989 
990         unregisterReceiver(mPackageInstallationReceiver);
991 
992         // Stop watching the default geolocation permissions
993         mSystemAllowGeolocationOrigins.stop();
994         mSystemAllowGeolocationOrigins = null;
995     }
996 
997     @Override
onConfigurationChanged(Configuration newConfig)998     public void onConfigurationChanged(Configuration newConfig) {
999         mConfigChanged = true;
1000         super.onConfigurationChanged(newConfig);
1001 
1002         if (mPageInfoDialog != null) {
1003             mPageInfoDialog.dismiss();
1004             showPageInfo(
1005                 mPageInfoView,
1006                 mPageInfoFromShowSSLCertificateOnError);
1007         }
1008         if (mSSLCertificateDialog != null) {
1009             mSSLCertificateDialog.dismiss();
1010             showSSLCertificate(
1011                 mSSLCertificateView);
1012         }
1013         if (mSSLCertificateOnErrorDialog != null) {
1014             mSSLCertificateOnErrorDialog.dismiss();
1015             showSSLCertificateOnError(
1016                 mSSLCertificateOnErrorView,
1017                 mSSLCertificateOnErrorHandler,
1018                 mSSLCertificateOnErrorError);
1019         }
1020         if (mHttpAuthenticationDialog != null) {
1021             String title = ((TextView) mHttpAuthenticationDialog
1022                     .findViewById(com.android.internal.R.id.alertTitle)).getText()
1023                     .toString();
1024             String name = ((TextView) mHttpAuthenticationDialog
1025                     .findViewById(R.id.username_edit)).getText().toString();
1026             String password = ((TextView) mHttpAuthenticationDialog
1027                     .findViewById(R.id.password_edit)).getText().toString();
1028             int focusId = mHttpAuthenticationDialog.getCurrentFocus()
1029                     .getId();
1030             mHttpAuthenticationDialog.dismiss();
1031             showHttpAuthentication(mHttpAuthHandler, null, null, title,
1032                     name, password, focusId);
1033         }
1034     }
1035 
1036     @Override
onLowMemory()1037     public void onLowMemory() {
1038         super.onLowMemory();
1039         mTabControl.freeMemory();
1040     }
1041 
resumeWebViewTimers()1042     private void resumeWebViewTimers() {
1043         Tab tab = mTabControl.getCurrentTab();
1044         if (tab == null) return; // monkey can trigger this
1045         boolean inLoad = tab.inLoad();
1046         if ((!mActivityInPause && !inLoad) || (mActivityInPause && inLoad)) {
1047             CookieSyncManager.getInstance().startSync();
1048             WebView w = tab.getWebView();
1049             if (w != null) {
1050                 w.resumeTimers();
1051             }
1052         }
1053     }
1054 
pauseWebViewTimers()1055     private boolean pauseWebViewTimers() {
1056         Tab tab = mTabControl.getCurrentTab();
1057         boolean inLoad = tab.inLoad();
1058         if (mActivityInPause && !inLoad) {
1059             CookieSyncManager.getInstance().stopSync();
1060             WebView w = mTabControl.getCurrentWebView();
1061             if (w != null) {
1062                 w.pauseTimers();
1063             }
1064             return true;
1065         } else {
1066             return false;
1067         }
1068     }
1069 
1070     // Open the icon database and retain all the icons for visited sites.
retainIconsOnStartup()1071     private void retainIconsOnStartup() {
1072         final WebIconDatabase db = WebIconDatabase.getInstance();
1073         db.open(getDir("icons", 0).getPath());
1074         Cursor c = null;
1075         try {
1076             c = Browser.getAllBookmarks(mResolver);
1077             if (c.moveToFirst()) {
1078                 int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL);
1079                 do {
1080                     String url = c.getString(urlIndex);
1081                     db.retainIconForPageUrl(url);
1082                 } while (c.moveToNext());
1083             }
1084         } catch (IllegalStateException e) {
1085             Log.e(LOGTAG, "retainIconsOnStartup", e);
1086         } finally {
1087             if (c!= null) c.close();
1088         }
1089     }
1090 
1091     // Helper method for getting the top window.
getTopWindow()1092     WebView getTopWindow() {
1093         return mTabControl.getCurrentTopWebView();
1094     }
1095 
getTabControl()1096     TabControl getTabControl() {
1097         return mTabControl;
1098     }
1099 
1100     @Override
onCreateOptionsMenu(Menu menu)1101     public boolean onCreateOptionsMenu(Menu menu) {
1102         super.onCreateOptionsMenu(menu);
1103 
1104         MenuInflater inflater = getMenuInflater();
1105         inflater.inflate(R.menu.browser, menu);
1106         mMenu = menu;
1107         updateInLoadMenuItems();
1108         return true;
1109     }
1110 
1111     /**
1112      * As the menu can be open when loading state changes
1113      * we must manually update the state of the stop/reload menu
1114      * item
1115      */
updateInLoadMenuItems()1116     private void updateInLoadMenuItems() {
1117         if (mMenu == null) {
1118             return;
1119         }
1120         MenuItem src = mInLoad ?
1121                 mMenu.findItem(R.id.stop_menu_id):
1122                     mMenu.findItem(R.id.reload_menu_id);
1123         MenuItem dest = mMenu.findItem(R.id.stop_reload_menu_id);
1124         dest.setIcon(src.getIcon());
1125         dest.setTitle(src.getTitle());
1126     }
1127 
1128     @Override
onContextItemSelected(MenuItem item)1129     public boolean onContextItemSelected(MenuItem item) {
1130         // chording is not an issue with context menus, but we use the same
1131         // options selector, so set mCanChord to true so we can access them.
1132         mCanChord = true;
1133         int id = item.getItemId();
1134         boolean result = true;
1135         switch (id) {
1136             // For the context menu from the title bar
1137             case R.id.title_bar_copy_page_url:
1138                 Tab currentTab = mTabControl.getCurrentTab();
1139                 if (null == currentTab) {
1140                     result = false;
1141                     break;
1142                 }
1143                 WebView mainView = currentTab.getWebView();
1144                 if (null == mainView) {
1145                     result = false;
1146                     break;
1147                 }
1148                 copy(mainView.getUrl());
1149                 break;
1150             // -- Browser context menu
1151             case R.id.open_context_menu_id:
1152             case R.id.open_newtab_context_menu_id:
1153             case R.id.bookmark_context_menu_id:
1154             case R.id.save_link_context_menu_id:
1155             case R.id.share_link_context_menu_id:
1156             case R.id.copy_link_context_menu_id:
1157                 final WebView webView = getTopWindow();
1158                 if (null == webView) {
1159                     result = false;
1160                     break;
1161                 }
1162                 final HashMap hrefMap = new HashMap();
1163                 hrefMap.put("webview", webView);
1164                 final Message msg = mHandler.obtainMessage(
1165                         FOCUS_NODE_HREF, id, 0, hrefMap);
1166                 webView.requestFocusNodeHref(msg);
1167                 break;
1168 
1169             default:
1170                 // For other context menus
1171                 result = onOptionsItemSelected(item);
1172         }
1173         mCanChord = false;
1174         return result;
1175     }
1176 
createGoogleSearchSourceBundle(String source)1177     private Bundle createGoogleSearchSourceBundle(String source) {
1178         Bundle bundle = new Bundle();
1179         bundle.putString(Search.SOURCE, source);
1180         return bundle;
1181     }
1182 
editUrl()1183     /* package */ void editUrl() {
1184         if (mOptionsMenuOpen) closeOptionsMenu();
1185         String url = (getTopWindow() == null) ? null : getTopWindow().getUrl();
1186         startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
1187                 null, false);
1188     }
1189 
1190     /**
1191      * Overriding this to insert a local information bundle
1192      */
1193     @Override
startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, boolean globalSearch)1194     public void startSearch(String initialQuery, boolean selectInitialQuery,
1195             Bundle appSearchData, boolean globalSearch) {
1196         if (appSearchData == null) {
1197             appSearchData = createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_TYPE);
1198         }
1199 
1200         SearchEngine searchEngine = mSettings.getSearchEngine();
1201         if (searchEngine != null && !searchEngine.supportsVoiceSearch()) {
1202             appSearchData.putBoolean(SearchManager.DISABLE_VOICE_SEARCH, true);
1203         }
1204 
1205         super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
1206     }
1207 
1208     /**
1209      * Switch tabs.  Called by the TitleBarSet when sliding the title bar
1210      * results in changing tabs.
1211      * @param index Index of the tab to change to, as defined by
1212      *              mTabControl.getTabIndex(Tab t).
1213      * @return boolean True if we successfully switched to a different tab.  If
1214      *                 the indexth tab is null, or if that tab is the same as
1215      *                 the current one, return false.
1216      */
switchToTab(int index)1217     /* package */ boolean switchToTab(int index) {
1218         Tab tab = mTabControl.getTab(index);
1219         Tab currentTab = mTabControl.getCurrentTab();
1220         if (tab == null || tab == currentTab) {
1221             return false;
1222         }
1223         if (currentTab != null) {
1224             // currentTab may be null if it was just removed.  In that case,
1225             // we do not need to remove it
1226             removeTabFromContentView(currentTab);
1227         }
1228         mTabControl.setCurrentTab(tab);
1229         attachTabToContentView(tab);
1230         resetTitleIconAndProgress();
1231         updateLockIconToLatest();
1232         return true;
1233     }
1234 
openTabToHomePage()1235     /* package */ Tab openTabToHomePage() {
1236         return openTabAndShow(mSettings.getHomePage(), false, null);
1237     }
1238 
closeCurrentWindow()1239     /* package */ void closeCurrentWindow() {
1240         final Tab current = mTabControl.getCurrentTab();
1241         if (mTabControl.getTabCount() == 1) {
1242             // This is the last tab.  Open a new one, with the home
1243             // page and close the current one.
1244             openTabToHomePage();
1245             closeTab(current);
1246             return;
1247         }
1248         final Tab parent = current.getParentTab();
1249         int indexToShow = -1;
1250         if (parent != null) {
1251             indexToShow = mTabControl.getTabIndex(parent);
1252         } else {
1253             final int currentIndex = mTabControl.getCurrentIndex();
1254             // Try to move to the tab to the right
1255             indexToShow = currentIndex + 1;
1256             if (indexToShow > mTabControl.getTabCount() - 1) {
1257                 // Try to move to the tab to the left
1258                 indexToShow = currentIndex - 1;
1259             }
1260         }
1261         if (switchToTab(indexToShow)) {
1262             // Close window
1263             closeTab(current);
1264         }
1265     }
1266 
1267     private ActiveTabsPage mActiveTabsPage;
1268 
1269     /**
1270      * Remove the active tabs page.
1271      * @param needToAttach If true, the active tabs page did not attach a tab
1272      *                     to the content view, so we need to do that here.
1273      */
removeActiveTabPage(boolean needToAttach)1274     /* package */ void removeActiveTabPage(boolean needToAttach) {
1275         mContentView.removeView(mActiveTabsPage);
1276         mActiveTabsPage = null;
1277         mMenuState = R.id.MAIN_MENU;
1278         if (needToAttach) {
1279             attachTabToContentView(mTabControl.getCurrentTab());
1280         }
1281         getTopWindow().requestFocus();
1282     }
1283 
showDialog(WebDialog dialog)1284     private WebView showDialog(WebDialog dialog) {
1285         Tab tab = mTabControl.getCurrentTab();
1286         if (tab.getSubWebView() == null) {
1287             // If the find or select is being performed on the main webview,
1288             // remove the embedded title bar.
1289             WebView mainView = tab.getWebView();
1290             if (mainView != null) {
1291                 mainView.setEmbeddedTitleBar(null);
1292             }
1293         }
1294         hideFakeTitleBar();
1295         mMenuState = EMPTY_MENU;
1296         return tab.showDialog(dialog);
1297     }
1298 
1299     @Override
onOptionsItemSelected(MenuItem item)1300     public boolean onOptionsItemSelected(MenuItem item) {
1301         if (!mCanChord) {
1302             // The user has already fired a shortcut with this hold down of the
1303             // menu key.
1304             return false;
1305         }
1306         if (null == getTopWindow()) {
1307             return false;
1308         }
1309         if (mMenuIsDown) {
1310             // The shortcut action consumes the MENU. Even if it is still down,
1311             // it won't trigger the next shortcut action. In the case of the
1312             // shortcut action triggering a new activity, like Bookmarks, we
1313             // won't get onKeyUp for MENU. So it is important to reset it here.
1314             mMenuIsDown = false;
1315         }
1316         switch (item.getItemId()) {
1317             // -- Main menu
1318             case R.id.new_tab_menu_id:
1319                 openTabToHomePage();
1320                 break;
1321 
1322             case R.id.goto_menu_id:
1323                 editUrl();
1324                 break;
1325 
1326             case R.id.bookmarks_menu_id:
1327                 bookmarksOrHistoryPicker(false);
1328                 break;
1329 
1330             case R.id.active_tabs_menu_id:
1331                 mActiveTabsPage = new ActiveTabsPage(this, mTabControl);
1332                 removeTabFromContentView(mTabControl.getCurrentTab());
1333                 hideFakeTitleBar();
1334                 mContentView.addView(mActiveTabsPage, COVER_SCREEN_PARAMS);
1335                 mActiveTabsPage.requestFocus();
1336                 mMenuState = EMPTY_MENU;
1337                 break;
1338 
1339             case R.id.add_bookmark_menu_id:
1340                 Intent i = new Intent(BrowserActivity.this,
1341                         AddBookmarkPage.class);
1342                 WebView w = getTopWindow();
1343                 i.putExtra("url", w.getUrl());
1344                 i.putExtra("title", w.getTitle());
1345                 i.putExtra("touch_icon_url", w.getTouchIconUrl());
1346                 i.putExtra("thumbnail", createScreenshot(w));
1347                 startActivity(i);
1348                 break;
1349 
1350             case R.id.stop_reload_menu_id:
1351                 if (mInLoad) {
1352                     stopLoading();
1353                 } else {
1354                     getTopWindow().reload();
1355                 }
1356                 break;
1357 
1358             case R.id.back_menu_id:
1359                 getTopWindow().goBack();
1360                 break;
1361 
1362             case R.id.forward_menu_id:
1363                 getTopWindow().goForward();
1364                 break;
1365 
1366             case R.id.close_menu_id:
1367                 // Close the subwindow if it exists.
1368                 if (mTabControl.getCurrentSubWindow() != null) {
1369                     dismissSubWindow(mTabControl.getCurrentTab());
1370                     break;
1371                 }
1372                 closeCurrentWindow();
1373                 break;
1374 
1375             case R.id.homepage_menu_id:
1376                 Tab current = mTabControl.getCurrentTab();
1377                 if (current != null) {
1378                     dismissSubWindow(current);
1379                     loadUrl(current.getWebView(), mSettings.getHomePage());
1380                 }
1381                 break;
1382 
1383             case R.id.preferences_menu_id:
1384                 Intent intent = new Intent(this,
1385                         BrowserPreferencesPage.class);
1386                 intent.putExtra(BrowserPreferencesPage.CURRENT_PAGE,
1387                         getTopWindow().getUrl());
1388                 startActivityForResult(intent, PREFERENCES_PAGE);
1389                 break;
1390 
1391             case R.id.find_menu_id:
1392                 showFindDialog();
1393                 break;
1394 
1395             case R.id.select_text_id:
1396                 if (true) {
1397                     Tab currentTab = mTabControl.getCurrentTab();
1398                     if (currentTab != null) {
1399                         currentTab.getWebView().setUpSelect();
1400                     }
1401                 } else {
1402                     showSelectDialog();
1403                 }
1404                 break;
1405 
1406             case R.id.page_info_menu_id:
1407                 showPageInfo(mTabControl.getCurrentTab(), false);
1408                 break;
1409 
1410             case R.id.classic_history_menu_id:
1411                 bookmarksOrHistoryPicker(true);
1412                 break;
1413 
1414             case R.id.title_bar_share_page_url:
1415             case R.id.share_page_menu_id:
1416                 Tab currentTab = mTabControl.getCurrentTab();
1417                 if (null == currentTab) {
1418                     mCanChord = false;
1419                     return false;
1420                 }
1421                 currentTab.populatePickerData();
1422                 sharePage(this, currentTab.getTitle(),
1423                         currentTab.getUrl(), currentTab.getFavicon(),
1424                         createScreenshot(currentTab.getWebView()));
1425                 break;
1426 
1427             case R.id.dump_nav_menu_id:
1428                 getTopWindow().debugDump();
1429                 break;
1430 
1431             case R.id.dump_counters_menu_id:
1432                 getTopWindow().dumpV8Counters();
1433                 break;
1434 
1435             case R.id.zoom_in_menu_id:
1436                 getTopWindow().zoomIn();
1437                 break;
1438 
1439             case R.id.zoom_out_menu_id:
1440                 getTopWindow().zoomOut();
1441                 break;
1442 
1443             case R.id.view_downloads_menu_id:
1444                 viewDownloads();
1445                 break;
1446 
1447             case R.id.window_one_menu_id:
1448             case R.id.window_two_menu_id:
1449             case R.id.window_three_menu_id:
1450             case R.id.window_four_menu_id:
1451             case R.id.window_five_menu_id:
1452             case R.id.window_six_menu_id:
1453             case R.id.window_seven_menu_id:
1454             case R.id.window_eight_menu_id:
1455                 {
1456                     int menuid = item.getItemId();
1457                     for (int id = 0; id < WINDOW_SHORTCUT_ID_ARRAY.length; id++) {
1458                         if (WINDOW_SHORTCUT_ID_ARRAY[id] == menuid) {
1459                             Tab desiredTab = mTabControl.getTab(id);
1460                             if (desiredTab != null &&
1461                                     desiredTab != mTabControl.getCurrentTab()) {
1462                                 switchToTab(id);
1463                             }
1464                             break;
1465                         }
1466                     }
1467                 }
1468                 break;
1469 
1470             default:
1471                 if (!super.onOptionsItemSelected(item)) {
1472                     return false;
1473                 }
1474                 // Otherwise fall through.
1475         }
1476         mCanChord = false;
1477         return true;
1478     }
1479 
dialogIsUp()1480     private boolean dialogIsUp() {
1481         return null != mFindDialog && mFindDialog.isVisible() ||
1482             null != mSelectDialog && mSelectDialog.isVisible();
1483     }
1484 
closeDialog(WebDialog dialog)1485     private boolean closeDialog(WebDialog dialog) {
1486         if (null == dialog || !dialog.isVisible()) return false;
1487         Tab currentTab = mTabControl.getCurrentTab();
1488         currentTab.closeDialog(dialog);
1489         dialog.dismiss();
1490         return true;
1491     }
1492 
1493     /*
1494      * Remove the find dialog or select dialog.
1495      */
closeDialogs()1496     public void closeDialogs() {
1497         if (!(closeDialog(mFindDialog) || closeDialog(mSelectDialog))) return;
1498         // If the Find was being performed in the main WebView, replace the
1499         // embedded title bar.
1500         Tab currentTab = mTabControl.getCurrentTab();
1501         if (currentTab.getSubWebView() == null) {
1502             WebView mainView = currentTab.getWebView();
1503             if (mainView != null) {
1504                 mainView.setEmbeddedTitleBar(mTitleBar);
1505             }
1506         }
1507         mMenuState = R.id.MAIN_MENU;
1508         if (mInLoad) {
1509             // The title bar was hidden, because otherwise it would cover up the
1510             // find or select dialog.  Now that the dialog has been removed,
1511             // show the fake title bar once again.
1512             showFakeTitleBar();
1513         }
1514     }
1515 
showFindDialog()1516     public void showFindDialog() {
1517         if (null == mFindDialog) {
1518             mFindDialog = new FindDialog(this);
1519         }
1520         showDialog(mFindDialog).setFindIsUp(true);
1521     }
1522 
setFindDialogText(String text)1523     public void setFindDialogText(String text) {
1524         mFindDialog.setText(text);
1525     }
1526 
showSelectDialog()1527     public void showSelectDialog() {
1528         if (null == mSelectDialog) {
1529             mSelectDialog = new SelectDialog(this);
1530         }
1531         showDialog(mSelectDialog).setUpSelect();
1532         mSelectDialog.hideSoftInput();
1533     }
1534 
1535     @Override
onPrepareOptionsMenu(Menu menu)1536     public boolean onPrepareOptionsMenu(Menu menu) {
1537         // This happens when the user begins to hold down the menu key, so
1538         // allow them to chord to get a shortcut.
1539         mCanChord = true;
1540         // Note: setVisible will decide whether an item is visible; while
1541         // setEnabled() will decide whether an item is enabled, which also means
1542         // whether the matching shortcut key will function.
1543         super.onPrepareOptionsMenu(menu);
1544         switch (mMenuState) {
1545             case EMPTY_MENU:
1546                 if (mCurrentMenuState != mMenuState) {
1547                     menu.setGroupVisible(R.id.MAIN_MENU, false);
1548                     menu.setGroupEnabled(R.id.MAIN_MENU, false);
1549                     menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false);
1550                 }
1551                 break;
1552             default:
1553                 if (mCurrentMenuState != mMenuState) {
1554                     menu.setGroupVisible(R.id.MAIN_MENU, true);
1555                     menu.setGroupEnabled(R.id.MAIN_MENU, true);
1556                     menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true);
1557                 }
1558                 final WebView w = getTopWindow();
1559                 boolean canGoBack = false;
1560                 boolean canGoForward = false;
1561                 boolean isHome = false;
1562                 if (w != null) {
1563                     canGoBack = w.canGoBack();
1564                     canGoForward = w.canGoForward();
1565                     isHome = mSettings.getHomePage().equals(w.getUrl());
1566                 }
1567                 final MenuItem back = menu.findItem(R.id.back_menu_id);
1568                 back.setEnabled(canGoBack);
1569 
1570                 final MenuItem home = menu.findItem(R.id.homepage_menu_id);
1571                 home.setEnabled(!isHome);
1572 
1573                 menu.findItem(R.id.forward_menu_id)
1574                         .setEnabled(canGoForward);
1575 
1576                 menu.findItem(R.id.new_tab_menu_id).setEnabled(
1577                         mTabControl.canCreateNewTab());
1578 
1579                 // decide whether to show the share link option
1580                 PackageManager pm = getPackageManager();
1581                 Intent send = new Intent(Intent.ACTION_SEND);
1582                 send.setType("text/plain");
1583                 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1584                 menu.findItem(R.id.share_page_menu_id).setVisible(ri != null);
1585 
1586                 boolean isNavDump = mSettings.isNavDump();
1587                 final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id);
1588                 nav.setVisible(isNavDump);
1589                 nav.setEnabled(isNavDump);
1590 
1591                 boolean showDebugSettings = mSettings.showDebugSettings();
1592                 final MenuItem counter = menu.findItem(R.id.dump_counters_menu_id);
1593                 counter.setVisible(showDebugSettings);
1594                 counter.setEnabled(showDebugSettings);
1595 
1596                 break;
1597         }
1598         mCurrentMenuState = mMenuState;
1599         return true;
1600     }
1601 
1602     @Override
onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo)1603     public void onCreateContextMenu(ContextMenu menu, View v,
1604             ContextMenuInfo menuInfo) {
1605         if (v instanceof TitleBar) {
1606             return;
1607         }
1608         WebView webview = (WebView) v;
1609         WebView.HitTestResult result = webview.getHitTestResult();
1610         if (result == null) {
1611             return;
1612         }
1613 
1614         int type = result.getType();
1615         if (type == WebView.HitTestResult.UNKNOWN_TYPE) {
1616             Log.w(LOGTAG,
1617                     "We should not show context menu when nothing is touched");
1618             return;
1619         }
1620         if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) {
1621             // let TextView handles context menu
1622             return;
1623         }
1624 
1625         // Note, http://b/issue?id=1106666 is requesting that
1626         // an inflated menu can be used again. This is not available
1627         // yet, so inflate each time (yuk!)
1628         MenuInflater inflater = getMenuInflater();
1629         inflater.inflate(R.menu.browsercontext, menu);
1630 
1631         // Show the correct menu group
1632         String extra = result.getExtra();
1633         menu.setGroupVisible(R.id.PHONE_MENU,
1634                 type == WebView.HitTestResult.PHONE_TYPE);
1635         menu.setGroupVisible(R.id.EMAIL_MENU,
1636                 type == WebView.HitTestResult.EMAIL_TYPE);
1637         menu.setGroupVisible(R.id.GEO_MENU,
1638                 type == WebView.HitTestResult.GEO_TYPE);
1639         menu.setGroupVisible(R.id.IMAGE_MENU,
1640                 type == WebView.HitTestResult.IMAGE_TYPE
1641                 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1642         menu.setGroupVisible(R.id.ANCHOR_MENU,
1643                 type == WebView.HitTestResult.SRC_ANCHOR_TYPE
1644                 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1645 
1646         // Setup custom handling depending on the type
1647         switch (type) {
1648             case WebView.HitTestResult.PHONE_TYPE:
1649                 menu.setHeaderTitle(Uri.decode(extra));
1650                 menu.findItem(R.id.dial_context_menu_id).setIntent(
1651                         new Intent(Intent.ACTION_VIEW, Uri
1652                                 .parse(WebView.SCHEME_TEL + extra)));
1653                 Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
1654                 addIntent.putExtra(Insert.PHONE, Uri.decode(extra));
1655                 addIntent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
1656                 menu.findItem(R.id.add_contact_context_menu_id).setIntent(
1657                         addIntent);
1658                 menu.findItem(R.id.copy_phone_context_menu_id).setOnMenuItemClickListener(
1659                         new Copy(extra));
1660                 break;
1661 
1662             case WebView.HitTestResult.EMAIL_TYPE:
1663                 menu.setHeaderTitle(extra);
1664                 menu.findItem(R.id.email_context_menu_id).setIntent(
1665                         new Intent(Intent.ACTION_VIEW, Uri
1666                                 .parse(WebView.SCHEME_MAILTO + extra)));
1667                 menu.findItem(R.id.copy_mail_context_menu_id).setOnMenuItemClickListener(
1668                         new Copy(extra));
1669                 break;
1670 
1671             case WebView.HitTestResult.GEO_TYPE:
1672                 menu.setHeaderTitle(extra);
1673                 menu.findItem(R.id.map_context_menu_id).setIntent(
1674                         new Intent(Intent.ACTION_VIEW, Uri
1675                                 .parse(WebView.SCHEME_GEO
1676                                         + URLEncoder.encode(extra))));
1677                 menu.findItem(R.id.copy_geo_context_menu_id).setOnMenuItemClickListener(
1678                         new Copy(extra));
1679                 break;
1680 
1681             case WebView.HitTestResult.SRC_ANCHOR_TYPE:
1682             case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
1683                 TextView titleView = (TextView) LayoutInflater.from(this)
1684                         .inflate(android.R.layout.browser_link_context_header,
1685                         null);
1686                 titleView.setText(extra);
1687                 menu.setHeaderView(titleView);
1688                 // decide whether to show the open link in new tab option
1689                 menu.findItem(R.id.open_newtab_context_menu_id).setVisible(
1690                         mTabControl.canCreateNewTab());
1691                 menu.findItem(R.id.bookmark_context_menu_id).setVisible(
1692                         Bookmarks.urlHasAcceptableScheme(extra));
1693                 PackageManager pm = getPackageManager();
1694                 Intent send = new Intent(Intent.ACTION_SEND);
1695                 send.setType("text/plain");
1696                 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1697                 menu.findItem(R.id.share_link_context_menu_id).setVisible(ri != null);
1698                 if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
1699                     break;
1700                 }
1701                 // otherwise fall through to handle image part
1702             case WebView.HitTestResult.IMAGE_TYPE:
1703                 if (type == WebView.HitTestResult.IMAGE_TYPE) {
1704                     menu.setHeaderTitle(extra);
1705                 }
1706                 menu.findItem(R.id.view_image_context_menu_id).setIntent(
1707                         new Intent(Intent.ACTION_VIEW, Uri.parse(extra)));
1708                 menu.findItem(R.id.download_context_menu_id).
1709                         setOnMenuItemClickListener(new Download(extra));
1710                 menu.findItem(R.id.set_wallpaper_context_menu_id).
1711                         setOnMenuItemClickListener(new SetAsWallpaper(extra));
1712                 break;
1713 
1714             default:
1715                 Log.w(LOGTAG, "We should not get here.");
1716                 break;
1717         }
1718         hideFakeTitleBar();
1719     }
1720 
1721     // Attach the given tab to the content view.
1722     // this should only be called for the current tab.
attachTabToContentView(Tab t)1723     private void attachTabToContentView(Tab t) {
1724         // Attach the container that contains the main WebView and any other UI
1725         // associated with the tab.
1726         t.attachTabToContentView(mContentView);
1727 
1728         if (mShouldShowErrorConsole) {
1729             ErrorConsoleView errorConsole = t.getErrorConsole(true);
1730             if (errorConsole.numberOfErrors() == 0) {
1731                 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
1732             } else {
1733                 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
1734             }
1735 
1736             mErrorConsoleContainer.addView(errorConsole,
1737                     new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
1738                                                   ViewGroup.LayoutParams.WRAP_CONTENT));
1739         }
1740 
1741         WebView view = t.getWebView();
1742         view.setEmbeddedTitleBar(mTitleBar);
1743         if (t.isInVoiceSearchMode()) {
1744             showVoiceTitleBar(t.getVoiceDisplayTitle());
1745         } else {
1746             revertVoiceTitleBar();
1747         }
1748         // Request focus on the top window.
1749         t.getTopWindow().requestFocus();
1750     }
1751 
1752     // Attach a sub window to the main WebView of the given tab.
attachSubWindow(Tab t)1753     void attachSubWindow(Tab t) {
1754         t.attachSubWindow(mContentView);
1755         getTopWindow().requestFocus();
1756     }
1757 
1758     // Remove the given tab from the content view.
removeTabFromContentView(Tab t)1759     private void removeTabFromContentView(Tab t) {
1760         // Remove the container that contains the main WebView.
1761         t.removeTabFromContentView(mContentView);
1762 
1763         ErrorConsoleView errorConsole = t.getErrorConsole(false);
1764         if (errorConsole != null) {
1765             mErrorConsoleContainer.removeView(errorConsole);
1766         }
1767 
1768         WebView view = t.getWebView();
1769         if (view != null) {
1770             view.setEmbeddedTitleBar(null);
1771         }
1772     }
1773 
1774     // Remove the sub window if it exists. Also called by TabControl when the
1775     // user clicks the 'X' to dismiss a sub window.
dismissSubWindow(Tab t)1776     /* package */ void dismissSubWindow(Tab t) {
1777         t.removeSubWindow(mContentView);
1778         // dismiss the subwindow. This will destroy the WebView.
1779         t.dismissSubWindow();
1780         getTopWindow().requestFocus();
1781     }
1782 
1783     // A wrapper function of {@link #openTabAndShow(UrlData, boolean, String)}
1784     // that accepts url as string.
openTabAndShow(String url, boolean closeOnExit, String appId)1785     private Tab openTabAndShow(String url, boolean closeOnExit, String appId) {
1786         return openTabAndShow(new UrlData(url), closeOnExit, appId);
1787     }
1788 
1789     // This method does a ton of stuff. It will attempt to create a new tab
1790     // if we haven't reached MAX_TABS. Otherwise it uses the current tab. If
1791     // url isn't null, it will load the given url.
openTabAndShow(UrlData urlData, boolean closeOnExit, String appId)1792     /* package */Tab openTabAndShow(UrlData urlData, boolean closeOnExit,
1793             String appId) {
1794         Tab currentTab = mTabControl.getCurrentTab();
1795         if (!mTabControl.canCreateNewTab()) {
1796             Tab closeTab = mTabControl.getTab(0);
1797             closeTab(closeTab);
1798             if (closeTab == currentTab) {
1799                 currentTab = null;
1800             }
1801         }
1802         final Tab tab = mTabControl.createNewTab(closeOnExit, appId,
1803                 urlData.mUrl);
1804         WebView webview = tab.getWebView();
1805         // If the last tab was removed from the active tabs page, currentTab
1806         // will be null.
1807         if (currentTab != null) {
1808             removeTabFromContentView(currentTab);
1809         }
1810         // We must set the new tab as the current tab to reflect the old
1811         // animation behavior.
1812         mTabControl.setCurrentTab(tab);
1813         attachTabToContentView(tab);
1814         if (!urlData.isEmpty()) {
1815             loadUrlDataIn(tab, urlData);
1816         }
1817         return tab;
1818     }
1819 
openTab(String url)1820     private Tab openTab(String url) {
1821         if (mSettings.openInBackground()) {
1822             Tab t = mTabControl.createNewTab();
1823             if (t != null) {
1824                 WebView view = t.getWebView();
1825                 loadUrl(view, url);
1826             }
1827             return t;
1828         } else {
1829             return openTabAndShow(url, false, null);
1830         }
1831     }
1832 
1833     private class Copy implements OnMenuItemClickListener {
1834         private CharSequence mText;
1835 
onMenuItemClick(MenuItem item)1836         public boolean onMenuItemClick(MenuItem item) {
1837             copy(mText);
1838             return true;
1839         }
1840 
Copy(CharSequence toCopy)1841         public Copy(CharSequence toCopy) {
1842             mText = toCopy;
1843         }
1844     }
1845 
1846     private class Download implements OnMenuItemClickListener {
1847         private String mText;
1848 
onMenuItemClick(MenuItem item)1849         public boolean onMenuItemClick(MenuItem item) {
1850             onDownloadStartNoStream(mText, null, null, null, -1);
1851             return true;
1852         }
1853 
Download(String toDownload)1854         public Download(String toDownload) {
1855             mText = toDownload;
1856         }
1857     }
1858 
1859     private class SetAsWallpaper extends Thread implements
1860             OnMenuItemClickListener, DialogInterface.OnCancelListener {
1861         private URL mUrl;
1862         private ProgressDialog mWallpaperProgress;
1863         private boolean mCanceled = false;
1864 
SetAsWallpaper(String url)1865         public SetAsWallpaper(String url) {
1866             try {
1867                 mUrl = new URL(url);
1868             } catch (MalformedURLException e) {
1869                 mUrl = null;
1870             }
1871         }
1872 
onCancel(DialogInterface dialog)1873         public void onCancel(DialogInterface dialog) {
1874             mCanceled = true;
1875         }
1876 
onMenuItemClick(MenuItem item)1877         public boolean onMenuItemClick(MenuItem item) {
1878             if (mUrl != null) {
1879                 // The user may have tried to set a image with a large file size as their
1880                 // background so it may take a few moments to perform the operation. Display
1881                 // a progress spinner while it is working.
1882                 mWallpaperProgress = new ProgressDialog(BrowserActivity.this);
1883                 mWallpaperProgress.setIndeterminate(true);
1884                 mWallpaperProgress.setMessage(getText(R.string.progress_dialog_setting_wallpaper));
1885                 mWallpaperProgress.setCancelable(true);
1886                 mWallpaperProgress.setOnCancelListener(this);
1887                 mWallpaperProgress.show();
1888                 start();
1889             }
1890             return true;
1891         }
1892 
run()1893         public void run() {
1894             Drawable oldWallpaper = BrowserActivity.this.getWallpaper();
1895             try {
1896                 // TODO: This will cause the resource to be downloaded again, when we
1897                 // should in most cases be able to grab it from the cache. To fix this
1898                 // we should query WebCore to see if we can access a cached version and
1899                 // instead open an input stream on that. This pattern could also be used
1900                 // in the download manager where the same problem exists.
1901                 InputStream inputstream = mUrl.openStream();
1902                 if (inputstream != null) {
1903                     setWallpaper(inputstream);
1904                 }
1905             } catch (IOException e) {
1906                 Log.e(LOGTAG, "Unable to set new wallpaper");
1907                 // Act as though the user canceled the operation so we try to
1908                 // restore the old wallpaper.
1909                 mCanceled = true;
1910             }
1911 
1912             if (mCanceled) {
1913                 // Restore the old wallpaper if the user cancelled whilst we were setting
1914                 // the new wallpaper.
1915                 int width = oldWallpaper.getIntrinsicWidth();
1916                 int height = oldWallpaper.getIntrinsicHeight();
1917                 Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
1918                 Canvas canvas = new Canvas(bm);
1919                 oldWallpaper.setBounds(0, 0, width, height);
1920                 oldWallpaper.draw(canvas);
1921                 try {
1922                     setWallpaper(bm);
1923                 } catch (IOException e) {
1924                     Log.e(LOGTAG, "Unable to restore old wallpaper.");
1925                 }
1926                 mCanceled = false;
1927             }
1928 
1929             if (mWallpaperProgress.isShowing()) {
1930                 mWallpaperProgress.dismiss();
1931             }
1932         }
1933     }
1934 
copy(CharSequence text)1935     private void copy(CharSequence text) {
1936         try {
1937             IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
1938             if (clip != null) {
1939                 clip.setClipboardText(text);
1940             }
1941         } catch (android.os.RemoteException e) {
1942             Log.e(LOGTAG, "Copy failed", e);
1943         }
1944     }
1945 
1946     /**
1947      * Resets the browser title-view to whatever it must be
1948      * (for example, if we had a loading error)
1949      * When we have a new page, we call resetTitle, when we
1950      * have to reset the titlebar to whatever it used to be
1951      * (for example, if the user chose to stop loading), we
1952      * call resetTitleAndRevertLockIcon.
1953      */
resetTitleAndRevertLockIcon()1954     /* package */ void resetTitleAndRevertLockIcon() {
1955         mTabControl.getCurrentTab().revertLockIcon();
1956         updateLockIconToLatest();
1957         resetTitleIconAndProgress();
1958     }
1959 
1960     /**
1961      * Reset the title, favicon, and progress.
1962      */
resetTitleIconAndProgress()1963     private void resetTitleIconAndProgress() {
1964         WebView current = mTabControl.getCurrentWebView();
1965         if (current == null) {
1966             return;
1967         }
1968         resetTitleAndIcon(current);
1969         int progress = current.getProgress();
1970         current.getWebChromeClient().onProgressChanged(current, progress);
1971     }
1972 
1973     // Reset the title and the icon based on the given item.
resetTitleAndIcon(WebView view)1974     private void resetTitleAndIcon(WebView view) {
1975         WebHistoryItem item = view.copyBackForwardList().getCurrentItem();
1976         if (item != null) {
1977             setUrlTitle(item.getUrl(), item.getTitle());
1978             setFavicon(item.getFavicon());
1979         } else {
1980             setUrlTitle(null, null);
1981             setFavicon(null);
1982         }
1983     }
1984 
1985     /**
1986      * Sets a title composed of the URL and the title string.
1987      * @param url The URL of the site being loaded.
1988      * @param title The title of the site being loaded.
1989      */
setUrlTitle(String url, String title)1990     void setUrlTitle(String url, String title) {
1991         mUrl = url;
1992         mTitle = title;
1993 
1994         // If we are in voice search mode, the title has already been set.
1995         if (mTabControl.getCurrentTab().isInVoiceSearchMode()) return;
1996         mTitleBar.setDisplayTitle(url);
1997         mFakeTitleBar.setDisplayTitle(url);
1998     }
1999 
2000     /**
2001      * @param url The URL to build a title version of the URL from.
2002      * @return The title version of the URL or null if fails.
2003      * The title version of the URL can be either the URL hostname,
2004      * or the hostname with an "https://" prefix (for secure URLs),
2005      * or an empty string if, for example, the URL in question is a
2006      * file:// URL with no hostname.
2007      */
buildTitleUrl(String url)2008     /* package */ static String buildTitleUrl(String url) {
2009         String titleUrl = null;
2010 
2011         if (url != null) {
2012             try {
2013                 // parse the url string
2014                 URL urlObj = new URL(url);
2015                 if (urlObj != null) {
2016                     titleUrl = "";
2017 
2018                     String protocol = urlObj.getProtocol();
2019                     String host = urlObj.getHost();
2020 
2021                     if (host != null && 0 < host.length()) {
2022                         titleUrl = host;
2023                         if (protocol != null) {
2024                             // if a secure site, add an "https://" prefix!
2025                             if (protocol.equalsIgnoreCase("https")) {
2026                                 titleUrl = protocol + "://" + host;
2027                             }
2028                         }
2029                     }
2030                 }
2031             } catch (MalformedURLException e) {}
2032         }
2033 
2034         return titleUrl;
2035     }
2036 
2037     // Set the favicon in the title bar.
setFavicon(Bitmap icon)2038     void setFavicon(Bitmap icon) {
2039         mTitleBar.setFavicon(icon);
2040         mFakeTitleBar.setFavicon(icon);
2041     }
2042 
2043     /**
2044      * Close the tab, remove its associated title bar, and adjust mTabControl's
2045      * current tab to a valid value.
2046      */
closeTab(Tab t)2047     /* package */ void closeTab(Tab t) {
2048         int currentIndex = mTabControl.getCurrentIndex();
2049         int removeIndex = mTabControl.getTabIndex(t);
2050         mTabControl.removeTab(t);
2051         if (currentIndex >= removeIndex && currentIndex != 0) {
2052             currentIndex--;
2053         }
2054         mTabControl.setCurrentTab(mTabControl.getTab(currentIndex));
2055         resetTitleIconAndProgress();
2056     }
2057 
goBackOnePageOrQuit()2058     /* package */ void goBackOnePageOrQuit() {
2059         Tab current = mTabControl.getCurrentTab();
2060         if (current == null) {
2061             /*
2062              * Instead of finishing the activity, simply push this to the back
2063              * of the stack and let ActivityManager to choose the foreground
2064              * activity. As BrowserActivity is singleTask, it will be always the
2065              * root of the task. So we can use either true or false for
2066              * moveTaskToBack().
2067              */
2068             moveTaskToBack(true);
2069             return;
2070         }
2071         WebView w = current.getWebView();
2072         if (w.canGoBack()) {
2073             w.goBack();
2074         } else {
2075             // Check to see if we are closing a window that was created by
2076             // another window. If so, we switch back to that window.
2077             Tab parent = current.getParentTab();
2078             if (parent != null) {
2079                 switchToTab(mTabControl.getTabIndex(parent));
2080                 // Now we close the other tab
2081                 closeTab(current);
2082             } else {
2083                 if (current.closeOnExit()) {
2084                     // force the tab's inLoad() to be false as we are going to
2085                     // either finish the activity or remove the tab. This will
2086                     // ensure pauseWebViewTimers() taking action.
2087                     mTabControl.getCurrentTab().clearInLoad();
2088                     if (mTabControl.getTabCount() == 1) {
2089                         finish();
2090                         return;
2091                     }
2092                     // call pauseWebViewTimers() now, we won't be able to call
2093                     // it in onPause() as the WebView won't be valid.
2094                     // Temporarily change mActivityInPause to be true as
2095                     // pauseWebViewTimers() will do nothing if mActivityInPause
2096                     // is false.
2097                     boolean savedState = mActivityInPause;
2098                     if (savedState) {
2099                         Log.e(LOGTAG, "BrowserActivity is already paused "
2100                                 + "while handing goBackOnePageOrQuit.");
2101                     }
2102                     mActivityInPause = true;
2103                     pauseWebViewTimers();
2104                     mActivityInPause = savedState;
2105                     removeTabFromContentView(current);
2106                     mTabControl.removeTab(current);
2107                 }
2108                 /*
2109                  * Instead of finishing the activity, simply push this to the back
2110                  * of the stack and let ActivityManager to choose the foreground
2111                  * activity. As BrowserActivity is singleTask, it will be always the
2112                  * root of the task. So we can use either true or false for
2113                  * moveTaskToBack().
2114                  */
2115                 moveTaskToBack(true);
2116             }
2117         }
2118     }
2119 
isMenuDown()2120     boolean isMenuDown() {
2121         return mMenuIsDown;
2122     }
2123 
2124     @Override
onKeyDown(int keyCode, KeyEvent event)2125     public boolean onKeyDown(int keyCode, KeyEvent event) {
2126         // Even if MENU is already held down, we need to call to super to open
2127         // the IME on long press.
2128         if (KeyEvent.KEYCODE_MENU == keyCode) {
2129             mMenuIsDown = true;
2130             return super.onKeyDown(keyCode, event);
2131         }
2132         // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is
2133         // still down, we don't want to trigger the search. Pretend to consume
2134         // the key and do nothing.
2135         if (mMenuIsDown) return true;
2136 
2137         switch(keyCode) {
2138             case KeyEvent.KEYCODE_SPACE:
2139                 // WebView/WebTextView handle the keys in the KeyDown. As
2140                 // the Activity's shortcut keys are only handled when WebView
2141                 // doesn't, have to do it in onKeyDown instead of onKeyUp.
2142                 if (event.isShiftPressed()) {
2143                     getTopWindow().pageUp(false);
2144                 } else {
2145                     getTopWindow().pageDown(false);
2146                 }
2147                 return true;
2148             case KeyEvent.KEYCODE_BACK:
2149                 if (event.getRepeatCount() == 0) {
2150                     event.startTracking();
2151                     return true;
2152                 } else if (mCustomView == null && mActiveTabsPage == null
2153                         && event.isLongPress()) {
2154                     bookmarksOrHistoryPicker(true);
2155                     return true;
2156                 }
2157                 break;
2158         }
2159         return super.onKeyDown(keyCode, event);
2160     }
2161 
2162     @Override
onKeyUp(int keyCode, KeyEvent event)2163     public boolean onKeyUp(int keyCode, KeyEvent event) {
2164         switch(keyCode) {
2165             case KeyEvent.KEYCODE_MENU:
2166                 mMenuIsDown = false;
2167                 break;
2168             case KeyEvent.KEYCODE_BACK:
2169                 if (event.isTracking() && !event.isCanceled()) {
2170                     if (mCustomView != null) {
2171                         // if a custom view is showing, hide it
2172                         mTabControl.getCurrentWebView().getWebChromeClient()
2173                                 .onHideCustomView();
2174                     } else if (mActiveTabsPage != null) {
2175                         // if tab page is showing, hide it
2176                         removeActiveTabPage(true);
2177                     } else {
2178                         WebView subwindow = mTabControl.getCurrentSubWindow();
2179                         if (subwindow != null) {
2180                             if (subwindow.canGoBack()) {
2181                                 subwindow.goBack();
2182                             } else {
2183                                 dismissSubWindow(mTabControl.getCurrentTab());
2184                             }
2185                         } else {
2186                             goBackOnePageOrQuit();
2187                         }
2188                     }
2189                     return true;
2190                 }
2191                 break;
2192         }
2193         return super.onKeyUp(keyCode, event);
2194     }
2195 
stopLoading()2196     /* package */ void stopLoading() {
2197         mDidStopLoad = true;
2198         resetTitleAndRevertLockIcon();
2199         WebView w = getTopWindow();
2200         w.stopLoading();
2201         // FIXME: before refactor, it is using mWebViewClient. So I keep the
2202         // same logic here. But for subwindow case, should we call into the main
2203         // WebView's onPageFinished as we never call its onPageStarted and if
2204         // the page finishes itself, we don't call onPageFinished.
2205         mTabControl.getCurrentWebView().getWebViewClient().onPageFinished(w,
2206                 w.getUrl());
2207 
2208         cancelStopToast();
2209         mStopToast = Toast
2210                 .makeText(this, R.string.stopping, Toast.LENGTH_SHORT);
2211         mStopToast.show();
2212     }
2213 
didUserStopLoading()2214     boolean didUserStopLoading() {
2215         return mDidStopLoad;
2216     }
2217 
cancelStopToast()2218     private void cancelStopToast() {
2219         if (mStopToast != null) {
2220             mStopToast.cancel();
2221             mStopToast = null;
2222         }
2223     }
2224 
2225     // called by a UI or non-UI thread to post the message
postMessage(int what, int arg1, int arg2, Object obj, long delayMillis)2226     public void postMessage(int what, int arg1, int arg2, Object obj,
2227             long delayMillis) {
2228         mHandler.sendMessageDelayed(mHandler.obtainMessage(what, arg1, arg2,
2229                 obj), delayMillis);
2230     }
2231 
2232     // called by a UI or non-UI thread to remove the message
removeMessages(int what, Object object)2233     void removeMessages(int what, Object object) {
2234         mHandler.removeMessages(what, object);
2235     }
2236 
2237     // public message ids
2238     public final static int LOAD_URL                = 1001;
2239     public final static int STOP_LOAD               = 1002;
2240 
2241     // Message Ids
2242     private static final int FOCUS_NODE_HREF         = 102;
2243     private static final int RELEASE_WAKELOCK        = 107;
2244 
2245     static final int UPDATE_BOOKMARK_THUMBNAIL       = 108;
2246 
2247     // Private handler for handling javascript and saving passwords
2248     private Handler mHandler = new Handler() {
2249 
2250         public void handleMessage(Message msg) {
2251             switch (msg.what) {
2252                 case FOCUS_NODE_HREF:
2253                 {
2254                     String url = (String) msg.getData().get("url");
2255                     String title = (String) msg.getData().get("title");
2256                     if (url == null || url.length() == 0) {
2257                         break;
2258                     }
2259                     HashMap focusNodeMap = (HashMap) msg.obj;
2260                     WebView view = (WebView) focusNodeMap.get("webview");
2261                     // Only apply the action if the top window did not change.
2262                     if (getTopWindow() != view) {
2263                         break;
2264                     }
2265                     switch (msg.arg1) {
2266                         case R.id.open_context_menu_id:
2267                         case R.id.view_image_context_menu_id:
2268                             loadUrlFromContext(getTopWindow(), url);
2269                             break;
2270                         case R.id.open_newtab_context_menu_id:
2271                             final Tab parent = mTabControl.getCurrentTab();
2272                             final Tab newTab = openTab(url);
2273                             if (newTab != parent) {
2274                                 parent.addChildTab(newTab);
2275                             }
2276                             break;
2277                         case R.id.bookmark_context_menu_id:
2278                             Intent intent = new Intent(BrowserActivity.this,
2279                                     AddBookmarkPage.class);
2280                             intent.putExtra("url", url);
2281                             intent.putExtra("title", title);
2282                             startActivity(intent);
2283                             break;
2284                         case R.id.share_link_context_menu_id:
2285                             // See if this site has been visited before
2286                             StringBuilder sb = new StringBuilder(
2287                                     Browser.BookmarkColumns.URL + " = ");
2288                             DatabaseUtils.appendEscapedSQLString(sb, url);
2289                             Cursor c = mResolver.query(Browser.BOOKMARKS_URI,
2290                                     Browser.HISTORY_PROJECTION,
2291                                     sb.toString(),
2292                                     null,
2293                                     null);
2294                             if (c.moveToFirst()) {
2295                                 // The site has been visited before, so grab the
2296                                 // info from the database.
2297                                 Bitmap favicon = null;
2298                                 Bitmap thumbnail = null;
2299                                 String linkTitle = c.getString(Browser.
2300                                         HISTORY_PROJECTION_TITLE_INDEX);
2301                                 byte[] data = c.getBlob(Browser.
2302                                         HISTORY_PROJECTION_FAVICON_INDEX);
2303                                 if (data != null) {
2304                                     favicon = BitmapFactory.decodeByteArray(
2305                                             data, 0, data.length);
2306                                 }
2307                                 data = c.getBlob(Browser.
2308                                         HISTORY_PROJECTION_THUMBNAIL_INDEX);
2309                                 if (data != null) {
2310                                     thumbnail = BitmapFactory.decodeByteArray(
2311                                             data, 0, data.length);
2312                                 }
2313                                 sharePage(BrowserActivity.this,
2314                                         linkTitle, url, favicon, thumbnail);
2315                             } else {
2316                                 Browser.sendString(BrowserActivity.this, url,
2317                                         getString(
2318                                         R.string.choosertitle_sharevia));
2319                             }
2320                             break;
2321                         case R.id.copy_link_context_menu_id:
2322                             copy(url);
2323                             break;
2324                         case R.id.save_link_context_menu_id:
2325                         case R.id.download_context_menu_id:
2326                             onDownloadStartNoStream(url, null, null, null, -1);
2327                             break;
2328                     }
2329                     break;
2330                 }
2331 
2332                 case LOAD_URL:
2333                     loadUrlFromContext(getTopWindow(), (String) msg.obj);
2334                     break;
2335 
2336                 case STOP_LOAD:
2337                     stopLoading();
2338                     break;
2339 
2340                 case RELEASE_WAKELOCK:
2341                     if (mWakeLock.isHeld()) {
2342                         mWakeLock.release();
2343                         // if we reach here, Browser should be still in the
2344                         // background loading after WAKELOCK_TIMEOUT (5-min).
2345                         // To avoid burning the battery, stop loading.
2346                         mTabControl.stopAllLoading();
2347                     }
2348                     break;
2349 
2350                 case UPDATE_BOOKMARK_THUMBNAIL:
2351                     WebView view = (WebView) msg.obj;
2352                     if (view != null) {
2353                         updateScreenshot(view);
2354                     }
2355                     break;
2356             }
2357         }
2358     };
2359 
2360     /**
2361      * Share a page, providing the title, url, favicon, and a screenshot.  Uses
2362      * an {@link Intent} to launch the Activity chooser.
2363      * @param c Context used to launch a new Activity.
2364      * @param title Title of the page.  Stored in the Intent with
2365      *          {@link Intent#EXTRA_SUBJECT}
2366      * @param url URL of the page.  Stored in the Intent with
2367      *          {@link Intent#EXTRA_TEXT}
2368      * @param favicon Bitmap of the favicon for the page.  Stored in the Intent
2369      *          with {@link Browser#EXTRA_SHARE_FAVICON}
2370      * @param screenshot Bitmap of a screenshot of the page.  Stored in the
2371      *          Intent with {@link Browser#EXTRA_SHARE_SCREENSHOT}
2372      */
sharePage(Context c, String title, String url, Bitmap favicon, Bitmap screenshot)2373     public static final void sharePage(Context c, String title, String url,
2374             Bitmap favicon, Bitmap screenshot) {
2375         Intent send = new Intent(Intent.ACTION_SEND);
2376         send.setType("text/plain");
2377         send.putExtra(Intent.EXTRA_TEXT, url);
2378         send.putExtra(Intent.EXTRA_SUBJECT, title);
2379         send.putExtra(Browser.EXTRA_SHARE_FAVICON, favicon);
2380         send.putExtra(Browser.EXTRA_SHARE_SCREENSHOT, screenshot);
2381         try {
2382             c.startActivity(Intent.createChooser(send, c.getString(
2383                     R.string.choosertitle_sharevia)));
2384         } catch(android.content.ActivityNotFoundException ex) {
2385             // if no app handles it, do nothing
2386         }
2387     }
2388 
updateScreenshot(WebView view)2389     private void updateScreenshot(WebView view) {
2390         // If this is a bookmarked site, add a screenshot to the database.
2391         // FIXME: When should we update?  Every time?
2392         // FIXME: Would like to make sure there is actually something to
2393         // draw, but the API for that (WebViewCore.pictureReady()) is not
2394         // currently accessible here.
2395 
2396         final Bitmap bm = createScreenshot(view);
2397         if (bm == null) {
2398             return;
2399         }
2400 
2401         final ContentResolver cr = getContentResolver();
2402         final String url = view.getUrl();
2403         final String originalUrl = view.getOriginalUrl();
2404 
2405         new AsyncTask<Void, Void, Void>() {
2406             @Override
2407             protected Void doInBackground(Void... unused) {
2408                 Cursor c = null;
2409                 try {
2410                     c = BrowserBookmarksAdapter.queryBookmarksForUrl(
2411                             cr, originalUrl, url, true);
2412                     if (c != null) {
2413                         if (c.moveToFirst()) {
2414                             ContentValues values = new ContentValues();
2415                             final ByteArrayOutputStream os
2416                                     = new ByteArrayOutputStream();
2417                             bm.compress(Bitmap.CompressFormat.PNG, 100, os);
2418                             values.put(Browser.BookmarkColumns.THUMBNAIL,
2419                                     os.toByteArray());
2420                             do {
2421                                 cr.update(ContentUris.withAppendedId(
2422                                         Browser.BOOKMARKS_URI, c.getInt(0)),
2423                                         values, null, null);
2424                             } while (c.moveToNext());
2425                         }
2426                     }
2427                 } catch (IllegalStateException e) {
2428                     // Ignore
2429                 } finally {
2430                     if (c != null) c.close();
2431                 }
2432                 return null;
2433             }
2434         }.execute();
2435     }
2436 
2437     /**
2438      * Values for the size of the thumbnail created when taking a screenshot.
2439      * Lazily initialized.  Instead of using these directly, use
2440      * getDesiredThumbnailWidth() or getDesiredThumbnailHeight().
2441      */
2442     private static int THUMBNAIL_WIDTH = 0;
2443     private static int THUMBNAIL_HEIGHT = 0;
2444 
2445     /**
2446      * Return the desired width for thumbnail screenshots, which are stored in
2447      * the database, and used on the bookmarks screen.
2448      * @param context Context for finding out the density of the screen.
2449      * @return int desired width for thumbnail screenshot.
2450      */
getDesiredThumbnailWidth(Context context)2451     /* package */ static int getDesiredThumbnailWidth(Context context) {
2452         if (THUMBNAIL_WIDTH == 0) {
2453             float density = context.getResources().getDisplayMetrics().density;
2454             THUMBNAIL_WIDTH = (int) (90 * density);
2455             THUMBNAIL_HEIGHT = (int) (80 * density);
2456         }
2457         return THUMBNAIL_WIDTH;
2458     }
2459 
2460     /**
2461      * Return the desired height for thumbnail screenshots, which are stored in
2462      * the database, and used on the bookmarks screen.
2463      * @param context Context for finding out the density of the screen.
2464      * @return int desired height for thumbnail screenshot.
2465      */
getDesiredThumbnailHeight(Context context)2466     /* package */ static int getDesiredThumbnailHeight(Context context) {
2467         // To ensure that they are both initialized.
2468         getDesiredThumbnailWidth(context);
2469         return THUMBNAIL_HEIGHT;
2470     }
2471 
createScreenshot(WebView view)2472     private Bitmap createScreenshot(WebView view) {
2473         Picture thumbnail = view.capturePicture();
2474         if (thumbnail == null) {
2475             return null;
2476         }
2477         Bitmap bm = Bitmap.createBitmap(getDesiredThumbnailWidth(this),
2478                 getDesiredThumbnailHeight(this), Bitmap.Config.RGB_565);
2479         Canvas canvas = new Canvas(bm);
2480         // May need to tweak these values to determine what is the
2481         // best scale factor
2482         int thumbnailWidth = thumbnail.getWidth();
2483         int thumbnailHeight = thumbnail.getHeight();
2484         float scaleFactorX = 1.0f;
2485         float scaleFactorY = 1.0f;
2486         if (thumbnailWidth > 0) {
2487             scaleFactorX = (float) getDesiredThumbnailWidth(this) /
2488                     (float)thumbnailWidth;
2489         } else {
2490             return null;
2491         }
2492 
2493         if (view.getWidth() > view.getHeight() &&
2494                 thumbnailHeight < view.getHeight() && thumbnailHeight > 0) {
2495             // If the device is in landscape and the page is shorter
2496             // than the height of the view, stretch the thumbnail to fill the
2497             // space.
2498             scaleFactorY = (float) getDesiredThumbnailHeight(this) /
2499                     (float)thumbnailHeight;
2500         } else {
2501             // In the portrait case, this looks nice.
2502             scaleFactorY = scaleFactorX;
2503         }
2504 
2505         canvas.scale(scaleFactorX, scaleFactorY);
2506 
2507         thumbnail.draw(canvas);
2508         return bm;
2509     }
2510 
2511     // -------------------------------------------------------------------------
2512     // Helper function for WebViewClient.
2513     //-------------------------------------------------------------------------
2514 
2515     // Use in overrideUrlLoading
2516     /* package */ final static String SCHEME_WTAI = "wtai://wp/";
2517     /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;";
2518     /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;";
2519     /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;";
2520 
2521     // Keep this initial progress in sync with initialProgressValue (* 100)
2522     // in ProgressTracker.cpp
2523     private final static int INITIAL_PROGRESS = 10;
2524 
onPageStarted(WebView view, String url, Bitmap favicon)2525     void onPageStarted(WebView view, String url, Bitmap favicon) {
2526         // when BrowserActivity just starts, onPageStarted may be called before
2527         // onResume as it is triggered from onCreate. Call resumeWebViewTimers
2528         // to start the timer. As we won't switch tabs while an activity is in
2529         // pause state, we can ensure calling resume and pause in pair.
2530         if (mActivityInPause) resumeWebViewTimers();
2531 
2532         resetLockIcon(url);
2533         setUrlTitle(url, null);
2534         setFavicon(favicon);
2535         // Show some progress so that the user knows the page is beginning to
2536         // load
2537         onProgressChanged(view, INITIAL_PROGRESS);
2538         mDidStopLoad = false;
2539         if (!mIsNetworkUp) createAndShowNetworkDialog();
2540         closeDialogs();
2541         if (mSettings.isTracing()) {
2542             String host;
2543             try {
2544                 WebAddress uri = new WebAddress(url);
2545                 host = uri.mHost;
2546             } catch (android.net.ParseException ex) {
2547                 host = "browser";
2548             }
2549             host = host.replace('.', '_');
2550             host += ".trace";
2551             mInTrace = true;
2552             Debug.startMethodTracing(host, 20 * 1024 * 1024);
2553         }
2554 
2555         // Performance probe
2556         if (false) {
2557             mStart = SystemClock.uptimeMillis();
2558             mProcessStart = Process.getElapsedCpuTime();
2559             long[] sysCpu = new long[7];
2560             if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2561                     sysCpu, null)) {
2562                 mUserStart = sysCpu[0] + sysCpu[1];
2563                 mSystemStart = sysCpu[2];
2564                 mIdleStart = sysCpu[3];
2565                 mIrqStart = sysCpu[4] + sysCpu[5] + sysCpu[6];
2566             }
2567             mUiStart = SystemClock.currentThreadTimeMillis();
2568         }
2569     }
2570 
onPageFinished(WebView view, String url)2571     void onPageFinished(WebView view, String url) {
2572         // Reset the title and icon in case we stopped a provisional load.
2573         resetTitleAndIcon(view);
2574         // Update the lock icon image only once we are done loading
2575         updateLockIconToLatest();
2576         // pause the WebView timer and release the wake lock if it is finished
2577         // while BrowserActivity is in pause state.
2578         if (mActivityInPause && pauseWebViewTimers()) {
2579             if (mWakeLock.isHeld()) {
2580                 mHandler.removeMessages(RELEASE_WAKELOCK);
2581                 mWakeLock.release();
2582             }
2583         }
2584 
2585         // Performance probe
2586         if (false) {
2587             long[] sysCpu = new long[7];
2588             if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2589                     sysCpu, null)) {
2590                 String uiInfo = "UI thread used "
2591                         + (SystemClock.currentThreadTimeMillis() - mUiStart)
2592                         + " ms";
2593                 if (LOGD_ENABLED) {
2594                     Log.d(LOGTAG, uiInfo);
2595                 }
2596                 //The string that gets written to the log
2597                 String performanceString = "It took total "
2598                         + (SystemClock.uptimeMillis() - mStart)
2599                         + " ms clock time to load the page."
2600                         + "\nbrowser process used "
2601                         + (Process.getElapsedCpuTime() - mProcessStart)
2602                         + " ms, user processes used "
2603                         + (sysCpu[0] + sysCpu[1] - mUserStart) * 10
2604                         + " ms, kernel used "
2605                         + (sysCpu[2] - mSystemStart) * 10
2606                         + " ms, idle took " + (sysCpu[3] - mIdleStart) * 10
2607                         + " ms and irq took "
2608                         + (sysCpu[4] + sysCpu[5] + sysCpu[6] - mIrqStart)
2609                         * 10 + " ms, " + uiInfo;
2610                 if (LOGD_ENABLED) {
2611                     Log.d(LOGTAG, performanceString + "\nWebpage: " + url);
2612                 }
2613                 if (url != null) {
2614                     // strip the url to maintain consistency
2615                     String newUrl = new String(url);
2616                     if (newUrl.startsWith("http://www.")) {
2617                         newUrl = newUrl.substring(11);
2618                     } else if (newUrl.startsWith("http://")) {
2619                         newUrl = newUrl.substring(7);
2620                     } else if (newUrl.startsWith("https://www.")) {
2621                         newUrl = newUrl.substring(12);
2622                     } else if (newUrl.startsWith("https://")) {
2623                         newUrl = newUrl.substring(8);
2624                     }
2625                     if (LOGD_ENABLED) {
2626                         Log.d(LOGTAG, newUrl + " loaded");
2627                     }
2628                 }
2629             }
2630          }
2631 
2632         if (mInTrace) {
2633             mInTrace = false;
2634             Debug.stopMethodTracing();
2635         }
2636     }
2637 
shouldOverrideUrlLoading(WebView view, String url)2638     boolean shouldOverrideUrlLoading(WebView view, String url) {
2639         if (url.startsWith(SCHEME_WTAI)) {
2640             // wtai://wp/mc;number
2641             // number=string(phone-number)
2642             if (url.startsWith(SCHEME_WTAI_MC)) {
2643                 Intent intent = new Intent(Intent.ACTION_VIEW,
2644                         Uri.parse(WebView.SCHEME_TEL +
2645                         url.substring(SCHEME_WTAI_MC.length())));
2646                 startActivity(intent);
2647                 return true;
2648             }
2649             // wtai://wp/sd;dtmf
2650             // dtmf=string(dialstring)
2651             if (url.startsWith(SCHEME_WTAI_SD)) {
2652                 // TODO: only send when there is active voice connection
2653                 return false;
2654             }
2655             // wtai://wp/ap;number;name
2656             // number=string(phone-number)
2657             // name=string
2658             if (url.startsWith(SCHEME_WTAI_AP)) {
2659                 // TODO
2660                 return false;
2661             }
2662         }
2663 
2664         // The "about:" schemes are internal to the browser; don't want these to
2665         // be dispatched to other apps. Similarly, javascript: schemas are private
2666         // to the page
2667         if (url.startsWith("about:") || url.startsWith("javascript:")) {
2668             return false;
2669         }
2670 
2671         Intent intent;
2672         // perform generic parsing of the URI to turn it into an Intent.
2673         try {
2674             intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
2675         } catch (URISyntaxException ex) {
2676             Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage());
2677             return false;
2678         }
2679 
2680         // check whether the intent can be resolved. If not, we will see
2681         // whether we can download it from the Market.
2682         if (getPackageManager().resolveActivity(intent, 0) == null) {
2683             String packagename = intent.getPackage();
2684             if (packagename != null) {
2685                 intent = new Intent(Intent.ACTION_VIEW, Uri
2686                         .parse("market://search?q=pname:" + packagename));
2687                 intent.addCategory(Intent.CATEGORY_BROWSABLE);
2688                 startActivity(intent);
2689                 return true;
2690             } else {
2691                 return false;
2692             }
2693         }
2694 
2695         // sanitize the Intent, ensuring web pages can not bypass browser
2696         // security (only access to BROWSABLE activities).
2697         intent.addCategory(Intent.CATEGORY_BROWSABLE);
2698         intent.setComponent(null);
2699         try {
2700             if (startActivityIfNeeded(intent, -1)) {
2701                 return true;
2702             }
2703         } catch (ActivityNotFoundException ex) {
2704             // ignore the error. If no application can handle the URL,
2705             // eg about:blank, assume the browser can handle it.
2706         }
2707 
2708         if (mMenuIsDown) {
2709             openTab(url);
2710             closeOptionsMenu();
2711             return true;
2712         }
2713         return false;
2714     }
2715 
2716     // -------------------------------------------------------------------------
2717     // Helper function for WebChromeClient
2718     // -------------------------------------------------------------------------
2719 
onProgressChanged(WebView view, int newProgress)2720     void onProgressChanged(WebView view, int newProgress) {
2721         mFakeTitleBar.setProgress(newProgress);
2722 
2723         if (newProgress == 100) {
2724             // onProgressChanged() may continue to be called after the main
2725             // frame has finished loading, as any remaining sub frames continue
2726             // to load. We'll only get called once though with newProgress as
2727             // 100 when everything is loaded. (onPageFinished is called once
2728             // when the main frame completes loading regardless of the state of
2729             // any sub frames so calls to onProgressChanges may continue after
2730             // onPageFinished has executed)
2731             if (mInLoad) {
2732                 mInLoad = false;
2733                 updateInLoadMenuItems();
2734                 // If the options menu is open, leave the title bar
2735                 if (!mOptionsMenuOpen || !mIconView) {
2736                     hideFakeTitleBar();
2737                 }
2738             }
2739         } else {
2740             if (!mInLoad) {
2741                 // onPageFinished may have already been called but a subframe is
2742                 // still loading and updating the progress. Reset mInLoad and
2743                 // update the menu items.
2744                 mInLoad = true;
2745                 updateInLoadMenuItems();
2746             }
2747             // When the page first begins to load, the Activity may still be
2748             // paused, in which case showFakeTitleBar will do nothing.  Call
2749             // again as the page continues to load so that it will be shown.
2750             // (Calling it will the fake title bar is already showing will also
2751             // do nothing.
2752             if (!mOptionsMenuOpen || mIconView) {
2753                 // This page has begun to load, so show the title bar
2754                 showFakeTitleBar();
2755             }
2756         }
2757     }
2758 
onShowCustomView(View view, WebChromeClient.CustomViewCallback callback)2759     void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
2760         // if a view already exists then immediately terminate the new one
2761         if (mCustomView != null) {
2762             callback.onCustomViewHidden();
2763             return;
2764         }
2765 
2766         // Add the custom view to its container.
2767         mCustomViewContainer.addView(view, COVER_SCREEN_GRAVITY_CENTER);
2768         mCustomView = view;
2769         mCustomViewCallback = callback;
2770         // Save the menu state and set it to empty while the custom
2771         // view is showing.
2772         mOldMenuState = mMenuState;
2773         mMenuState = EMPTY_MENU;
2774         // Hide the content view.
2775         mContentView.setVisibility(View.GONE);
2776         // Finally show the custom view container.
2777         setStatusBarVisibility(false);
2778         mCustomViewContainer.setVisibility(View.VISIBLE);
2779         mCustomViewContainer.bringToFront();
2780     }
2781 
onHideCustomView()2782     void onHideCustomView() {
2783         if (mCustomView == null)
2784             return;
2785         // Hide the custom view.
2786         mCustomView.setVisibility(View.GONE);
2787         // Remove the custom view from its container.
2788         mCustomViewContainer.removeView(mCustomView);
2789         mCustomView = null;
2790         // Reset the old menu state.
2791         mMenuState = mOldMenuState;
2792         mOldMenuState = EMPTY_MENU;
2793         mCustomViewContainer.setVisibility(View.GONE);
2794         mCustomViewCallback.onCustomViewHidden();
2795         // Show the content view.
2796         setStatusBarVisibility(true);
2797         mContentView.setVisibility(View.VISIBLE);
2798     }
2799 
getDefaultVideoPoster()2800     Bitmap getDefaultVideoPoster() {
2801         if (mDefaultVideoPoster == null) {
2802             mDefaultVideoPoster = BitmapFactory.decodeResource(
2803                     getResources(), R.drawable.default_video_poster);
2804         }
2805         return mDefaultVideoPoster;
2806     }
2807 
getVideoLoadingProgressView()2808     View getVideoLoadingProgressView() {
2809         if (mVideoProgressView == null) {
2810             LayoutInflater inflater = LayoutInflater.from(BrowserActivity.this);
2811             mVideoProgressView = inflater.inflate(
2812                     R.layout.video_loading_progress, null);
2813         }
2814         return mVideoProgressView;
2815     }
2816 
2817     /*
2818      * The Object used to inform the WebView of the file to upload.
2819      */
2820     private ValueCallback<Uri> mUploadMessage;
2821 
openFileChooser(ValueCallback<Uri> uploadMsg)2822     void openFileChooser(ValueCallback<Uri> uploadMsg) {
2823         if (mUploadMessage != null) return;
2824         mUploadMessage = uploadMsg;
2825         Intent i = new Intent(Intent.ACTION_GET_CONTENT);
2826         i.addCategory(Intent.CATEGORY_OPENABLE);
2827         i.setType("*/*");
2828         BrowserActivity.this.startActivityForResult(Intent.createChooser(i,
2829                 getString(R.string.choose_upload)), FILE_SELECTED);
2830     }
2831 
2832     // -------------------------------------------------------------------------
2833     // Implement functions for DownloadListener
2834     // -------------------------------------------------------------------------
2835 
2836     /**
2837      * Notify the host application a download should be done, or that
2838      * the data should be streamed if a streaming viewer is available.
2839      * @param url The full url to the content that should be downloaded
2840      * @param contentDisposition Content-disposition http header, if
2841      *                           present.
2842      * @param mimetype The mimetype of the content reported by the server
2843      * @param contentLength The file size reported by the server
2844      */
onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength)2845     public void onDownloadStart(String url, String userAgent,
2846             String contentDisposition, String mimetype, long contentLength) {
2847         // if we're dealing wih A/V content that's not explicitly marked
2848         //     for download, check if it's streamable.
2849         if (contentDisposition == null
2850                 || !contentDisposition.regionMatches(
2851                         true, 0, "attachment", 0, 10)) {
2852             // query the package manager to see if there's a registered handler
2853             //     that matches.
2854             Intent intent = new Intent(Intent.ACTION_VIEW);
2855             intent.setDataAndType(Uri.parse(url), mimetype);
2856             ResolveInfo info = getPackageManager().resolveActivity(intent,
2857                     PackageManager.MATCH_DEFAULT_ONLY);
2858             if (info != null) {
2859                 ComponentName myName = getComponentName();
2860                 // If we resolved to ourselves, we don't want to attempt to
2861                 // load the url only to try and download it again.
2862                 if (!myName.getPackageName().equals(
2863                         info.activityInfo.packageName)
2864                         || !myName.getClassName().equals(
2865                                 info.activityInfo.name)) {
2866                     // someone (other than us) knows how to handle this mime
2867                     // type with this scheme, don't download.
2868                     try {
2869                         startActivity(intent);
2870                         return;
2871                     } catch (ActivityNotFoundException ex) {
2872                         if (LOGD_ENABLED) {
2873                             Log.d(LOGTAG, "activity not found for " + mimetype
2874                                     + " over " + Uri.parse(url).getScheme(),
2875                                     ex);
2876                         }
2877                         // Best behavior is to fall back to a download in this
2878                         // case
2879                     }
2880                 }
2881             }
2882         }
2883         onDownloadStartNoStream(url, userAgent, contentDisposition, mimetype, contentLength);
2884     }
2885 
2886     // This is to work around the fact that java.net.URI throws Exceptions
2887     // instead of just encoding URL's properly
2888     // Helper method for onDownloadStartNoStream
encodePath(String path)2889     private static String encodePath(String path) {
2890         char[] chars = path.toCharArray();
2891 
2892         boolean needed = false;
2893         for (char c : chars) {
2894             if (c == '[' || c == ']') {
2895                 needed = true;
2896                 break;
2897             }
2898         }
2899         if (needed == false) {
2900             return path;
2901         }
2902 
2903         StringBuilder sb = new StringBuilder("");
2904         for (char c : chars) {
2905             if (c == '[' || c == ']') {
2906                 sb.append('%');
2907                 sb.append(Integer.toHexString(c));
2908             } else {
2909                 sb.append(c);
2910             }
2911         }
2912 
2913         return sb.toString();
2914     }
2915 
2916     /**
2917      * Notify the host application a download should be done, even if there
2918      * is a streaming viewer available for thise type.
2919      * @param url The full url to the content that should be downloaded
2920      * @param contentDisposition Content-disposition http header, if
2921      *                           present.
2922      * @param mimetype The mimetype of the content reported by the server
2923      * @param contentLength The file size reported by the server
2924      */
onDownloadStartNoStream(String url, String userAgent, String contentDisposition, String mimetype, long contentLength)2925     /*package */ void onDownloadStartNoStream(String url, String userAgent,
2926             String contentDisposition, String mimetype, long contentLength) {
2927 
2928         String filename = URLUtil.guessFileName(url,
2929                 contentDisposition, mimetype);
2930 
2931         // Check to see if we have an SDCard
2932         String status = Environment.getExternalStorageState();
2933         if (!status.equals(Environment.MEDIA_MOUNTED)) {
2934             int title;
2935             String msg;
2936 
2937             // Check to see if the SDCard is busy, same as the music app
2938             if (status.equals(Environment.MEDIA_SHARED)) {
2939                 msg = getString(R.string.download_sdcard_busy_dlg_msg);
2940                 title = R.string.download_sdcard_busy_dlg_title;
2941             } else {
2942                 msg = getString(R.string.download_no_sdcard_dlg_msg, filename);
2943                 title = R.string.download_no_sdcard_dlg_title;
2944             }
2945 
2946             new AlertDialog.Builder(this)
2947                 .setTitle(title)
2948                 .setIcon(android.R.drawable.ic_dialog_alert)
2949                 .setMessage(msg)
2950                 .setPositiveButton(R.string.ok, null)
2951                 .show();
2952             return;
2953         }
2954 
2955         // java.net.URI is a lot stricter than KURL so we have to encode some
2956         // extra characters. Fix for b 2538060 and b 1634719
2957         WebAddress webAddress;
2958         try {
2959             webAddress = new WebAddress(url);
2960             webAddress.mPath = encodePath(webAddress.mPath);
2961         } catch (Exception e) {
2962             // This only happens for very bad urls, we want to chatch the
2963             // exception here
2964             Log.e(LOGTAG, "Exception trying to parse url:" + url);
2965             return;
2966         }
2967 
2968         // XXX: Have to use the old url since the cookies were stored using the
2969         // old percent-encoded url.
2970         String cookies = CookieManager.getInstance().getCookie(url);
2971 
2972         ContentValues values = new ContentValues();
2973         values.put(Downloads.Impl.COLUMN_URI, webAddress.toString());
2974         values.put(Downloads.Impl.COLUMN_COOKIE_DATA, cookies);
2975         values.put(Downloads.Impl.COLUMN_USER_AGENT, userAgent);
2976         values.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE,
2977                 getPackageName());
2978         values.put(Downloads.Impl.COLUMN_NOTIFICATION_CLASS,
2979                 OpenDownloadReceiver.class.getCanonicalName());
2980         values.put(Downloads.Impl.COLUMN_VISIBILITY,
2981                 Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
2982         values.put(Downloads.Impl.COLUMN_MIME_TYPE, mimetype);
2983         values.put(Downloads.Impl.COLUMN_FILE_NAME_HINT, filename);
2984         values.put(Downloads.Impl.COLUMN_DESCRIPTION, webAddress.mHost);
2985         if (contentLength > 0) {
2986             values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, contentLength);
2987         }
2988         if (mimetype == null) {
2989             // We must have long pressed on a link or image to download it. We
2990             // are not sure of the mimetype in this case, so do a head request
2991             new FetchUrlMimeType(this).execute(values);
2992         } else {
2993             final Uri contentUri =
2994                     getContentResolver().insert(Downloads.Impl.CONTENT_URI, values);
2995         }
2996         Toast.makeText(this, R.string.download_pending, Toast.LENGTH_SHORT)
2997                 .show();
2998     }
2999 
3000     // -------------------------------------------------------------------------
3001 
3002     /**
3003      * Resets the lock icon. This method is called when we start a new load and
3004      * know the url to be loaded.
3005      */
resetLockIcon(String url)3006     private void resetLockIcon(String url) {
3007         // Save the lock-icon state (we revert to it if the load gets cancelled)
3008         mTabControl.getCurrentTab().resetLockIcon(url);
3009         updateLockIconImage(LOCK_ICON_UNSECURE);
3010     }
3011 
3012     /**
3013      * Update the lock icon to correspond to our latest state.
3014      */
updateLockIconToLatest()3015     private void updateLockIconToLatest() {
3016         updateLockIconImage(mTabControl.getCurrentTab().getLockIconType());
3017     }
3018 
3019     /**
3020      * Updates the lock-icon image in the title-bar.
3021      */
updateLockIconImage(int lockIconType)3022     private void updateLockIconImage(int lockIconType) {
3023         Drawable d = null;
3024         if (lockIconType == LOCK_ICON_SECURE) {
3025             d = mSecLockIcon;
3026         } else if (lockIconType == LOCK_ICON_MIXED) {
3027             d = mMixLockIcon;
3028         }
3029         mTitleBar.setLock(d);
3030         mFakeTitleBar.setLock(d);
3031     }
3032 
3033     /**
3034      * Displays a page-info dialog.
3035      * @param tab The tab to show info about
3036      * @param fromShowSSLCertificateOnError The flag that indicates whether
3037      * this dialog was opened from the SSL-certificate-on-error dialog or
3038      * not. This is important, since we need to know whether to return to
3039      * the parent dialog or simply dismiss.
3040      */
showPageInfo(final Tab tab, final boolean fromShowSSLCertificateOnError)3041     private void showPageInfo(final Tab tab,
3042                               final boolean fromShowSSLCertificateOnError) {
3043         final LayoutInflater factory = LayoutInflater
3044                 .from(this);
3045 
3046         final View pageInfoView = factory.inflate(R.layout.page_info, null);
3047 
3048         final WebView view = tab.getWebView();
3049 
3050         String url = null;
3051         String title = null;
3052 
3053         if (view == null) {
3054             url = tab.getUrl();
3055             title = tab.getTitle();
3056         } else if (view == mTabControl.getCurrentWebView()) {
3057              // Use the cached title and url if this is the current WebView
3058             url = mUrl;
3059             title = mTitle;
3060         } else {
3061             url = view.getUrl();
3062             title = view.getTitle();
3063         }
3064 
3065         if (url == null) {
3066             url = "";
3067         }
3068         if (title == null) {
3069             title = "";
3070         }
3071 
3072         ((TextView) pageInfoView.findViewById(R.id.address)).setText(url);
3073         ((TextView) pageInfoView.findViewById(R.id.title)).setText(title);
3074 
3075         mPageInfoView = tab;
3076         mPageInfoFromShowSSLCertificateOnError = fromShowSSLCertificateOnError;
3077 
3078         AlertDialog.Builder alertDialogBuilder =
3079             new AlertDialog.Builder(this)
3080             .setTitle(R.string.page_info).setIcon(android.R.drawable.ic_dialog_info)
3081             .setView(pageInfoView)
3082             .setPositiveButton(
3083                 R.string.ok,
3084                 new DialogInterface.OnClickListener() {
3085                     public void onClick(DialogInterface dialog,
3086                                         int whichButton) {
3087                         mPageInfoDialog = null;
3088                         mPageInfoView = null;
3089 
3090                         // if we came here from the SSL error dialog
3091                         if (fromShowSSLCertificateOnError) {
3092                             // go back to the SSL error dialog
3093                             showSSLCertificateOnError(
3094                                 mSSLCertificateOnErrorView,
3095                                 mSSLCertificateOnErrorHandler,
3096                                 mSSLCertificateOnErrorError);
3097                         }
3098                     }
3099                 })
3100             .setOnCancelListener(
3101                 new DialogInterface.OnCancelListener() {
3102                     public void onCancel(DialogInterface dialog) {
3103                         mPageInfoDialog = null;
3104                         mPageInfoView = null;
3105 
3106                         // if we came here from the SSL error dialog
3107                         if (fromShowSSLCertificateOnError) {
3108                             // go back to the SSL error dialog
3109                             showSSLCertificateOnError(
3110                                 mSSLCertificateOnErrorView,
3111                                 mSSLCertificateOnErrorHandler,
3112                                 mSSLCertificateOnErrorError);
3113                         }
3114                     }
3115                 });
3116 
3117         // if we have a main top-level page SSL certificate set or a certificate
3118         // error
3119         if (fromShowSSLCertificateOnError ||
3120                 (view != null && view.getCertificate() != null)) {
3121             // add a 'View Certificate' button
3122             alertDialogBuilder.setNeutralButton(
3123                 R.string.view_certificate,
3124                 new DialogInterface.OnClickListener() {
3125                     public void onClick(DialogInterface dialog,
3126                                         int whichButton) {
3127                         mPageInfoDialog = null;
3128                         mPageInfoView = null;
3129 
3130                         // if we came here from the SSL error dialog
3131                         if (fromShowSSLCertificateOnError) {
3132                             // go back to the SSL error dialog
3133                             showSSLCertificateOnError(
3134                                 mSSLCertificateOnErrorView,
3135                                 mSSLCertificateOnErrorHandler,
3136                                 mSSLCertificateOnErrorError);
3137                         } else {
3138                             // otherwise, display the top-most certificate from
3139                             // the chain
3140                             if (view.getCertificate() != null) {
3141                                 showSSLCertificate(tab);
3142                             }
3143                         }
3144                     }
3145                 });
3146         }
3147 
3148         mPageInfoDialog = alertDialogBuilder.show();
3149     }
3150 
3151        /**
3152      * Displays the main top-level page SSL certificate dialog
3153      * (accessible from the Page-Info dialog).
3154      * @param tab The tab to show certificate for.
3155      */
showSSLCertificate(final Tab tab)3156     private void showSSLCertificate(final Tab tab) {
3157         final View certificateView =
3158                 inflateCertificateView(tab.getWebView().getCertificate());
3159         if (certificateView == null) {
3160             return;
3161         }
3162 
3163         LayoutInflater factory = LayoutInflater.from(this);
3164 
3165         final LinearLayout placeholder =
3166                 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3167 
3168         LinearLayout ll = (LinearLayout) factory.inflate(
3169             R.layout.ssl_success, placeholder);
3170         ((TextView)ll.findViewById(R.id.success))
3171             .setText(R.string.ssl_certificate_is_valid);
3172 
3173         mSSLCertificateView = tab;
3174         mSSLCertificateDialog =
3175             new AlertDialog.Builder(this)
3176                 .setTitle(R.string.ssl_certificate).setIcon(
3177                     R.drawable.ic_dialog_browser_certificate_secure)
3178                 .setView(certificateView)
3179                 .setPositiveButton(R.string.ok,
3180                         new DialogInterface.OnClickListener() {
3181                             public void onClick(DialogInterface dialog,
3182                                     int whichButton) {
3183                                 mSSLCertificateDialog = null;
3184                                 mSSLCertificateView = null;
3185 
3186                                 showPageInfo(tab, false);
3187                             }
3188                         })
3189                 .setOnCancelListener(
3190                         new DialogInterface.OnCancelListener() {
3191                             public void onCancel(DialogInterface dialog) {
3192                                 mSSLCertificateDialog = null;
3193                                 mSSLCertificateView = null;
3194 
3195                                 showPageInfo(tab, false);
3196                             }
3197                         })
3198                 .show();
3199     }
3200 
3201     /**
3202      * Displays the SSL error certificate dialog.
3203      * @param view The target web-view.
3204      * @param handler The SSL error handler responsible for cancelling the
3205      * connection that resulted in an SSL error or proceeding per user request.
3206      * @param error The SSL error object.
3207      */
showSSLCertificateOnError( final WebView view, final SslErrorHandler handler, final SslError error)3208     void showSSLCertificateOnError(
3209         final WebView view, final SslErrorHandler handler, final SslError error) {
3210 
3211         final View certificateView =
3212             inflateCertificateView(error.getCertificate());
3213         if (certificateView == null) {
3214             return;
3215         }
3216 
3217         LayoutInflater factory = LayoutInflater.from(this);
3218 
3219         final LinearLayout placeholder =
3220                 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3221 
3222         if (error.hasError(SslError.SSL_UNTRUSTED)) {
3223             LinearLayout ll = (LinearLayout)factory
3224                 .inflate(R.layout.ssl_warning, placeholder);
3225             ((TextView)ll.findViewById(R.id.warning))
3226                 .setText(R.string.ssl_untrusted);
3227         }
3228 
3229         if (error.hasError(SslError.SSL_IDMISMATCH)) {
3230             LinearLayout ll = (LinearLayout)factory
3231                 .inflate(R.layout.ssl_warning, placeholder);
3232             ((TextView)ll.findViewById(R.id.warning))
3233                 .setText(R.string.ssl_mismatch);
3234         }
3235 
3236         if (error.hasError(SslError.SSL_EXPIRED)) {
3237             LinearLayout ll = (LinearLayout)factory
3238                 .inflate(R.layout.ssl_warning, placeholder);
3239             ((TextView)ll.findViewById(R.id.warning))
3240                 .setText(R.string.ssl_expired);
3241         }
3242 
3243         if (error.hasError(SslError.SSL_NOTYETVALID)) {
3244             LinearLayout ll = (LinearLayout)factory
3245                 .inflate(R.layout.ssl_warning, placeholder);
3246             ((TextView)ll.findViewById(R.id.warning))
3247                 .setText(R.string.ssl_not_yet_valid);
3248         }
3249 
3250         mSSLCertificateOnErrorHandler = handler;
3251         mSSLCertificateOnErrorView = view;
3252         mSSLCertificateOnErrorError = error;
3253         mSSLCertificateOnErrorDialog =
3254             new AlertDialog.Builder(this)
3255                 .setTitle(R.string.ssl_certificate).setIcon(
3256                     R.drawable.ic_dialog_browser_certificate_partially_secure)
3257                 .setView(certificateView)
3258                 .setPositiveButton(R.string.ok,
3259                         new DialogInterface.OnClickListener() {
3260                             public void onClick(DialogInterface dialog,
3261                                     int whichButton) {
3262                                 mSSLCertificateOnErrorDialog = null;
3263                                 mSSLCertificateOnErrorView = null;
3264                                 mSSLCertificateOnErrorHandler = null;
3265                                 mSSLCertificateOnErrorError = null;
3266 
3267                                 view.getWebViewClient().onReceivedSslError(
3268                                                 view, handler, error);
3269                             }
3270                         })
3271                  .setNeutralButton(R.string.page_info_view,
3272                         new DialogInterface.OnClickListener() {
3273                             public void onClick(DialogInterface dialog,
3274                                     int whichButton) {
3275                                 mSSLCertificateOnErrorDialog = null;
3276 
3277                                 // do not clear the dialog state: we will
3278                                 // need to show the dialog again once the
3279                                 // user is done exploring the page-info details
3280 
3281                                 showPageInfo(mTabControl.getTabFromView(view),
3282                                         true);
3283                             }
3284                         })
3285                 .setOnCancelListener(
3286                         new DialogInterface.OnCancelListener() {
3287                             public void onCancel(DialogInterface dialog) {
3288                                 mSSLCertificateOnErrorDialog = null;
3289                                 mSSLCertificateOnErrorView = null;
3290                                 mSSLCertificateOnErrorHandler = null;
3291                                 mSSLCertificateOnErrorError = null;
3292 
3293                                 view.getWebViewClient().onReceivedSslError(
3294                                                 view, handler, error);
3295                             }
3296                         })
3297                 .show();
3298     }
3299 
3300     /**
3301      * Inflates the SSL certificate view (helper method).
3302      * @param certificate The SSL certificate.
3303      * @return The resultant certificate view with issued-to, issued-by,
3304      * issued-on, expires-on, and possibly other fields set.
3305      * If the input certificate is null, returns null.
3306      */
inflateCertificateView(SslCertificate certificate)3307     private View inflateCertificateView(SslCertificate certificate) {
3308         if (certificate == null) {
3309             return null;
3310         }
3311 
3312         LayoutInflater factory = LayoutInflater.from(this);
3313 
3314         View certificateView = factory.inflate(
3315             R.layout.ssl_certificate, null);
3316 
3317         // issued to:
3318         SslCertificate.DName issuedTo = certificate.getIssuedTo();
3319         if (issuedTo != null) {
3320             ((TextView) certificateView.findViewById(R.id.to_common))
3321                 .setText(issuedTo.getCName());
3322             ((TextView) certificateView.findViewById(R.id.to_org))
3323                 .setText(issuedTo.getOName());
3324             ((TextView) certificateView.findViewById(R.id.to_org_unit))
3325                 .setText(issuedTo.getUName());
3326         }
3327 
3328         // issued by:
3329         SslCertificate.DName issuedBy = certificate.getIssuedBy();
3330         if (issuedBy != null) {
3331             ((TextView) certificateView.findViewById(R.id.by_common))
3332                 .setText(issuedBy.getCName());
3333             ((TextView) certificateView.findViewById(R.id.by_org))
3334                 .setText(issuedBy.getOName());
3335             ((TextView) certificateView.findViewById(R.id.by_org_unit))
3336                 .setText(issuedBy.getUName());
3337         }
3338 
3339         // issued on:
3340         String issuedOn = formatCertificateDate(
3341             certificate.getValidNotBeforeDate());
3342         ((TextView) certificateView.findViewById(R.id.issued_on))
3343             .setText(issuedOn);
3344 
3345         // expires on:
3346         String expiresOn = formatCertificateDate(
3347             certificate.getValidNotAfterDate());
3348         ((TextView) certificateView.findViewById(R.id.expires_on))
3349             .setText(expiresOn);
3350 
3351         return certificateView;
3352     }
3353 
3354     /**
3355      * Formats the certificate date to a properly localized date string.
3356      * @return Properly localized version of the certificate date string and
3357      * the "" if it fails to localize.
3358      */
formatCertificateDate(Date certificateDate)3359     private String formatCertificateDate(Date certificateDate) {
3360       if (certificateDate == null) {
3361           return "";
3362       }
3363       String formattedDate = DateFormat.getDateFormat(this).format(certificateDate);
3364       if (formattedDate == null) {
3365           return "";
3366       }
3367       return formattedDate;
3368     }
3369 
3370     /**
3371      * Displays an http-authentication dialog.
3372      */
showHttpAuthentication(final HttpAuthHandler handler, final String host, final String realm, final String title, final String name, final String password, int focusId)3373     void showHttpAuthentication(final HttpAuthHandler handler,
3374             final String host, final String realm, final String title,
3375             final String name, final String password, int focusId) {
3376         LayoutInflater factory = LayoutInflater.from(this);
3377         final View v = factory
3378                 .inflate(R.layout.http_authentication, null);
3379         if (name != null) {
3380             ((EditText) v.findViewById(R.id.username_edit)).setText(name);
3381         }
3382         if (password != null) {
3383             ((EditText) v.findViewById(R.id.password_edit)).setText(password);
3384         }
3385 
3386         String titleText = title;
3387         if (titleText == null) {
3388             titleText = getText(R.string.sign_in_to).toString().replace(
3389                     "%s1", host).replace("%s2", realm);
3390         }
3391 
3392         mHttpAuthHandler = handler;
3393         AlertDialog dialog = new AlertDialog.Builder(this)
3394                 .setTitle(titleText)
3395                 .setIcon(android.R.drawable.ic_dialog_alert)
3396                 .setView(v)
3397                 .setPositiveButton(R.string.action,
3398                         new DialogInterface.OnClickListener() {
3399                              public void onClick(DialogInterface dialog,
3400                                      int whichButton) {
3401                                 String nm = ((EditText) v
3402                                         .findViewById(R.id.username_edit))
3403                                         .getText().toString();
3404                                 String pw = ((EditText) v
3405                                         .findViewById(R.id.password_edit))
3406                                         .getText().toString();
3407                                 BrowserActivity.this.setHttpAuthUsernamePassword
3408                                         (host, realm, nm, pw);
3409                                 handler.proceed(nm, pw);
3410                                 mHttpAuthenticationDialog = null;
3411                                 mHttpAuthHandler = null;
3412                             }})
3413                 .setNegativeButton(R.string.cancel,
3414                         new DialogInterface.OnClickListener() {
3415                             public void onClick(DialogInterface dialog,
3416                                     int whichButton) {
3417                                 handler.cancel();
3418                                 BrowserActivity.this.resetTitleAndRevertLockIcon();
3419                                 mHttpAuthenticationDialog = null;
3420                                 mHttpAuthHandler = null;
3421                             }})
3422                 .setOnCancelListener(new DialogInterface.OnCancelListener() {
3423                         public void onCancel(DialogInterface dialog) {
3424                             handler.cancel();
3425                             BrowserActivity.this.resetTitleAndRevertLockIcon();
3426                             mHttpAuthenticationDialog = null;
3427                             mHttpAuthHandler = null;
3428                         }})
3429                 .create();
3430         // Make the IME appear when the dialog is displayed if applicable.
3431         dialog.getWindow().setSoftInputMode(
3432                 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
3433         dialog.show();
3434         if (focusId != 0) {
3435             dialog.findViewById(focusId).requestFocus();
3436         } else {
3437             v.findViewById(R.id.username_edit).requestFocus();
3438         }
3439         mHttpAuthenticationDialog = dialog;
3440     }
3441 
getProgress()3442     public int getProgress() {
3443         WebView w = mTabControl.getCurrentWebView();
3444         if (w != null) {
3445             return w.getProgress();
3446         } else {
3447             return 100;
3448         }
3449     }
3450 
3451     /**
3452      * Set HTTP authentication password.
3453      *
3454      * @param host The host for the password
3455      * @param realm The realm for the password
3456      * @param username The username for the password. If it is null, it means
3457      *            password can't be saved.
3458      * @param password The password
3459      */
setHttpAuthUsernamePassword(String host, String realm, String username, String password)3460     public void setHttpAuthUsernamePassword(String host, String realm,
3461                                             String username,
3462                                             String password) {
3463         WebView w = getTopWindow();
3464         if (w != null) {
3465             w.setHttpAuthUsernamePassword(host, realm, username, password);
3466         }
3467     }
3468 
3469     /**
3470      * connectivity manager says net has come or gone... inform the user
3471      * @param up true if net has come up, false if net has gone down
3472      */
onNetworkToggle(boolean up)3473     public void onNetworkToggle(boolean up) {
3474         if (up == mIsNetworkUp) {
3475             return;
3476         } else if (up) {
3477             mIsNetworkUp = true;
3478             if (mAlertDialog != null) {
3479                 mAlertDialog.cancel();
3480                 mAlertDialog = null;
3481             }
3482         } else {
3483             mIsNetworkUp = false;
3484             if (mInLoad) {
3485                 createAndShowNetworkDialog();
3486            }
3487         }
3488         WebView w = mTabControl.getCurrentWebView();
3489         if (w != null) {
3490             w.setNetworkAvailable(up);
3491         }
3492     }
3493 
isNetworkUp()3494     boolean isNetworkUp() {
3495         return mIsNetworkUp;
3496     }
3497 
3498     // This method shows the network dialog alerting the user that the net is
3499     // down. It will only show the dialog if mAlertDialog is null.
createAndShowNetworkDialog()3500     private void createAndShowNetworkDialog() {
3501         if (mAlertDialog == null) {
3502             mAlertDialog = new AlertDialog.Builder(this)
3503                     .setTitle(R.string.loadSuspendedTitle)
3504                     .setMessage(R.string.loadSuspended)
3505                     .setPositiveButton(R.string.ok, null)
3506                     .show();
3507         }
3508     }
3509 
3510     @Override
onActivityResult(int requestCode, int resultCode, Intent intent)3511     protected void onActivityResult(int requestCode, int resultCode,
3512                                     Intent intent) {
3513         if (getTopWindow() == null) return;
3514 
3515         switch (requestCode) {
3516             case COMBO_PAGE:
3517                 if (resultCode == RESULT_OK && intent != null) {
3518                     String data = intent.getAction();
3519                     Bundle extras = intent.getExtras();
3520                     if (extras != null && extras.getBoolean("new_window", false)) {
3521                         openTab(data);
3522                     } else {
3523                         final Tab currentTab =
3524                                 mTabControl.getCurrentTab();
3525                         dismissSubWindow(currentTab);
3526                         if (data != null && data.length() != 0) {
3527                             loadUrl(getTopWindow(), data);
3528                         }
3529                     }
3530                 }
3531                 // Deliberately fall through to PREFERENCES_PAGE, since the
3532                 // same extra may be attached to the COMBO_PAGE
3533             case PREFERENCES_PAGE:
3534                 if (resultCode == RESULT_OK && intent != null) {
3535                     String action = intent.getStringExtra(Intent.EXTRA_TEXT);
3536                     if (BrowserSettings.PREF_CLEAR_HISTORY.equals(action)) {
3537                         mTabControl.removeParentChildRelationShips();
3538                     }
3539                 }
3540                 break;
3541             // Choose a file from the file picker.
3542             case FILE_SELECTED:
3543                 if (null == mUploadMessage) break;
3544                 Uri result = intent == null || resultCode != RESULT_OK ? null
3545                         : intent.getData();
3546                 mUploadMessage.onReceiveValue(result);
3547                 mUploadMessage = null;
3548                 break;
3549             default:
3550                 break;
3551         }
3552         getTopWindow().requestFocus();
3553     }
3554 
3555     /*
3556      * This method is called as a result of the user selecting the options
3557      * menu to see the download window. It shows the download window on top of
3558      * the current window.
3559      */
viewDownloads()3560     private void viewDownloads() {
3561         Intent intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
3562         startActivity(intent);
3563     }
3564 
3565     /**
3566      * Open the Go page.
3567      * @param startWithHistory If true, open starting on the history tab.
3568      *                         Otherwise, start with the bookmarks tab.
3569      */
bookmarksOrHistoryPicker(boolean startWithHistory)3570     /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory) {
3571         WebView current = mTabControl.getCurrentWebView();
3572         if (current == null) {
3573             return;
3574         }
3575         Intent intent = new Intent(this,
3576                 CombinedBookmarkHistoryActivity.class);
3577         String title = current.getTitle();
3578         String url = current.getUrl();
3579         Bitmap thumbnail = createScreenshot(current);
3580 
3581         // Just in case the user opens bookmarks before a page finishes loading
3582         // so the current history item, and therefore the page, is null.
3583         if (null == url) {
3584             url = mLastEnteredUrl;
3585             // This can happen.
3586             if (null == url) {
3587                 url = mSettings.getHomePage();
3588             }
3589         }
3590         // In case the web page has not yet received its associated title.
3591         if (title == null) {
3592             title = url;
3593         }
3594         intent.putExtra("title", title);
3595         intent.putExtra("url", url);
3596         intent.putExtra("thumbnail", thumbnail);
3597         // Disable opening in a new window if we have maxed out the windows
3598         intent.putExtra("disable_new_window", !mTabControl.canCreateNewTab());
3599         intent.putExtra("touch_icon_url", current.getTouchIconUrl());
3600         if (startWithHistory) {
3601             intent.putExtra(CombinedBookmarkHistoryActivity.STARTING_TAB,
3602                     CombinedBookmarkHistoryActivity.HISTORY_TAB);
3603         }
3604         startActivityForResult(intent, COMBO_PAGE);
3605     }
3606 
3607     // Called when loading from context menu or LOAD_URL message
loadUrlFromContext(WebView view, String url)3608     private void loadUrlFromContext(WebView view, String url) {
3609         // In case the user enters nothing.
3610         if (url != null && url.length() != 0 && view != null) {
3611             url = smartUrlFilter(url);
3612             if (!view.getWebViewClient().shouldOverrideUrlLoading(view, url)) {
3613                 loadUrl(view, url);
3614             }
3615         }
3616     }
3617 
3618     /**
3619      * Load the URL into the given WebView and update the title bar
3620      * to reflect the new load.  Call this instead of WebView.loadUrl
3621      * directly.
3622      * @param view The WebView used to load url.
3623      * @param url The URL to load.
3624      */
loadUrl(WebView view, String url)3625     private void loadUrl(WebView view, String url) {
3626         updateTitleBarForNewLoad(view, url);
3627         view.loadUrl(url);
3628     }
3629 
3630     /**
3631      * Load UrlData into a Tab and update the title bar to reflect the new
3632      * load.  Call this instead of UrlData.loadIn directly.
3633      * @param t The Tab used to load.
3634      * @param data The UrlData being loaded.
3635      */
loadUrlDataIn(Tab t, UrlData data)3636     private void loadUrlDataIn(Tab t, UrlData data) {
3637         updateTitleBarForNewLoad(t.getWebView(), data.mUrl);
3638         data.loadIn(t);
3639     }
3640 
3641     /**
3642      * If the WebView is the top window, update the title bar to reflect
3643      * loading the new URL.  i.e. set its text, clear the favicon (which
3644      * will be set once the page begins loading), and set the progress to
3645      * INITIAL_PROGRESS to show that the page has begun to load. Called
3646      * by loadUrl and loadUrlDataIn.
3647      * @param view The WebView that is starting a load.
3648      * @param url The URL that is being loaded.
3649      */
updateTitleBarForNewLoad(WebView view, String url)3650     private void updateTitleBarForNewLoad(WebView view, String url) {
3651         if (view == getTopWindow()) {
3652             setUrlTitle(url, null);
3653             setFavicon(null);
3654             onProgressChanged(view, INITIAL_PROGRESS);
3655         }
3656     }
3657 
smartUrlFilter(Uri inUri)3658     private String smartUrlFilter(Uri inUri) {
3659         if (inUri != null) {
3660             return smartUrlFilter(inUri.toString());
3661         }
3662         return null;
3663     }
3664 
3665     protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
3666             "(?i)" + // switch on case insensitive matching
3667             "(" +    // begin group for schema
3668             "(?:http|https|file):\\/\\/" +
3669             "|(?:inline|data|about|javascript):" +
3670             ")" +
3671             "(.*)" );
3672 
3673     /**
3674      * Attempts to determine whether user input is a URL or search
3675      * terms.  Anything with a space is passed to search.
3676      *
3677      * Converts to lowercase any mistakenly uppercased schema (i.e.,
3678      * "Http://" converts to "http://"
3679      *
3680      * @return Original or modified URL
3681      *
3682      */
smartUrlFilter(String url)3683     String smartUrlFilter(String url) {
3684 
3685         String inUrl = url.trim();
3686         boolean hasSpace = inUrl.indexOf(' ') != -1;
3687 
3688         Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl);
3689         if (matcher.matches()) {
3690             // force scheme to lowercase
3691             String scheme = matcher.group(1);
3692             String lcScheme = scheme.toLowerCase();
3693             if (!lcScheme.equals(scheme)) {
3694                 inUrl = lcScheme + matcher.group(2);
3695             }
3696             if (hasSpace) {
3697                 inUrl = inUrl.replace(" ", "%20");
3698             }
3699             return inUrl;
3700         }
3701         if (!hasSpace) {
3702             if (Patterns.WEB_URL.matcher(inUrl).matches()) {
3703                 return URLUtil.guessUrl(inUrl);
3704             }
3705         }
3706 
3707         // FIXME: Is this the correct place to add to searches?
3708         // what if someone else calls this function?
3709 
3710         Browser.addSearchUrl(mResolver, inUrl);
3711         return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER);
3712     }
3713 
setShouldShowErrorConsole(boolean flag)3714     /* package */ void setShouldShowErrorConsole(boolean flag) {
3715         if (flag == mShouldShowErrorConsole) {
3716             // Nothing to do.
3717             return;
3718         }
3719         Tab t = mTabControl.getCurrentTab();
3720         if (t == null) {
3721             // There is no current tab so we cannot toggle the error console
3722             return;
3723         }
3724 
3725         mShouldShowErrorConsole = flag;
3726 
3727         ErrorConsoleView errorConsole = t.getErrorConsole(true);
3728 
3729         if (flag) {
3730             // Setting the show state of the console will cause it's the layout to be inflated.
3731             if (errorConsole.numberOfErrors() > 0) {
3732                 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
3733             } else {
3734                 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
3735             }
3736 
3737             // Now we can add it to the main view.
3738             mErrorConsoleContainer.addView(errorConsole,
3739                     new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
3740                                                   ViewGroup.LayoutParams.WRAP_CONTENT));
3741         } else {
3742             mErrorConsoleContainer.removeView(errorConsole);
3743         }
3744 
3745     }
3746 
shouldShowErrorConsole()3747     boolean shouldShowErrorConsole() {
3748         return mShouldShowErrorConsole;
3749     }
3750 
setStatusBarVisibility(boolean visible)3751     private void setStatusBarVisibility(boolean visible) {
3752         int flag = visible ? 0 : WindowManager.LayoutParams.FLAG_FULLSCREEN;
3753         getWindow().setFlags(flag, WindowManager.LayoutParams.FLAG_FULLSCREEN);
3754     }
3755 
3756 
sendNetworkType(String type, String subtype)3757     private void sendNetworkType(String type, String subtype) {
3758         WebView w = mTabControl.getCurrentWebView();
3759         if (w != null) {
3760             w.setNetworkType(type, subtype);
3761         }
3762     }
3763 
packageChanged(String packageName, boolean wasAdded)3764     private void packageChanged(String packageName, boolean wasAdded) {
3765         WebView w = mTabControl.getCurrentWebView();
3766         if (w == null) {
3767             return;
3768         }
3769 
3770         if (wasAdded) {
3771             w.addPackageName(packageName);
3772         } else {
3773             w.removePackageName(packageName);
3774         }
3775     }
3776 
addPackageNames(Set<String> packageNames)3777     private void addPackageNames(Set<String> packageNames) {
3778         WebView w = mTabControl.getCurrentWebView();
3779         if (w == null) {
3780             return;
3781         }
3782 
3783         w.addPackageNames(packageNames);
3784     }
3785 
getInstalledPackages()3786     private void getInstalledPackages() {
3787         AsyncTask<Void, Void, Set<String> > task =
3788             new AsyncTask<Void, Void, Set<String> >() {
3789             protected Set<String> doInBackground(Void... unused) {
3790                 Set<String> installedPackages = new HashSet<String>();
3791                 PackageManager pm = BrowserActivity.this.getPackageManager();
3792                 if (pm != null) {
3793                     List<PackageInfo> packages = pm.getInstalledPackages(0);
3794                     for (PackageInfo p : packages) {
3795                         if (BrowserActivity.this.sGoogleApps.contains(p.packageName)) {
3796                             installedPackages.add(p.packageName);
3797                         }
3798                     }
3799                 }
3800 
3801                 return installedPackages;
3802             }
3803 
3804             // Executes on the UI thread
3805             protected void onPostExecute(Set<String> installedPackages) {
3806                 addPackageNames(installedPackages);
3807             }
3808         };
3809         task.execute();
3810     }
3811 
3812     final static int LOCK_ICON_UNSECURE = 0;
3813     final static int LOCK_ICON_SECURE   = 1;
3814     final static int LOCK_ICON_MIXED    = 2;
3815 
3816     private BrowserSettings mSettings;
3817     private TabControl      mTabControl;
3818     private ContentResolver mResolver;
3819     private FrameLayout     mContentView;
3820     private View            mCustomView;
3821     private FrameLayout     mCustomViewContainer;
3822     private WebChromeClient.CustomViewCallback mCustomViewCallback;
3823 
3824     // FIXME, temp address onPrepareMenu performance problem. When we move everything out of
3825     // view, we should rewrite this.
3826     private int mCurrentMenuState = 0;
3827     private int mMenuState = R.id.MAIN_MENU;
3828     private int mOldMenuState = EMPTY_MENU;
3829     private static final int EMPTY_MENU = -1;
3830     private Menu mMenu;
3831 
3832     private FindDialog mFindDialog;
3833     private SelectDialog mSelectDialog;
3834     // Used to prevent chording to result in firing two shortcuts immediately
3835     // one after another.  Fixes bug 1211714.
3836     boolean mCanChord;
3837 
3838     private boolean mInLoad;
3839     private boolean mIsNetworkUp;
3840     private boolean mDidStopLoad;
3841 
3842     /* package */ boolean mActivityInPause = true;
3843 
3844     private boolean mMenuIsDown;
3845 
3846     private static boolean mInTrace;
3847 
3848     // Performance probe
3849     private static final int[] SYSTEM_CPU_FORMAT = new int[] {
3850             Process.PROC_SPACE_TERM | Process.PROC_COMBINE,
3851             Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 1: user time
3852             Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 2: nice time
3853             Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 3: sys time
3854             Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 4: idle time
3855             Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 5: iowait time
3856             Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 6: irq time
3857             Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG  // 7: softirq time
3858     };
3859 
3860     private long mStart;
3861     private long mProcessStart;
3862     private long mUserStart;
3863     private long mSystemStart;
3864     private long mIdleStart;
3865     private long mIrqStart;
3866 
3867     private long mUiStart;
3868 
3869     private Drawable    mMixLockIcon;
3870     private Drawable    mSecLockIcon;
3871 
3872     /* hold a ref so we can auto-cancel if necessary */
3873     private AlertDialog mAlertDialog;
3874 
3875     // The up-to-date URL and title (these can be different from those stored
3876     // in WebView, since it takes some time for the information in WebView to
3877     // get updated)
3878     private String mUrl;
3879     private String mTitle;
3880 
3881     // As PageInfo has different style for landscape / portrait, we have
3882     // to re-open it when configuration changed
3883     private AlertDialog mPageInfoDialog;
3884     private Tab mPageInfoView;
3885     // If the Page-Info dialog is launched from the SSL-certificate-on-error
3886     // dialog, we should not just dismiss it, but should get back to the
3887     // SSL-certificate-on-error dialog. This flag is used to store this state
3888     private boolean mPageInfoFromShowSSLCertificateOnError;
3889 
3890     // as SSLCertificateOnError has different style for landscape / portrait,
3891     // we have to re-open it when configuration changed
3892     private AlertDialog mSSLCertificateOnErrorDialog;
3893     private WebView mSSLCertificateOnErrorView;
3894     private SslErrorHandler mSSLCertificateOnErrorHandler;
3895     private SslError mSSLCertificateOnErrorError;
3896 
3897     // as SSLCertificate has different style for landscape / portrait, we
3898     // have to re-open it when configuration changed
3899     private AlertDialog mSSLCertificateDialog;
3900     private Tab mSSLCertificateView;
3901 
3902     // as HttpAuthentication has different style for landscape / portrait, we
3903     // have to re-open it when configuration changed
3904     private AlertDialog mHttpAuthenticationDialog;
3905     private HttpAuthHandler mHttpAuthHandler;
3906 
3907     /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS =
3908                                             new FrameLayout.LayoutParams(
3909                                             ViewGroup.LayoutParams.MATCH_PARENT,
3910                                             ViewGroup.LayoutParams.MATCH_PARENT);
3911     /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
3912                                             new FrameLayout.LayoutParams(
3913                                             ViewGroup.LayoutParams.MATCH_PARENT,
3914                                             ViewGroup.LayoutParams.MATCH_PARENT,
3915                                             Gravity.CENTER);
3916     // Google search
3917     final static String QuickSearch_G = "http://www.google.com/m?q=%s";
3918 
3919     final static String QUERY_PLACE_HOLDER = "%s";
3920 
3921     // "source" parameter for Google search through search key
3922     final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key";
3923     // "source" parameter for Google search through goto menu
3924     final static String GOOGLE_SEARCH_SOURCE_GOTO = "browser-goto";
3925     // "source" parameter for Google search through simplily type
3926     final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type";
3927     // "source" parameter for Google search suggested by the browser
3928     final static String GOOGLE_SEARCH_SOURCE_SUGGEST = "browser-suggest";
3929     // "source" parameter for Google search from unknown source
3930     final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown";
3931 
3932     private final static String LOGTAG = "browser";
3933 
3934     private String mLastEnteredUrl;
3935 
3936     private PowerManager.WakeLock mWakeLock;
3937     private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
3938 
3939     private Toast mStopToast;
3940 
3941     private TitleBar mTitleBar;
3942 
3943     private LinearLayout mErrorConsoleContainer = null;
3944     private boolean mShouldShowErrorConsole = false;
3945 
3946     // As the ids are dynamically created, we can't guarantee that they will
3947     // be in sequence, so this static array maps ids to a window number.
3948     final static private int[] WINDOW_SHORTCUT_ID_ARRAY =
3949     { R.id.window_one_menu_id, R.id.window_two_menu_id, R.id.window_three_menu_id,
3950       R.id.window_four_menu_id, R.id.window_five_menu_id, R.id.window_six_menu_id,
3951       R.id.window_seven_menu_id, R.id.window_eight_menu_id };
3952 
3953     // monitor platform changes
3954     private IntentFilter mNetworkStateChangedFilter;
3955     private BroadcastReceiver mNetworkStateIntentReceiver;
3956 
3957     private BroadcastReceiver mPackageInstallationReceiver;
3958 
3959     private SystemAllowGeolocationOrigins mSystemAllowGeolocationOrigins;
3960 
3961     // activity requestCode
3962     final static int COMBO_PAGE                 = 1;
3963     final static int PREFERENCES_PAGE           = 3;
3964     final static int FILE_SELECTED              = 4;
3965 
3966     // the default <video> poster
3967     private Bitmap mDefaultVideoPoster;
3968     // the video progress view
3969     private View mVideoProgressView;
3970 
3971     // The Google packages we monitor for the navigator.isApplicationInstalled()
3972     // API. Add as needed.
3973     private static Set<String> sGoogleApps;
3974     static {
3975         sGoogleApps = new HashSet<String>();
3976         sGoogleApps.add("com.google.android.youtube");
3977     }
3978 
3979     /**
3980      * A UrlData class to abstract how the content will be set to WebView.
3981      * This base class uses loadUrl to show the content.
3982      */
3983     /* package */ static class UrlData {
3984         final String mUrl;
3985         final Map<String, String> mHeaders;
3986         final Intent mVoiceIntent;
3987 
UrlData(String url)3988         UrlData(String url) {
3989             this.mUrl = url;
3990             this.mHeaders = null;
3991             this.mVoiceIntent = null;
3992         }
3993 
UrlData(String url, Map<String, String> headers, Intent intent)3994         UrlData(String url, Map<String, String> headers, Intent intent) {
3995             this.mUrl = url;
3996             this.mHeaders = headers;
3997             if (RecognizerResultsIntent.ACTION_VOICE_SEARCH_RESULTS
3998                     .equals(intent.getAction())) {
3999                 this.mVoiceIntent = intent;
4000             } else {
4001                 this.mVoiceIntent = null;
4002             }
4003         }
4004 
isEmpty()4005         boolean isEmpty() {
4006             return mVoiceIntent == null && (mUrl == null || mUrl.length() == 0);
4007         }
4008 
4009         /**
4010          * Load this UrlData into the given Tab.  Use loadUrlDataIn to update
4011          * the title bar as well.
4012          */
loadIn(Tab t)4013         public void loadIn(Tab t) {
4014             if (mVoiceIntent != null) {
4015                 t.activateVoiceSearchMode(mVoiceIntent);
4016             } else {
4017                 t.getWebView().loadUrl(mUrl, mHeaders);
4018             }
4019         }
4020     };
4021 
4022     /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null);
4023 
needsRlz(Uri uri)4024     private static boolean needsRlz(Uri uri) {
4025         if ((uri.getQueryParameter("rlz") == null) &&
4026             (uri.getQueryParameter("q") != null) &&
4027             UrlUtils.isGoogleUri(uri)) {
4028             return true;
4029         }
4030         return false;
4031     }
4032 
addRlzParameter(Uri uri, String rlz)4033     private static Uri addRlzParameter(Uri uri, String rlz) {
4034         if (rlz.isEmpty()) {
4035             return uri;
4036         }
4037         return uri.buildUpon().appendQueryParameter("rlz", rlz).build();
4038     }
4039 }
4040