blob: 5a2755439bfc5836a4520c17a6b593cbce5192dc [file] [log] [blame]
The Android Open Source Project0c908882009-03-03 19:32:16 -08001/*
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
17package com.android.browser;
18
19import com.google.android.googleapps.IGoogleLoginService;
20import com.google.android.googlelogin.GoogleLoginServiceConstants;
21
22import android.app.Activity;
The Android Open Source Project0c908882009-03-03 19:32:16 -080023import android.app.AlertDialog;
24import android.app.ProgressDialog;
25import android.app.SearchManager;
26import android.content.ActivityNotFoundException;
27import android.content.BroadcastReceiver;
28import android.content.ComponentName;
29import android.content.ContentResolver;
Leon Scrogginsb6b7f9e2009-06-18 12:05:28 -040030import android.content.ContentUris;
The Android Open Source Project0c908882009-03-03 19:32:16 -080031import android.content.ContentValues;
32import android.content.Context;
33import android.content.DialogInterface;
34import android.content.Intent;
35import android.content.IntentFilter;
36import android.content.ServiceConnection;
37import android.content.DialogInterface.OnCancelListener;
Grace Klobab4da0ad2009-05-14 14:45:40 -070038import android.content.pm.PackageInfo;
The Android Open Source Project0c908882009-03-03 19:32:16 -080039import android.content.pm.PackageManager;
40import android.content.pm.ResolveInfo;
41import android.content.res.AssetManager;
42import android.content.res.Configuration;
43import android.content.res.Resources;
44import android.database.Cursor;
45import android.database.sqlite.SQLiteDatabase;
46import android.database.sqlite.SQLiteException;
47import android.graphics.Bitmap;
48import android.graphics.Canvas;
49import android.graphics.Color;
50import android.graphics.DrawFilter;
51import android.graphics.Paint;
52import android.graphics.PaintFlagsDrawFilter;
53import android.graphics.Picture;
54import android.graphics.drawable.BitmapDrawable;
55import android.graphics.drawable.Drawable;
56import android.graphics.drawable.LayerDrawable;
57import android.graphics.drawable.PaintDrawable;
58import android.hardware.SensorListener;
59import android.hardware.SensorManager;
60import android.net.ConnectivityManager;
61import android.net.Uri;
62import android.net.WebAddress;
63import android.net.http.EventHandler;
64import android.net.http.SslCertificate;
65import android.net.http.SslError;
66import android.os.AsyncTask;
67import android.os.Bundle;
68import android.os.Debug;
69import android.os.Environment;
70import android.os.Handler;
71import android.os.IBinder;
72import android.os.Message;
73import android.os.PowerManager;
74import android.os.Process;
75import android.os.RemoteException;
76import android.os.ServiceManager;
77import android.os.SystemClock;
The Android Open Source Project0c908882009-03-03 19:32:16 -080078import android.provider.Browser;
79import android.provider.Contacts;
80import android.provider.Downloads;
81import android.provider.MediaStore;
82import android.provider.Contacts.Intents.Insert;
83import android.text.IClipboard;
84import android.text.TextUtils;
85import android.text.format.DateFormat;
86import android.text.util.Regex;
The Android Open Source Project0c908882009-03-03 19:32:16 -080087import android.util.Log;
88import android.view.ContextMenu;
89import android.view.Gravity;
90import android.view.KeyEvent;
91import android.view.LayoutInflater;
92import android.view.Menu;
93import android.view.MenuInflater;
94import android.view.MenuItem;
95import android.view.View;
96import android.view.ViewGroup;
97import android.view.Window;
98import android.view.WindowManager;
99import android.view.ContextMenu.ContextMenuInfo;
100import android.view.MenuItem.OnMenuItemClickListener;
101import android.view.animation.AlphaAnimation;
102import android.view.animation.Animation;
103import android.view.animation.AnimationSet;
104import android.view.animation.DecelerateInterpolator;
105import android.view.animation.ScaleAnimation;
106import android.view.animation.TranslateAnimation;
107import android.webkit.CookieManager;
108import android.webkit.CookieSyncManager;
109import android.webkit.DownloadListener;
Steve Block2bc69912009-07-30 14:45:13 +0100110import android.webkit.GeolocationPermissions;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800111import android.webkit.HttpAuthHandler;
Grace Klobab4da0ad2009-05-14 14:45:40 -0700112import android.webkit.PluginManager;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800113import android.webkit.SslErrorHandler;
114import android.webkit.URLUtil;
115import android.webkit.WebChromeClient;
Andrei Popescuc9b55562009-07-07 10:51:15 +0100116import android.webkit.WebChromeClient.CustomViewCallback;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800117import android.webkit.WebHistoryItem;
118import android.webkit.WebIconDatabase;
Ben Murdoch092dd5d2009-04-22 12:34:12 +0100119import android.webkit.WebStorage;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800120import android.webkit.WebView;
121import android.webkit.WebViewClient;
122import android.widget.EditText;
123import android.widget.FrameLayout;
124import android.widget.LinearLayout;
125import android.widget.TextView;
126import android.widget.Toast;
127
128import java.io.BufferedOutputStream;
Leon Scrogginsb6b7f9e2009-06-18 12:05:28 -0400129import java.io.ByteArrayOutputStream;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800130import java.io.File;
131import java.io.FileInputStream;
132import java.io.FileOutputStream;
133import java.io.IOException;
134import java.io.InputStream;
135import java.net.MalformedURLException;
136import java.net.URI;
Dianne Hackborn99189432009-06-17 18:06:18 -0700137import java.net.URISyntaxException;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800138import java.net.URL;
139import java.net.URLEncoder;
140import java.text.ParseException;
141import java.util.Date;
142import java.util.Enumeration;
143import java.util.HashMap;
Patrick Scott37911c72009-03-24 18:02:58 -0700144import java.util.LinkedList;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800145import java.util.Vector;
146import java.util.regex.Matcher;
147import java.util.regex.Pattern;
148import java.util.zip.ZipEntry;
149import java.util.zip.ZipFile;
150
151public class BrowserActivity extends Activity
152 implements KeyTracker.OnKeyTracker,
153 View.OnCreateContextMenuListener,
154 DownloadListener {
155
Dave Bort31a6d1c2009-04-13 15:56:49 -0700156 /* Define some aliases to make these debugging flags easier to refer to.
157 * This file imports android.provider.Browser, so we can't just refer to "Browser.DEBUG".
158 */
159 private final static boolean DEBUG = com.android.browser.Browser.DEBUG;
160 private final static boolean LOGV_ENABLED = com.android.browser.Browser.LOGV_ENABLED;
161 private final static boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED;
162
The Android Open Source Project0c908882009-03-03 19:32:16 -0800163 private IGoogleLoginService mGls = null;
164 private ServiceConnection mGlsConnection = null;
165
166 private SensorManager mSensorManager = null;
167
Satish Sampath565505b2009-05-29 15:37:27 +0100168 // These are single-character shortcuts for searching popular sources.
169 private static final int SHORTCUT_INVALID = 0;
170 private static final int SHORTCUT_GOOGLE_SEARCH = 1;
171 private static final int SHORTCUT_WIKIPEDIA_SEARCH = 2;
172 private static final int SHORTCUT_DICTIONARY_SEARCH = 3;
173 private static final int SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH = 4;
174
The Android Open Source Project0c908882009-03-03 19:32:16 -0800175 /* Whitelisted webpages
176 private static HashSet<String> sWhiteList;
177
178 static {
179 sWhiteList = new HashSet<String>();
180 sWhiteList.add("cnn.com/");
181 sWhiteList.add("espn.go.com/");
182 sWhiteList.add("nytimes.com/");
183 sWhiteList.add("engadget.com/");
184 sWhiteList.add("yahoo.com/");
185 sWhiteList.add("msn.com/");
186 sWhiteList.add("amazon.com/");
187 sWhiteList.add("consumerist.com/");
188 sWhiteList.add("google.com/m/news");
189 }
190 */
191
192 private void setupHomePage() {
193 final Runnable getAccount = new Runnable() {
194 public void run() {
195 // Lower priority
196 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
197 // get the default home page
198 String homepage = mSettings.getHomePage();
199
200 try {
201 if (mGls == null) return;
202
Grace Klobaf2c5c1b2009-05-26 10:48:31 -0700203 if (!homepage.startsWith("http://www.google.")) return;
204 if (homepage.indexOf('?') == -1) return;
205
The Android Open Source Project0c908882009-03-03 19:32:16 -0800206 String hostedUser = mGls.getAccount(GoogleLoginServiceConstants.PREFER_HOSTED);
207 String googleUser = mGls.getAccount(GoogleLoginServiceConstants.REQUIRE_GOOGLE);
208
209 // three cases:
210 //
211 // hostedUser == googleUser
212 // The device has only a google account
213 //
214 // hostedUser != googleUser
215 // The device has a hosted account and a google account
216 //
217 // hostedUser != null, googleUser == null
218 // The device has only a hosted account (so far)
219
220 // developers might have no accounts at all
221 if (hostedUser == null) return;
222
223 if (googleUser == null || !hostedUser.equals(googleUser)) {
224 String domain = hostedUser.substring(hostedUser.lastIndexOf('@')+1);
Grace Klobaf2c5c1b2009-05-26 10:48:31 -0700225 homepage = homepage.replace("?", "/a/" + domain + "?");
The Android Open Source Project0c908882009-03-03 19:32:16 -0800226 }
227 } catch (RemoteException ignore) {
228 // Login service died; carry on
229 } catch (RuntimeException ignore) {
230 // Login service died; carry on
231 } finally {
232 finish(homepage);
233 }
234 }
235
236 private void finish(final String homepage) {
237 mHandler.post(new Runnable() {
238 public void run() {
239 mSettings.setHomePage(BrowserActivity.this, homepage);
240 resumeAfterCredentials();
241
242 // as this is running in a separate thread,
243 // BrowserActivity's onDestroy() may have been called,
244 // which also calls unbindService().
245 if (mGlsConnection != null) {
246 // we no longer need to keep GLS open
247 unbindService(mGlsConnection);
248 mGlsConnection = null;
249 }
250 } });
251 } };
252
253 final boolean[] done = { false };
254
255 // Open a connection to the Google Login Service. The first
256 // time the connection is established, set up the homepage depending on
257 // the account in a background thread.
258 mGlsConnection = new ServiceConnection() {
259 public void onServiceConnected(ComponentName className, IBinder service) {
260 mGls = IGoogleLoginService.Stub.asInterface(service);
261 if (done[0] == false) {
262 done[0] = true;
263 Thread account = new Thread(getAccount);
264 account.setName("GLSAccount");
265 account.start();
266 }
267 }
268 public void onServiceDisconnected(ComponentName className) {
269 mGls = null;
270 }
271 };
272
273 bindService(GoogleLoginServiceConstants.SERVICE_INTENT,
274 mGlsConnection, Context.BIND_AUTO_CREATE);
275 }
276
Cary Clarka9771242009-08-11 16:42:26 -0400277 private static class ClearThumbnails extends AsyncTask<File, Void, Void> {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800278 @Override
279 public Void doInBackground(File... files) {
280 if (files != null) {
281 for (File f : files) {
Cary Clarkd6be1752009-08-12 12:56:42 -0400282 if (!f.delete()) {
283 Log.e(LOGTAG, f.getPath() + " was not deleted");
284 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800285 }
286 }
287 return null;
288 }
289 }
290
Leon Scroggins81db3662009-06-04 17:45:11 -0400291 // Flag to enable the touchable browser bar with buttons
292 private final boolean CUSTOM_BROWSER_BAR = true;
293
The Android Open Source Project0c908882009-03-03 19:32:16 -0800294 @Override public void onCreate(Bundle icicle) {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700295 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800296 Log.v(LOGTAG, this + " onStart");
297 }
298 super.onCreate(icicle);
Leon Scroggins81db3662009-06-04 17:45:11 -0400299 if (CUSTOM_BROWSER_BAR) {
300 this.requestWindowFeature(Window.FEATURE_NO_TITLE);
Leon Scroggins81db3662009-06-04 17:45:11 -0400301 } else {
302 this.requestWindowFeature(Window.FEATURE_LEFT_ICON);
303 this.requestWindowFeature(Window.FEATURE_RIGHT_ICON);
304 this.requestWindowFeature(Window.FEATURE_PROGRESS);
305 this.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
306 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800307 // test the browser in OpenGL
308 // requestWindowFeature(Window.FEATURE_OPENGL);
309
310 setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
311
312 mResolver = getContentResolver();
313
The Android Open Source Project0c908882009-03-03 19:32:16 -0800314 //
315 // start MASF proxy service
316 //
317 //Intent proxyServiceIntent = new Intent();
318 //proxyServiceIntent.setComponent
319 // (new ComponentName(
320 // "com.android.masfproxyservice",
321 // "com.android.masfproxyservice.MasfProxyService"));
322 //startService(proxyServiceIntent, null);
323
324 mSecLockIcon = Resources.getSystem().getDrawable(
325 android.R.drawable.ic_secure);
326 mMixLockIcon = Resources.getSystem().getDrawable(
327 android.R.drawable.ic_partial_secure);
328 mGenericFavicon = getResources().getDrawable(
329 R.drawable.app_web_browser_sm);
330
Leon Scroggins81db3662009-06-04 17:45:11 -0400331 FrameLayout frameLayout = (FrameLayout) getWindow().getDecorView()
332 .findViewById(com.android.internal.R.id.content);
333 if (CUSTOM_BROWSER_BAR) {
Andrei Popescuadc008d2009-06-26 14:11:30 +0100334 // This FrameLayout will hold the custom FrameLayout and a LinearLayout
335 // that contains the title bar and a FrameLayout, which
Leon Scroggins81db3662009-06-04 17:45:11 -0400336 // holds everything else.
Andrei Popescuadc008d2009-06-26 14:11:30 +0100337 FrameLayout browserFrameLayout = (FrameLayout) LayoutInflater.from(this)
Leon Scrogginse4b3bda2009-06-09 15:46:41 -0400338 .inflate(R.layout.custom_screen, null);
Leon Scroggins1f005d32009-08-10 17:36:42 -0400339 mTitleBar = (TitleBarSet) browserFrameLayout.findViewById(R.id.title_bar);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100340 mContentView = (FrameLayout) browserFrameLayout.findViewById(
Leon Scrogginse4b3bda2009-06-09 15:46:41 -0400341 R.id.main_content);
Ben Murdochbff2d602009-07-01 20:19:05 +0100342 mErrorConsoleContainer = (LinearLayout) browserFrameLayout.findViewById(
343 R.id.error_console);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100344 mCustomViewContainer = (FrameLayout) browserFrameLayout
345 .findViewById(R.id.fullscreen_custom_content);
346 frameLayout.addView(browserFrameLayout, COVER_SCREEN_PARAMS);
Leon Scroggins81db3662009-06-04 17:45:11 -0400347 } else {
Andrei Popescuadc008d2009-06-26 14:11:30 +0100348 mCustomViewContainer = new FrameLayout(this);
Andrei Popescu78f75702009-06-26 16:50:04 +0100349 mCustomViewContainer.setBackgroundColor(Color.BLACK);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100350 mContentView = new FrameLayout(this);
Ben Murdochbff2d602009-07-01 20:19:05 +0100351
352 LinearLayout linearLayout = new LinearLayout(this);
353 linearLayout.setOrientation(LinearLayout.VERTICAL);
354 mErrorConsoleContainer = new LinearLayout(this);
355 linearLayout.addView(mErrorConsoleContainer, new LinearLayout.LayoutParams(
356 ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
357 linearLayout.addView(mContentView, COVER_SCREEN_PARAMS);
Andrei Popescuadc008d2009-06-26 14:11:30 +0100358 frameLayout.addView(mCustomViewContainer, COVER_SCREEN_PARAMS);
Ben Murdochbff2d602009-07-01 20:19:05 +0100359 frameLayout.addView(linearLayout, COVER_SCREEN_PARAMS);
Leon Scroggins81db3662009-06-04 17:45:11 -0400360 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800361
362 // Create the tab control and our initial tab
363 mTabControl = new TabControl(this);
364
365 // Open the icon database and retain all the bookmark urls for favicons
366 retainIconsOnStartup();
367
368 // Keep a settings instance handy.
369 mSettings = BrowserSettings.getInstance();
370 mSettings.setTabControl(mTabControl);
371 mSettings.loadFromDb(this);
372
373 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
374 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Browser");
375
Grace Klobaa34f6862009-07-31 16:28:17 -0700376 /* enables registration for changes in network status from
377 http stack */
378 mNetworkStateChangedFilter = new IntentFilter();
379 mNetworkStateChangedFilter.addAction(
380 ConnectivityManager.CONNECTIVITY_ACTION);
381 mNetworkStateIntentReceiver = new BroadcastReceiver() {
382 @Override
383 public void onReceive(Context context, Intent intent) {
384 if (intent.getAction().equals(
385 ConnectivityManager.CONNECTIVITY_ACTION)) {
386 boolean down = intent.getBooleanExtra(
387 ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
388 onNetworkToggle(!down);
389 }
390 }
391 };
392
Grace Kloba615c6c92009-08-03 10:22:44 -0700393 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
394 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
395 filter.addDataScheme("package");
396 mPackageInstallationReceiver = new BroadcastReceiver() {
397 @Override
398 public void onReceive(Context context, Intent intent) {
399 final String action = intent.getAction();
400 final String packageName = intent.getData()
401 .getSchemeSpecificPart();
402 final boolean replacing = intent.getBooleanExtra(
403 Intent.EXTRA_REPLACING, false);
404 if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) {
405 // if it is replacing, refreshPlugins() when adding
406 return;
407 }
408 PackageManager pm = BrowserActivity.this.getPackageManager();
409 PackageInfo pkgInfo = null;
410 try {
411 pkgInfo = pm.getPackageInfo(packageName,
412 PackageManager.GET_PERMISSIONS);
413 } catch (PackageManager.NameNotFoundException e) {
414 return;
415 }
416 if (pkgInfo != null) {
417 String permissions[] = pkgInfo.requestedPermissions;
418 if (permissions == null) {
419 return;
420 }
421 boolean permissionOk = false;
422 for (String permit : permissions) {
423 if (PluginManager.PLUGIN_PERMISSION.equals(permit)) {
424 permissionOk = true;
425 break;
426 }
427 }
428 if (permissionOk) {
429 PluginManager.getInstance(BrowserActivity.this)
430 .refreshPlugins(
431 Intent.ACTION_PACKAGE_ADDED
432 .equals(action));
433 }
434 }
435 }
436 };
437 registerReceiver(mPackageInstallationReceiver, filter);
438
Satish Sampath565505b2009-05-29 15:37:27 +0100439 // If this was a web search request, pass it on to the default web search provider.
440 if (handleWebSearchIntent(getIntent())) {
441 moveTaskToBack(true);
442 return;
443 }
444
The Android Open Source Project0c908882009-03-03 19:32:16 -0800445 if (!mTabControl.restoreState(icicle)) {
446 // clear up the thumbnail directory if we can't restore the state as
447 // none of the files in the directory are referenced any more.
448 new ClearThumbnails().execute(
449 mTabControl.getThumbnailDir().listFiles());
Grace Klobaaab3f092009-07-30 12:29:51 -0700450 // there is no quit on Android. But if we can't restore the state,
451 // we can treat it as a new Browser, remove the old session cookies.
452 CookieManager.getInstance().removeSessionCookie();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800453 final Intent intent = getIntent();
454 final Bundle extra = intent.getExtras();
455 // Create an initial tab.
456 // If the intent is ACTION_VIEW and data is not null, the Browser is
457 // invoked to view the content by another application. In this case,
458 // the tab will be close when exit.
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700459 UrlData urlData = getUrlDataFromIntent(intent);
460
The Android Open Source Project0c908882009-03-03 19:32:16 -0800461 final TabControl.Tab t = mTabControl.createNewTab(
462 Intent.ACTION_VIEW.equals(intent.getAction()) &&
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700463 intent.getData() != null,
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700464 intent.getStringExtra(Browser.EXTRA_APPLICATION_ID), urlData.mUrl);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800465 mTabControl.setCurrentTab(t);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800466 attachTabToContentView(t);
467 WebView webView = t.getWebView();
468 if (extra != null) {
469 int scale = extra.getInt(Browser.INITIAL_ZOOM_LEVEL, 0);
470 if (scale > 0 && scale <= 1000) {
471 webView.setInitialScale(scale);
472 }
473 }
474 // If we are not restoring from an icicle, then there is a high
475 // likely hood this is the first run. So, check to see if the
476 // homepage needs to be configured and copy any plugins from our
477 // asset directory to the data partition.
478 if ((extra == null || !extra.getBoolean("testing"))
479 && !mSettings.isLoginInitialized()) {
480 setupHomePage();
481 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800482
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700483 if (urlData.isEmpty()) {
Leon Scroggins160a7e72009-08-14 18:28:01 -0400484 bookmarksOrHistoryPicker(false, true);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800485 } else {
Grace Kloba81678d92009-06-30 07:09:56 -0700486 if (extra != null) {
487 urlData.setPostData(extra
488 .getByteArray(Browser.EXTRA_POST_DATA));
489 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700490 urlData.loadIn(webView);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800491 }
492 } else {
493 // TabControl.restoreState() will create a new tab even if
Leon Scroggins1f005d32009-08-10 17:36:42 -0400494 // restoring the state fails.
The Android Open Source Project0c908882009-03-03 19:32:16 -0800495 attachTabToContentView(mTabControl.getCurrentTab());
496 }
Grace Kloba615c6c92009-08-03 10:22:44 -0700497
Leon Scroggins1f005d32009-08-10 17:36:42 -0400498 if (CUSTOM_BROWSER_BAR) {
Leon Scrogginsa81a7642009-08-31 17:05:41 -0400499 mTitleBar.init(this);
Leon Scroggins1f005d32009-08-10 17:36:42 -0400500 // Create title bars for all of the tabs that have been created
501 for (int i = 0; i < mTabControl.getTabCount(); i ++) {
502 WebView view = mTabControl.getTab(i).getWebView();
503 mTitleBar.addTab(view, false);
504 }
505
Leon Scroggins1f005d32009-08-10 17:36:42 -0400506 mTitleBar.setCurrentTab(mTabControl.getCurrentIndex());
507 }
508
Feng Qianb3c02da2009-06-29 15:58:08 -0700509 // Read JavaScript flags if it exists.
510 String jsFlags = mSettings.getJsFlags();
511 if (jsFlags.trim().length() != 0) {
512 mTabControl.getCurrentWebView().setJsFlags(jsFlags);
513 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800514 }
515
516 @Override
517 protected void onNewIntent(Intent intent) {
518 TabControl.Tab current = mTabControl.getCurrentTab();
519 // When a tab is closed on exit, the current tab index is set to -1.
520 // Reset before proceed as Browser requires the current tab to be set.
521 if (current == null) {
522 // Try to reset the tab in case the index was incorrect.
523 current = mTabControl.getTab(0);
524 if (current == null) {
525 // No tabs at all so just ignore this intent.
526 return;
527 }
528 mTabControl.setCurrentTab(current);
Leon Scroggins1f005d32009-08-10 17:36:42 -0400529 if (CUSTOM_BROWSER_BAR) {
530 mTitleBar.setCurrentTab(mTabControl.getTabIndex(current));
531 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800532 attachTabToContentView(current);
533 resetTitleAndIcon(current.getWebView());
534 }
535 final String action = intent.getAction();
536 final int flags = intent.getFlags();
537 if (Intent.ACTION_MAIN.equals(action) ||
538 (flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
539 // just resume the browser
540 return;
541 }
542 if (Intent.ACTION_VIEW.equals(action)
543 || Intent.ACTION_SEARCH.equals(action)
544 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
545 || Intent.ACTION_WEB_SEARCH.equals(action)) {
Satish Sampath565505b2009-05-29 15:37:27 +0100546 // If this was a search request (e.g. search query directly typed into the address bar),
547 // pass it on to the default web search provider.
548 if (handleWebSearchIntent(intent)) {
549 return;
550 }
551
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700552 UrlData urlData = getUrlDataFromIntent(intent);
553 if (urlData.isEmpty()) {
554 urlData = new UrlData(mSettings.getHomePage());
The Android Open Source Project0c908882009-03-03 19:32:16 -0800555 }
Grace Kloba81678d92009-06-30 07:09:56 -0700556 urlData.setPostData(intent
557 .getByteArrayExtra(Browser.EXTRA_POST_DATA));
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700558
Grace Klobacc634032009-07-28 15:58:19 -0700559 final String appId = intent
560 .getStringExtra(Browser.EXTRA_APPLICATION_ID);
561 if (Intent.ACTION_VIEW.equals(action)
562 && !getPackageName().equals(appId)
563 && (flags & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
Patrick Scottcd115892009-07-16 09:42:58 -0400564 TabControl.Tab appTab = mTabControl.getTabFromId(appId);
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700565 if (appTab != null) {
566 Log.i(LOGTAG, "Reusing tab for " + appId);
567 // Dismiss the subwindow if applicable.
568 dismissSubWindow(appTab);
569 // Since we might kill the WebView, remove it from the
570 // content view first.
571 removeTabFromContentView(appTab);
572 // Recreate the main WebView after destroying the old one.
573 // If the WebView has the same original url and is on that
574 // page, it can be reused.
575 boolean needsLoad =
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700576 mTabControl.recreateWebView(appTab, urlData.mUrl);
Ben Murdochbff2d602009-07-01 20:19:05 +0100577
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700578 if (current != appTab) {
Leon Scroggins1f005d32009-08-10 17:36:42 -0400579 switchToTab(mTabControl.getTabIndex(appTab));
580 if (needsLoad) {
581 urlData.loadIn(appTab.getWebView());
582 }
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700583 } else {
Leon Scroggins1f005d32009-08-10 17:36:42 -0400584 // If the tab was the current tab, we have to attach
585 // it to the view system again.
586 attachTabToContentView(appTab);
587 if (needsLoad) {
588 urlData.loadIn(appTab.getWebView());
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700589 }
590 }
591 return;
Patrick Scottcd115892009-07-16 09:42:58 -0400592 } else {
593 // No matching application tab, try to find a regular tab
594 // with a matching url.
595 appTab = mTabControl.findUnusedTabWithUrl(urlData.mUrl);
Leon Scroggins25515f82009-08-19 15:31:58 -0400596 if (appTab != null) {
597 if (current != appTab) {
598 switchToTab(mTabControl.getTabIndex(appTab));
599 }
600 // Otherwise, we are already viewing the correct tab.
Patrick Scottcd115892009-07-16 09:42:58 -0400601 } else {
602 // if FLAG_ACTIVITY_BROUGHT_TO_FRONT flag is on, the url
603 // will be opened in a new tab unless we have reached
604 // MAX_TABS. Then the url will be opened in the current
605 // tab. If a new tab is created, it will have "true" for
606 // exit on close.
Leon Scroggins1f005d32009-08-10 17:36:42 -0400607 openTabAndShow(urlData, true, appId);
Patrick Scottcd115892009-07-16 09:42:58 -0400608 }
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700609 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800610 } else {
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700611 if ("about:debug".equals(urlData.mUrl)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800612 mSettings.toggleDebugSettings();
613 return;
614 }
Leon Scroggins1f005d32009-08-10 17:36:42 -0400615 // Get rid of the subwindow if it exists
616 dismissSubWindow(current);
617 urlData.loadIn(current.getWebView());
The Android Open Source Project0c908882009-03-03 19:32:16 -0800618 }
619 }
620 }
621
Satish Sampath565505b2009-05-29 15:37:27 +0100622 private int parseUrlShortcut(String url) {
623 if (url == null) return SHORTCUT_INVALID;
624
625 // FIXME: quick search, need to be customized by setting
626 if (url.length() > 2 && url.charAt(1) == ' ') {
627 switch (url.charAt(0)) {
628 case 'g': return SHORTCUT_GOOGLE_SEARCH;
629 case 'w': return SHORTCUT_WIKIPEDIA_SEARCH;
630 case 'd': return SHORTCUT_DICTIONARY_SEARCH;
631 case 'l': return SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH;
632 }
633 }
634 return SHORTCUT_INVALID;
635 }
636
637 /**
638 * Launches the default web search activity with the query parameters if the given intent's data
639 * are identified as plain search terms and not URLs/shortcuts.
640 * @return true if the intent was handled and web search activity was launched, false if not.
641 */
642 private boolean handleWebSearchIntent(Intent intent) {
643 if (intent == null) return false;
644
645 String url = null;
646 final String action = intent.getAction();
647 if (Intent.ACTION_VIEW.equals(action)) {
648 url = intent.getData().toString();
649 } else if (Intent.ACTION_SEARCH.equals(action)
650 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
651 || Intent.ACTION_WEB_SEARCH.equals(action)) {
652 url = intent.getStringExtra(SearchManager.QUERY);
653 }
Satish Sampath15e9f2d2009-06-23 22:29:49 +0100654 return handleWebSearchRequest(url, intent.getBundleExtra(SearchManager.APP_DATA));
Satish Sampath565505b2009-05-29 15:37:27 +0100655 }
656
657 /**
658 * Launches the default web search activity with the query parameters if the given url string
659 * was identified as plain search terms and not URL/shortcut.
660 * @return true if the request was handled and web search activity was launched, false if not.
661 */
Satish Sampath15e9f2d2009-06-23 22:29:49 +0100662 private boolean handleWebSearchRequest(String inUrl, Bundle appData) {
Satish Sampath565505b2009-05-29 15:37:27 +0100663 if (inUrl == null) return false;
664
665 // In general, we shouldn't modify URL from Intent.
666 // But currently, we get the user-typed URL from search box as well.
667 String url = fixUrl(inUrl).trim();
668
669 // URLs and site specific search shortcuts are handled by the regular flow of control, so
670 // return early.
671 if (Regex.WEB_URL_PATTERN.matcher(url).matches()
Satish Sampathbc5b9f32009-06-04 18:21:40 +0100672 || ACCEPTED_URI_SCHEMA.matcher(url).matches()
Satish Sampath565505b2009-05-29 15:37:27 +0100673 || parseUrlShortcut(url) != SHORTCUT_INVALID) {
674 return false;
675 }
676
677 Browser.updateVisitedHistory(mResolver, url, false);
678 Browser.addSearchUrl(mResolver, url);
679
680 Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
681 intent.addCategory(Intent.CATEGORY_DEFAULT);
682 intent.putExtra(SearchManager.QUERY, url);
Satish Sampath15e9f2d2009-06-23 22:29:49 +0100683 if (appData != null) {
684 intent.putExtra(SearchManager.APP_DATA, appData);
685 }
Grace Klobacc634032009-07-28 15:58:19 -0700686 intent.putExtra(Browser.EXTRA_APPLICATION_ID, getPackageName());
Satish Sampath565505b2009-05-29 15:37:27 +0100687 startActivity(intent);
688
689 return true;
690 }
691
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700692 private UrlData getUrlDataFromIntent(Intent intent) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800693 String url = null;
694 if (intent != null) {
695 final String action = intent.getAction();
696 if (Intent.ACTION_VIEW.equals(action)) {
697 url = smartUrlFilter(intent.getData());
698 if (url != null && url.startsWith("content:")) {
699 /* Append mimetype so webview knows how to display */
700 String mimeType = intent.resolveType(getContentResolver());
701 if (mimeType != null) {
702 url += "?" + mimeType;
703 }
704 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700705 if ("inline:".equals(url)) {
706 return new InlinedUrlData(
707 intent.getStringExtra(Browser.EXTRA_INLINE_CONTENT),
708 intent.getType(),
709 intent.getStringExtra(Browser.EXTRA_INLINE_ENCODING),
710 intent.getStringExtra(Browser.EXTRA_INLINE_FAILURL));
711 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800712 } else if (Intent.ACTION_SEARCH.equals(action)
713 || MediaStore.INTENT_ACTION_MEDIA_SEARCH.equals(action)
714 || Intent.ACTION_WEB_SEARCH.equals(action)) {
715 url = intent.getStringExtra(SearchManager.QUERY);
716 if (url != null) {
717 mLastEnteredUrl = url;
718 // Don't add Urls, just search terms.
719 // Urls will get added when the page is loaded.
720 if (!Regex.WEB_URL_PATTERN.matcher(url).matches()) {
721 Browser.updateVisitedHistory(mResolver, url, false);
722 }
723 // In general, we shouldn't modify URL from Intent.
724 // But currently, we get the user-typed URL from search box as well.
725 url = fixUrl(url);
726 url = smartUrlFilter(url);
727 String searchSource = "&source=android-" + GOOGLE_SEARCH_SOURCE_SUGGEST + "&";
728 if (url.contains(searchSource)) {
729 String source = null;
730 final Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA);
731 if (appData != null) {
732 source = appData.getString(SearchManager.SOURCE);
733 }
734 if (TextUtils.isEmpty(source)) {
735 source = GOOGLE_SEARCH_SOURCE_UNKNOWN;
736 }
737 url = url.replace(searchSource, "&source=android-"+source+"&");
738 }
739 }
740 }
741 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -0700742 return new UrlData(url);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800743 }
744
745 /* package */ static String fixUrl(String inUrl) {
746 if (inUrl.startsWith("http://") || inUrl.startsWith("https://"))
747 return inUrl;
748 if (inUrl.startsWith("http:") ||
749 inUrl.startsWith("https:")) {
750 if (inUrl.startsWith("http:/") || inUrl.startsWith("https:/")) {
751 inUrl = inUrl.replaceFirst("/", "//");
752 } else inUrl = inUrl.replaceFirst(":", "://");
753 }
754 return inUrl;
755 }
756
757 /**
758 * Looking for the pattern like this
759 *
760 * *
761 * * *
762 * *** * *******
763 * * *
764 * * *
765 * *
766 */
767 private final SensorListener mSensorListener = new SensorListener() {
768 private long mLastGestureTime;
769 private float[] mPrev = new float[3];
770 private float[] mPrevDiff = new float[3];
771 private float[] mDiff = new float[3];
772 private float[] mRevertDiff = new float[3];
773
774 public void onSensorChanged(int sensor, float[] values) {
775 boolean show = false;
776 float[] diff = new float[3];
777
778 for (int i = 0; i < 3; i++) {
779 diff[i] = values[i] - mPrev[i];
780 if (Math.abs(diff[i]) > 1) {
781 show = true;
782 }
783 if ((diff[i] > 1.0 && mDiff[i] < 0.2)
784 || (diff[i] < -1.0 && mDiff[i] > -0.2)) {
785 // start track when there is a big move, or revert
786 mRevertDiff[i] = mDiff[i];
787 mDiff[i] = 0;
788 } else if (diff[i] > -0.2 && diff[i] < 0.2) {
789 // reset when it is flat
790 mDiff[i] = mRevertDiff[i] = 0;
791 }
792 mDiff[i] += diff[i];
793 mPrevDiff[i] = diff[i];
794 mPrev[i] = values[i];
795 }
796
797 if (false) {
798 // only shows if we think the delta is big enough, in an attempt
799 // to detect "serious" moves left/right or up/down
800 Log.d("BrowserSensorHack", "sensorChanged " + sensor + " ("
801 + values[0] + ", " + values[1] + ", " + values[2] + ")"
802 + " diff(" + diff[0] + " " + diff[1] + " " + diff[2]
803 + ")");
804 Log.d("BrowserSensorHack", " mDiff(" + mDiff[0] + " "
805 + mDiff[1] + " " + mDiff[2] + ")" + " mRevertDiff("
806 + mRevertDiff[0] + " " + mRevertDiff[1] + " "
807 + mRevertDiff[2] + ")");
808 }
809
810 long now = android.os.SystemClock.uptimeMillis();
811 if (now - mLastGestureTime > 1000) {
812 mLastGestureTime = 0;
813
814 float y = mDiff[1];
815 float z = mDiff[2];
816 float ay = Math.abs(y);
817 float az = Math.abs(z);
818 float ry = mRevertDiff[1];
819 float rz = mRevertDiff[2];
820 float ary = Math.abs(ry);
821 float arz = Math.abs(rz);
822 boolean gestY = ay > 2.5f && ary > 1.0f && ay > ary;
823 boolean gestZ = az > 3.5f && arz > 1.0f && az > arz;
824
825 if ((gestY || gestZ) && !(gestY && gestZ)) {
826 WebView view = mTabControl.getCurrentWebView();
827
828 if (view != null) {
829 if (gestZ) {
830 if (z < 0) {
831 view.zoomOut();
832 } else {
833 view.zoomIn();
834 }
835 } else {
836 view.flingScroll(0, Math.round(y * 100));
837 }
838 }
839 mLastGestureTime = now;
840 }
841 }
842 }
843
844 public void onAccuracyChanged(int sensor, int accuracy) {
845 // TODO Auto-generated method stub
846
847 }
848 };
849
850 @Override protected void onResume() {
851 super.onResume();
Dave Bort31a6d1c2009-04-13 15:56:49 -0700852 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800853 Log.v(LOGTAG, "BrowserActivity.onResume: this=" + this);
854 }
855
856 if (!mActivityInPause) {
857 Log.e(LOGTAG, "BrowserActivity is already resumed.");
858 return;
859 }
860
Mike Reed7bfa63b2009-05-28 11:08:32 -0400861 mTabControl.resumeCurrentTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800862 mActivityInPause = false;
Mike Reed7bfa63b2009-05-28 11:08:32 -0400863 resumeWebViewTimers();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800864
865 if (mWakeLock.isHeld()) {
866 mHandler.removeMessages(RELEASE_WAKELOCK);
867 mWakeLock.release();
868 }
869
870 if (mCredsDlg != null) {
871 if (!mHandler.hasMessages(CANCEL_CREDS_REQUEST)) {
872 // In case credential request never comes back
873 mHandler.sendEmptyMessageDelayed(CANCEL_CREDS_REQUEST, 6000);
874 }
875 }
876
877 registerReceiver(mNetworkStateIntentReceiver,
878 mNetworkStateChangedFilter);
879 WebView.enablePlatformNotifications();
880
881 if (mSettings.doFlick()) {
882 if (mSensorManager == null) {
883 mSensorManager = (SensorManager) getSystemService(
884 Context.SENSOR_SERVICE);
885 }
886 mSensorManager.registerListener(mSensorListener,
887 SensorManager.SENSOR_ACCELEROMETER,
888 SensorManager.SENSOR_DELAY_FASTEST);
889 } else {
890 mSensorManager = null;
891 }
892 }
893
Leon Scrogginsa81a7642009-08-31 17:05:41 -0400894 @Override
895 public boolean onMenuOpened(int featureId, Menu menu) {
896 mTitleBar.setVisibility(View.VISIBLE);
897 return true;
898 }
899
The Android Open Source Project0c908882009-03-03 19:32:16 -0800900 /**
901 * onSaveInstanceState(Bundle map)
902 * onSaveInstanceState is called right before onStop(). The map contains
903 * the saved state.
904 */
905 @Override protected void onSaveInstanceState(Bundle outState) {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700906 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800907 Log.v(LOGTAG, "BrowserActivity.onSaveInstanceState: this=" + this);
908 }
909 // the default implementation requires each view to have an id. As the
910 // browser handles the state itself and it doesn't use id for the views,
911 // don't call the default implementation. Otherwise it will trigger the
912 // warning like this, "couldn't save which view has focus because the
913 // focused view XXX has no id".
914
915 // Save all the tabs
916 mTabControl.saveState(outState);
917 }
918
919 @Override protected void onPause() {
920 super.onPause();
921
922 if (mActivityInPause) {
923 Log.e(LOGTAG, "BrowserActivity is already paused.");
924 return;
925 }
926
Mike Reed7bfa63b2009-05-28 11:08:32 -0400927 mTabControl.pauseCurrentTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -0800928 mActivityInPause = true;
Mike Reed7bfa63b2009-05-28 11:08:32 -0400929 if (mTabControl.getCurrentIndex() >= 0 && !pauseWebViewTimers()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800930 mWakeLock.acquire();
931 mHandler.sendMessageDelayed(mHandler
932 .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT);
933 }
934
935 // Clear the credentials toast if it is up
936 if (mCredsDlg != null && mCredsDlg.isShowing()) {
937 mCredsDlg.dismiss();
938 }
939 mCredsDlg = null;
940
941 cancelStopToast();
942
943 // unregister network state listener
944 unregisterReceiver(mNetworkStateIntentReceiver);
945 WebView.disablePlatformNotifications();
946
947 if (mSensorManager != null) {
948 mSensorManager.unregisterListener(mSensorListener);
949 }
950 }
951
952 @Override protected void onDestroy() {
Dave Bort31a6d1c2009-04-13 15:56:49 -0700953 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800954 Log.v(LOGTAG, "BrowserActivity.onDestroy: this=" + this);
955 }
956 super.onDestroy();
957 // Remove the current tab and sub window
958 TabControl.Tab t = mTabControl.getCurrentTab();
Patrick Scottfb5e77f2009-04-08 19:17:37 -0700959 if (t != null) {
960 dismissSubWindow(t);
961 removeTabFromContentView(t);
962 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800963 // Destroy all the tabs
964 mTabControl.destroy();
965 WebIconDatabase.getInstance().close();
966 if (mGlsConnection != null) {
967 unbindService(mGlsConnection);
968 mGlsConnection = null;
969 }
970
971 //
972 // stop MASF proxy service
973 //
974 //Intent proxyServiceIntent = new Intent();
975 //proxyServiceIntent.setComponent
976 // (new ComponentName(
977 // "com.android.masfproxyservice",
978 // "com.android.masfproxyservice.MasfProxyService"));
979 //stopService(proxyServiceIntent);
Grace Klobab4da0ad2009-05-14 14:45:40 -0700980
981 unregisterReceiver(mPackageInstallationReceiver);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800982 }
983
984 @Override
985 public void onConfigurationChanged(Configuration newConfig) {
986 super.onConfigurationChanged(newConfig);
987
988 if (mPageInfoDialog != null) {
989 mPageInfoDialog.dismiss();
990 showPageInfo(
991 mPageInfoView,
992 mPageInfoFromShowSSLCertificateOnError.booleanValue());
993 }
994 if (mSSLCertificateDialog != null) {
995 mSSLCertificateDialog.dismiss();
996 showSSLCertificate(
997 mSSLCertificateView);
998 }
999 if (mSSLCertificateOnErrorDialog != null) {
1000 mSSLCertificateOnErrorDialog.dismiss();
1001 showSSLCertificateOnError(
1002 mSSLCertificateOnErrorView,
1003 mSSLCertificateOnErrorHandler,
1004 mSSLCertificateOnErrorError);
1005 }
1006 if (mHttpAuthenticationDialog != null) {
1007 String title = ((TextView) mHttpAuthenticationDialog
1008 .findViewById(com.android.internal.R.id.alertTitle)).getText()
1009 .toString();
1010 String name = ((TextView) mHttpAuthenticationDialog
1011 .findViewById(R.id.username_edit)).getText().toString();
1012 String password = ((TextView) mHttpAuthenticationDialog
1013 .findViewById(R.id.password_edit)).getText().toString();
1014 int focusId = mHttpAuthenticationDialog.getCurrentFocus()
1015 .getId();
1016 mHttpAuthenticationDialog.dismiss();
1017 showHttpAuthentication(mHttpAuthHandler, null, null, title,
1018 name, password, focusId);
1019 }
1020 if (mFindDialog != null && mFindDialog.isShowing()) {
1021 mFindDialog.onConfigurationChanged(newConfig);
1022 }
1023 }
1024
1025 @Override public void onLowMemory() {
1026 super.onLowMemory();
1027 mTabControl.freeMemory();
1028 }
1029
Mike Reed7bfa63b2009-05-28 11:08:32 -04001030 private boolean resumeWebViewTimers() {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001031 if ((!mActivityInPause && !mPageStarted) ||
1032 (mActivityInPause && mPageStarted)) {
1033 CookieSyncManager.getInstance().startSync();
1034 WebView w = mTabControl.getCurrentWebView();
1035 if (w != null) {
1036 w.resumeTimers();
1037 }
1038 return true;
1039 } else {
1040 return false;
1041 }
1042 }
1043
Mike Reed7bfa63b2009-05-28 11:08:32 -04001044 private boolean pauseWebViewTimers() {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001045 if (mActivityInPause && !mPageStarted) {
1046 CookieSyncManager.getInstance().stopSync();
1047 WebView w = mTabControl.getCurrentWebView();
1048 if (w != null) {
1049 w.pauseTimers();
1050 }
1051 return true;
1052 } else {
1053 return false;
1054 }
1055 }
1056
Leon Scroggins1f005d32009-08-10 17:36:42 -04001057 // FIXME: Do we want to call this when loading google for the first time?
The Android Open Source Project0c908882009-03-03 19:32:16 -08001058 /*
1059 * This function is called when we are launching for the first time. We
1060 * are waiting for the login credentials before loading Google home
1061 * pages. This way the user will be logged in straight away.
1062 */
1063 private void waitForCredentials() {
1064 // Show a toast
1065 mCredsDlg = new ProgressDialog(this);
1066 mCredsDlg.setIndeterminate(true);
1067 mCredsDlg.setMessage(getText(R.string.retrieving_creds_dlg_msg));
1068 // If the user cancels the operation, then cancel the Google
1069 // Credentials request.
1070 mCredsDlg.setCancelMessage(mHandler.obtainMessage(CANCEL_CREDS_REQUEST));
1071 mCredsDlg.show();
1072
1073 // We set a timeout for the retrieval of credentials in onResume()
1074 // as that is when we have freed up some CPU time to get
1075 // the login credentials.
1076 }
1077
1078 /*
1079 * If we have received the credentials or we have timed out and we are
1080 * showing the credentials dialog, then it is time to move on.
1081 */
1082 private void resumeAfterCredentials() {
1083 if (mCredsDlg == null) {
1084 return;
1085 }
1086
1087 // Clear the toast
1088 if (mCredsDlg.isShowing()) {
1089 mCredsDlg.dismiss();
1090 }
1091 mCredsDlg = null;
1092
1093 // Clear any pending timeout
1094 mHandler.removeMessages(CANCEL_CREDS_REQUEST);
1095
1096 // Load the page
1097 WebView w = mTabControl.getCurrentWebView();
1098 if (w != null) {
1099 w.loadUrl(mSettings.getHomePage());
1100 }
1101
1102 // Update the settings, need to do this last as it can take a moment
1103 // to persist the settings. In the mean time we could be loading
1104 // content.
1105 mSettings.setLoginInitialized(this);
1106 }
1107
1108 // Open the icon database and retain all the icons for visited sites.
1109 private void retainIconsOnStartup() {
1110 final WebIconDatabase db = WebIconDatabase.getInstance();
1111 db.open(getDir("icons", 0).getPath());
1112 try {
1113 Cursor c = Browser.getAllBookmarks(mResolver);
1114 if (!c.moveToFirst()) {
1115 c.deactivate();
1116 return;
1117 }
1118 int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL);
1119 do {
1120 String url = c.getString(urlIndex);
1121 db.retainIconForPageUrl(url);
1122 } while (c.moveToNext());
1123 c.deactivate();
1124 } catch (IllegalStateException e) {
1125 Log.e(LOGTAG, "retainIconsOnStartup", e);
1126 }
1127 }
1128
1129 // Helper method for getting the top window.
1130 WebView getTopWindow() {
1131 return mTabControl.getCurrentTopWebView();
1132 }
1133
1134 @Override
1135 public boolean onCreateOptionsMenu(Menu menu) {
1136 super.onCreateOptionsMenu(menu);
1137
1138 MenuInflater inflater = getMenuInflater();
1139 inflater.inflate(R.menu.browser, menu);
1140 mMenu = menu;
1141 updateInLoadMenuItems();
1142 return true;
1143 }
1144
1145 /**
1146 * As the menu can be open when loading state changes
1147 * we must manually update the state of the stop/reload menu
1148 * item
1149 */
1150 private void updateInLoadMenuItems() {
1151 if (mMenu == null) {
1152 return;
1153 }
1154 MenuItem src = mInLoad ?
1155 mMenu.findItem(R.id.stop_menu_id):
1156 mMenu.findItem(R.id.reload_menu_id);
1157 MenuItem dest = mMenu.findItem(R.id.stop_reload_menu_id);
1158 dest.setIcon(src.getIcon());
1159 dest.setTitle(src.getTitle());
1160 }
1161
1162 @Override
1163 public boolean onContextItemSelected(MenuItem item) {
1164 // chording is not an issue with context menus, but we use the same
1165 // options selector, so set mCanChord to true so we can access them.
1166 mCanChord = true;
1167 int id = item.getItemId();
1168 final WebView webView = getTopWindow();
Leon Scroggins0d7ae0e2009-06-05 11:04:45 -04001169 if (null == webView) {
1170 return false;
1171 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001172 final HashMap hrefMap = new HashMap();
1173 hrefMap.put("webview", webView);
1174 final Message msg = mHandler.obtainMessage(
1175 FOCUS_NODE_HREF, id, 0, hrefMap);
1176 switch (id) {
1177 // -- Browser context menu
1178 case R.id.open_context_menu_id:
1179 case R.id.open_newtab_context_menu_id:
1180 case R.id.bookmark_context_menu_id:
1181 case R.id.save_link_context_menu_id:
1182 case R.id.share_link_context_menu_id:
1183 case R.id.copy_link_context_menu_id:
1184 webView.requestFocusNodeHref(msg);
1185 break;
1186
1187 default:
1188 // For other context menus
1189 return onOptionsItemSelected(item);
1190 }
1191 mCanChord = false;
1192 return true;
1193 }
1194
1195 private Bundle createGoogleSearchSourceBundle(String source) {
1196 Bundle bundle = new Bundle();
1197 bundle.putString(SearchManager.SOURCE, source);
1198 return bundle;
1199 }
1200
1201 /**
The Android Open Source Project4e5f5872009-03-09 11:52:14 -07001202 * Overriding this to insert a local information bundle
The Android Open Source Project0c908882009-03-03 19:32:16 -08001203 */
1204 @Override
1205 public boolean onSearchRequested() {
Leon Scroggins5bbe9802009-07-31 13:10:55 -04001206 String url = (getTopWindow() == null) ? null : getTopWindow().getUrl();
Grace Kloba83f47342009-07-20 10:44:31 -07001207 startSearch(mSettings.getHomePage().equals(url) ? null : url, true,
The Android Open Source Project4e5f5872009-03-09 11:52:14 -07001208 createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_SEARCHKEY), false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001209 return true;
1210 }
1211
1212 @Override
1213 public void startSearch(String initialQuery, boolean selectInitialQuery,
1214 Bundle appSearchData, boolean globalSearch) {
1215 if (appSearchData == null) {
1216 appSearchData = createGoogleSearchSourceBundle(GOOGLE_SEARCH_SOURCE_TYPE);
1217 }
1218 super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
1219 }
1220
Leon Scroggins1f005d32009-08-10 17:36:42 -04001221 /**
1222 * Switch tabs. Called by the TitleBarSet when sliding the title bar
1223 * results in changing tabs.
Leon Scroggins160a7e72009-08-14 18:28:01 -04001224 * @param index Index of the tab to change to, as defined by
1225 * mTabControl.getTabIndex(Tab t).
1226 * @return boolean True if we successfully switched to a different tab. If
1227 * the indexth tab is null, or if that tab is the same as
1228 * the current one, return false.
Leon Scroggins1f005d32009-08-10 17:36:42 -04001229 */
Leon Scroggins160a7e72009-08-14 18:28:01 -04001230 /* package */ boolean switchToTab(int index) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001231 TabControl.Tab tab = mTabControl.getTab(index);
1232 TabControl.Tab currentTab = mTabControl.getCurrentTab();
1233 if (tab == null || tab == currentTab) {
Leon Scroggins160a7e72009-08-14 18:28:01 -04001234 return false;
Leon Scroggins1f005d32009-08-10 17:36:42 -04001235 }
1236 if (currentTab != null) {
1237 // currentTab may be null if it was just removed. In that case,
1238 // we do not need to remove it
1239 removeTabFromContentView(currentTab);
1240 }
1241 removeTabFromContentView(tab);
1242 mTabControl.setCurrentTab(tab);
1243 attachTabToContentView(tab);
Leon Scrogginsa11b75a2009-08-18 10:22:05 -04001244 if (CUSTOM_BROWSER_BAR) {
1245 mTitleBar.setCurrentTab(index);
Leon Scroggins416b7cb2009-08-19 16:26:12 -04001246 WebView view = tab.getWebView();
1247 view.slideIntoFocus();
Leon Scrogginsa11b75a2009-08-18 10:22:05 -04001248 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04001249 return true;
Leon Scroggins1f005d32009-08-10 17:36:42 -04001250 }
1251
1252 /* package */ void closeCurrentWindow() {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001253 final TabControl.Tab current = mTabControl.getCurrentTab();
Leon Scroggins160a7e72009-08-14 18:28:01 -04001254 if (mTabControl.getTabCount() == 1) {
1255 // This is the last tab. Open a new one, as well as the history
1256 // picker, and close the current one.
1257 TabControl.Tab newTab = openTabAndShow(
1258 BrowserActivity.EMPTY_URL_DATA, false, null);
1259 bookmarksOrHistoryPicker(false, true);
1260 closeTab(current);
1261 mTabControl.setCurrentTab(newTab);
1262 return;
1263 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001264 final TabControl.Tab parent = current.getParentTab();
Leon Scroggins1f005d32009-08-10 17:36:42 -04001265 int indexToShow = -1;
1266 if (parent != null) {
1267 indexToShow = mTabControl.getTabIndex(parent);
1268 } else {
Leon Scroggins160a7e72009-08-14 18:28:01 -04001269 final int currentIndex = mTabControl.getCurrentIndex();
1270 // Try to move to the tab to the right
1271 indexToShow = currentIndex + 1;
1272 if (indexToShow > mTabControl.getTabCount() - 1) {
1273 // Try to move to the tab to the left
1274 indexToShow = currentIndex - 1;
Leon Scroggins1f005d32009-08-10 17:36:42 -04001275 }
1276 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04001277 if (switchToTab(indexToShow)) {
1278 // Close window
1279 closeTab(current);
1280 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001281 }
1282
The Android Open Source Project0c908882009-03-03 19:32:16 -08001283 @Override
1284 public boolean onOptionsItemSelected(MenuItem item) {
1285 if (!mCanChord) {
1286 // The user has already fired a shortcut with this hold down of the
1287 // menu key.
1288 return false;
1289 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001290 if (null == getTopWindow()) {
Leon Scroggins0d7ae0e2009-06-05 11:04:45 -04001291 return false;
1292 }
Grace Kloba6ee9c492009-07-13 10:04:34 -07001293 if (mMenuIsDown) {
1294 // The shortcut action consumes the MENU. Even if it is still down,
1295 // it won't trigger the next shortcut action. In the case of the
1296 // shortcut action triggering a new activity, like Bookmarks, we
1297 // won't get onKeyUp for MENU. So it is important to reset it here.
1298 mMenuIsDown = false;
1299 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001300 switch (item.getItemId()) {
1301 // -- Main menu
Leon Scrogginsa81a7642009-08-31 17:05:41 -04001302 case R.id.new_tab_menu_id:
1303 openTabAndShow(EMPTY_URL_DATA, false, null);
1304 bookmarksOrHistoryPicker(false, true);
1305 break;
1306
Leon Scroggins64b80f32009-08-07 12:03:34 -04001307 case R.id.goto_menu_id:
Leon Scroggins160a7e72009-08-14 18:28:01 -04001308 bookmarksOrHistoryPicker(false, false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001309 break;
1310
Leon Scroggins1f005d32009-08-10 17:36:42 -04001311 case R.id.add_bookmark_menu_id:
1312 Intent i = new Intent(BrowserActivity.this,
1313 AddBookmarkPage.class);
1314 WebView w = getTopWindow();
1315 i.putExtra("url", w.getUrl());
1316 i.putExtra("title", w.getTitle());
1317 startActivity(i);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001318 break;
1319
1320 case R.id.stop_reload_menu_id:
1321 if (mInLoad) {
1322 stopLoading();
1323 } else {
1324 getTopWindow().reload();
1325 }
1326 break;
1327
1328 case R.id.back_menu_id:
1329 getTopWindow().goBack();
1330 break;
1331
1332 case R.id.forward_menu_id:
1333 getTopWindow().goForward();
1334 break;
1335
1336 case R.id.close_menu_id:
1337 // Close the subwindow if it exists.
1338 if (mTabControl.getCurrentSubWindow() != null) {
1339 dismissSubWindow(mTabControl.getCurrentTab());
1340 break;
1341 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001342 closeCurrentWindow();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001343 break;
1344
1345 case R.id.homepage_menu_id:
1346 TabControl.Tab current = mTabControl.getCurrentTab();
1347 if (current != null) {
1348 dismissSubWindow(current);
1349 current.getWebView().loadUrl(mSettings.getHomePage());
1350 }
1351 break;
1352
1353 case R.id.preferences_menu_id:
1354 Intent intent = new Intent(this,
1355 BrowserPreferencesPage.class);
1356 startActivityForResult(intent, PREFERENCES_PAGE);
1357 break;
1358
1359 case R.id.find_menu_id:
1360 if (null == mFindDialog) {
1361 mFindDialog = new FindDialog(this);
1362 }
1363 mFindDialog.setWebView(getTopWindow());
1364 mFindDialog.show();
1365 mMenuState = EMPTY_MENU;
1366 break;
1367
1368 case R.id.select_text_id:
1369 getTopWindow().emulateShiftHeld();
1370 break;
1371 case R.id.page_info_menu_id:
1372 showPageInfo(mTabControl.getCurrentTab(), false);
1373 break;
1374
1375 case R.id.classic_history_menu_id:
Leon Scroggins160a7e72009-08-14 18:28:01 -04001376 bookmarksOrHistoryPicker(true, false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001377 break;
1378
1379 case R.id.share_page_menu_id:
1380 Browser.sendString(this, getTopWindow().getUrl());
1381 break;
1382
1383 case R.id.dump_nav_menu_id:
1384 getTopWindow().debugDump();
1385 break;
1386
1387 case R.id.zoom_in_menu_id:
1388 getTopWindow().zoomIn();
1389 break;
1390
1391 case R.id.zoom_out_menu_id:
1392 getTopWindow().zoomOut();
1393 break;
1394
1395 case R.id.view_downloads_menu_id:
1396 viewDownloads(null);
1397 break;
1398
The Android Open Source Project0c908882009-03-03 19:32:16 -08001399 case R.id.window_one_menu_id:
1400 case R.id.window_two_menu_id:
1401 case R.id.window_three_menu_id:
1402 case R.id.window_four_menu_id:
1403 case R.id.window_five_menu_id:
1404 case R.id.window_six_menu_id:
1405 case R.id.window_seven_menu_id:
1406 case R.id.window_eight_menu_id:
1407 {
1408 int menuid = item.getItemId();
1409 for (int id = 0; id < WINDOW_SHORTCUT_ID_ARRAY.length; id++) {
1410 if (WINDOW_SHORTCUT_ID_ARRAY[id] == menuid) {
1411 TabControl.Tab desiredTab = mTabControl.getTab(id);
1412 if (desiredTab != null &&
1413 desiredTab != mTabControl.getCurrentTab()) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001414 switchToTab(id);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001415 }
1416 break;
1417 }
1418 }
1419 }
1420 break;
1421
1422 default:
1423 if (!super.onOptionsItemSelected(item)) {
1424 return false;
1425 }
1426 // Otherwise fall through.
1427 }
1428 mCanChord = false;
1429 return true;
1430 }
1431
1432 public void closeFind() {
1433 mMenuState = R.id.MAIN_MENU;
1434 }
1435
1436 @Override public boolean onPrepareOptionsMenu(Menu menu)
1437 {
1438 // This happens when the user begins to hold down the menu key, so
1439 // allow them to chord to get a shortcut.
1440 mCanChord = true;
1441 // Note: setVisible will decide whether an item is visible; while
1442 // setEnabled() will decide whether an item is enabled, which also means
1443 // whether the matching shortcut key will function.
1444 super.onPrepareOptionsMenu(menu);
1445 switch (mMenuState) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001446 case EMPTY_MENU:
1447 if (mCurrentMenuState != mMenuState) {
1448 menu.setGroupVisible(R.id.MAIN_MENU, false);
1449 menu.setGroupEnabled(R.id.MAIN_MENU, false);
1450 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001451 }
1452 break;
1453 default:
1454 if (mCurrentMenuState != mMenuState) {
1455 menu.setGroupVisible(R.id.MAIN_MENU, true);
1456 menu.setGroupEnabled(R.id.MAIN_MENU, true);
1457 menu.setGroupEnabled(R.id.MAIN_SHORTCUT_MENU, true);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001458 }
1459 final WebView w = getTopWindow();
1460 boolean canGoBack = false;
1461 boolean canGoForward = false;
1462 boolean isHome = false;
1463 if (w != null) {
1464 canGoBack = w.canGoBack();
1465 canGoForward = w.canGoForward();
1466 isHome = mSettings.getHomePage().equals(w.getUrl());
1467 }
1468 final MenuItem back = menu.findItem(R.id.back_menu_id);
1469 back.setEnabled(canGoBack);
1470
1471 final MenuItem home = menu.findItem(R.id.homepage_menu_id);
1472 home.setEnabled(!isHome);
1473
1474 menu.findItem(R.id.forward_menu_id)
1475 .setEnabled(canGoForward);
1476
Leon Scrogginsa81a7642009-08-31 17:05:41 -04001477 menu.findItem(R.id.new_tab_menu_id).setEnabled(
1478 mTabControl.getTabCount() < TabControl.MAX_TABS);
1479
The Android Open Source Project0c908882009-03-03 19:32:16 -08001480 // decide whether to show the share link option
1481 PackageManager pm = getPackageManager();
1482 Intent send = new Intent(Intent.ACTION_SEND);
1483 send.setType("text/plain");
1484 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1485 menu.findItem(R.id.share_page_menu_id).setVisible(ri != null);
1486
The Android Open Source Project0c908882009-03-03 19:32:16 -08001487 boolean isNavDump = mSettings.isNavDump();
1488 final MenuItem nav = menu.findItem(R.id.dump_nav_menu_id);
1489 nav.setVisible(isNavDump);
1490 nav.setEnabled(isNavDump);
1491 break;
1492 }
1493 mCurrentMenuState = mMenuState;
1494 return true;
1495 }
1496
1497 @Override
1498 public void onCreateContextMenu(ContextMenu menu, View v,
1499 ContextMenuInfo menuInfo) {
1500 WebView webview = (WebView) v;
1501 WebView.HitTestResult result = webview.getHitTestResult();
1502 if (result == null) {
1503 return;
1504 }
1505
1506 int type = result.getType();
1507 if (type == WebView.HitTestResult.UNKNOWN_TYPE) {
1508 Log.w(LOGTAG,
1509 "We should not show context menu when nothing is touched");
1510 return;
1511 }
1512 if (type == WebView.HitTestResult.EDIT_TEXT_TYPE) {
1513 // let TextView handles context menu
1514 return;
1515 }
1516
1517 // Note, http://b/issue?id=1106666 is requesting that
1518 // an inflated menu can be used again. This is not available
1519 // yet, so inflate each time (yuk!)
1520 MenuInflater inflater = getMenuInflater();
1521 inflater.inflate(R.menu.browsercontext, menu);
1522
1523 // Show the correct menu group
1524 String extra = result.getExtra();
1525 menu.setGroupVisible(R.id.PHONE_MENU,
1526 type == WebView.HitTestResult.PHONE_TYPE);
1527 menu.setGroupVisible(R.id.EMAIL_MENU,
1528 type == WebView.HitTestResult.EMAIL_TYPE);
1529 menu.setGroupVisible(R.id.GEO_MENU,
1530 type == WebView.HitTestResult.GEO_TYPE);
1531 menu.setGroupVisible(R.id.IMAGE_MENU,
1532 type == WebView.HitTestResult.IMAGE_TYPE
1533 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1534 menu.setGroupVisible(R.id.ANCHOR_MENU,
1535 type == WebView.HitTestResult.SRC_ANCHOR_TYPE
1536 || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
1537
1538 // Setup custom handling depending on the type
1539 switch (type) {
1540 case WebView.HitTestResult.PHONE_TYPE:
1541 menu.setHeaderTitle(Uri.decode(extra));
1542 menu.findItem(R.id.dial_context_menu_id).setIntent(
1543 new Intent(Intent.ACTION_VIEW, Uri
1544 .parse(WebView.SCHEME_TEL + extra)));
1545 Intent addIntent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
1546 addIntent.putExtra(Insert.PHONE, Uri.decode(extra));
1547 addIntent.setType(Contacts.People.CONTENT_ITEM_TYPE);
1548 menu.findItem(R.id.add_contact_context_menu_id).setIntent(
1549 addIntent);
1550 menu.findItem(R.id.copy_phone_context_menu_id).setOnMenuItemClickListener(
1551 new Copy(extra));
1552 break;
1553
1554 case WebView.HitTestResult.EMAIL_TYPE:
1555 menu.setHeaderTitle(extra);
1556 menu.findItem(R.id.email_context_menu_id).setIntent(
1557 new Intent(Intent.ACTION_VIEW, Uri
1558 .parse(WebView.SCHEME_MAILTO + extra)));
1559 menu.findItem(R.id.copy_mail_context_menu_id).setOnMenuItemClickListener(
1560 new Copy(extra));
1561 break;
1562
1563 case WebView.HitTestResult.GEO_TYPE:
1564 menu.setHeaderTitle(extra);
1565 menu.findItem(R.id.map_context_menu_id).setIntent(
1566 new Intent(Intent.ACTION_VIEW, Uri
1567 .parse(WebView.SCHEME_GEO
1568 + URLEncoder.encode(extra))));
1569 menu.findItem(R.id.copy_geo_context_menu_id).setOnMenuItemClickListener(
1570 new Copy(extra));
1571 break;
1572
1573 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
1574 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
1575 TextView titleView = (TextView) LayoutInflater.from(this)
1576 .inflate(android.R.layout.browser_link_context_header,
1577 null);
1578 titleView.setText(extra);
1579 menu.setHeaderView(titleView);
1580 // decide whether to show the open link in new tab option
1581 menu.findItem(R.id.open_newtab_context_menu_id).setVisible(
1582 mTabControl.getTabCount() < TabControl.MAX_TABS);
1583 PackageManager pm = getPackageManager();
1584 Intent send = new Intent(Intent.ACTION_SEND);
1585 send.setType("text/plain");
1586 ResolveInfo ri = pm.resolveActivity(send, PackageManager.MATCH_DEFAULT_ONLY);
1587 menu.findItem(R.id.share_link_context_menu_id).setVisible(ri != null);
1588 if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
1589 break;
1590 }
1591 // otherwise fall through to handle image part
1592 case WebView.HitTestResult.IMAGE_TYPE:
1593 if (type == WebView.HitTestResult.IMAGE_TYPE) {
1594 menu.setHeaderTitle(extra);
1595 }
1596 menu.findItem(R.id.view_image_context_menu_id).setIntent(
1597 new Intent(Intent.ACTION_VIEW, Uri.parse(extra)));
1598 menu.findItem(R.id.download_context_menu_id).
1599 setOnMenuItemClickListener(new Download(extra));
1600 break;
1601
1602 default:
1603 Log.w(LOGTAG, "We should not get here.");
1604 break;
1605 }
1606 }
1607
The Android Open Source Project0c908882009-03-03 19:32:16 -08001608 // Attach the given tab to the content view.
1609 private void attachTabToContentView(TabControl.Tab t) {
Steve Block2bc69912009-07-30 14:45:13 +01001610 // Attach the container that contains the main WebView and any other UI
1611 // associated with the tab.
1612 mContentView.addView(t.getContainer(), COVER_SCREEN_PARAMS);
Ben Murdochbff2d602009-07-01 20:19:05 +01001613
1614 if (mShouldShowErrorConsole) {
1615 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
1616 if (errorConsole.numberOfErrors() == 0) {
1617 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
1618 } else {
1619 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
1620 }
1621
1622 mErrorConsoleContainer.addView(errorConsole,
1623 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
1624 ViewGroup.LayoutParams.WRAP_CONTENT));
1625 }
1626
The Android Open Source Project0c908882009-03-03 19:32:16 -08001627 // Attach the sub window if necessary
1628 attachSubWindow(t);
1629 // Request focus on the top window.
1630 t.getTopWindow().requestFocus();
1631 }
1632
1633 // Attach a sub window to the main WebView of the given tab.
1634 private void attachSubWindow(TabControl.Tab t) {
1635 // If a sub window exists, attach it to the content view.
1636 final WebView subView = t.getSubWebView();
1637 if (subView != null) {
1638 final View container = t.getSubWebViewContainer();
1639 mContentView.addView(container, COVER_SCREEN_PARAMS);
1640 subView.requestFocus();
1641 }
1642 }
1643
1644 // Remove the given tab from the content view.
1645 private void removeTabFromContentView(TabControl.Tab t) {
Steve Block2bc69912009-07-30 14:45:13 +01001646 // Remove the container that contains the main WebView.
1647 mContentView.removeView(t.getContainer());
Ben Murdochbff2d602009-07-01 20:19:05 +01001648
1649 if (mTabControl.getCurrentErrorConsole(false) != null) {
1650 mErrorConsoleContainer.removeView(mTabControl.getCurrentErrorConsole(false));
1651 }
1652
The Android Open Source Project0c908882009-03-03 19:32:16 -08001653 // Remove the sub window if it exists.
1654 if (t.getSubWebView() != null) {
1655 mContentView.removeView(t.getSubWebViewContainer());
1656 }
1657 }
1658
1659 // Remove the sub window if it exists. Also called by TabControl when the
1660 // user clicks the 'X' to dismiss a sub window.
1661 /* package */ void dismissSubWindow(TabControl.Tab t) {
1662 final WebView mainView = t.getWebView();
1663 if (t.getSubWebView() != null) {
1664 // Remove the container view and request focus on the main WebView.
1665 mContentView.removeView(t.getSubWebViewContainer());
1666 mainView.requestFocus();
1667 // Tell the TabControl to dismiss the subwindow. This will destroy
1668 // the WebView.
1669 mTabControl.dismissSubWindow(t);
1670 }
1671 }
1672
Leon Scroggins1f005d32009-08-10 17:36:42 -04001673 // A wrapper function of {@link #openTabAndShow(UrlData, boolean, String)}
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07001674 // that accepts url as string.
Leon Scroggins1f005d32009-08-10 17:36:42 -04001675 private TabControl.Tab openTabAndShow(String url, boolean closeOnExit,
1676 String appId) {
1677 return openTabAndShow(new UrlData(url), closeOnExit, appId);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001678 }
1679
1680 // This method does a ton of stuff. It will attempt to create a new tab
1681 // if we haven't reached MAX_TABS. Otherwise it uses the current tab. If
Leon Scroggins1f005d32009-08-10 17:36:42 -04001682 // url isn't null, it will load the given url.
1683 /* package */ TabControl.Tab openTabAndShow(UrlData urlData,
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07001684 boolean closeOnExit, String appId) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001685 final boolean newTab = mTabControl.getTabCount() != TabControl.MAX_TABS;
1686 final TabControl.Tab currentTab = mTabControl.getCurrentTab();
1687 if (newTab) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001688 final TabControl.Tab tab = mTabControl.createNewTab(
1689 closeOnExit, appId, urlData.mUrl);
1690 WebView webview = tab.getWebView();
1691 if (CUSTOM_BROWSER_BAR) {
1692 mTitleBar.addTab(webview, true);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001693 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001694 removeTabFromContentView(currentTab);
1695 attachTabToContentView(tab);
Patrick Scott8bbd69f2009-08-14 13:35:53 -04001696 // We must set the new tab as the current tab to reflect the old
1697 // animation behavior.
1698 mTabControl.setCurrentTab(tab);
Leon Scroggins160a7e72009-08-14 18:28:01 -04001699 if (!urlData.isEmpty()) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001700 urlData.loadIn(webview);
1701 }
1702 return tab;
1703 } else {
1704 // Get rid of the subwindow if it exists
1705 dismissSubWindow(currentTab);
1706 if (!urlData.isEmpty()) {
1707 // Load the given url.
1708 urlData.loadIn(currentTab.getWebView());
The Android Open Source Project0c908882009-03-03 19:32:16 -08001709 }
1710 }
Grace Klobac9181842009-04-14 08:53:22 -07001711 return currentTab;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001712 }
1713
Grace Klobac9181842009-04-14 08:53:22 -07001714 private TabControl.Tab openTab(String url) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001715 if (mSettings.openInBackground()) {
The Android Open Source Projectf59ec872009-03-13 13:04:24 -07001716 TabControl.Tab t = mTabControl.createNewTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001717 if (t != null) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001718 WebView view = t.getWebView();
1719 if (CUSTOM_BROWSER_BAR) {
1720 mTitleBar.addTab(view, false);
1721 }
1722 view.loadUrl(url);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001723 }
Grace Klobac9181842009-04-14 08:53:22 -07001724 return t;
The Android Open Source Project0c908882009-03-03 19:32:16 -08001725 } else {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001726 return openTabAndShow(url, false, null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001727 }
1728 }
1729
1730 private class Copy implements OnMenuItemClickListener {
1731 private CharSequence mText;
1732
1733 public boolean onMenuItemClick(MenuItem item) {
1734 copy(mText);
1735 return true;
1736 }
1737
1738 public Copy(CharSequence toCopy) {
1739 mText = toCopy;
1740 }
1741 }
1742
1743 private class Download implements OnMenuItemClickListener {
1744 private String mText;
1745
1746 public boolean onMenuItemClick(MenuItem item) {
1747 onDownloadStartNoStream(mText, null, null, null, -1);
1748 return true;
1749 }
1750
1751 public Download(String toDownload) {
1752 mText = toDownload;
1753 }
1754 }
1755
1756 private void copy(CharSequence text) {
1757 try {
1758 IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
1759 if (clip != null) {
1760 clip.setClipboardText(text);
1761 }
1762 } catch (android.os.RemoteException e) {
1763 Log.e(LOGTAG, "Copy failed", e);
1764 }
1765 }
1766
1767 /**
1768 * Resets the browser title-view to whatever it must be (for example, if we
1769 * load a page from history).
1770 */
1771 private void resetTitle() {
1772 resetLockIcon();
1773 resetTitleIconAndProgress();
1774 }
1775
1776 /**
1777 * Resets the browser title-view to whatever it must be
1778 * (for example, if we had a loading error)
1779 * When we have a new page, we call resetTitle, when we
1780 * have to reset the titlebar to whatever it used to be
1781 * (for example, if the user chose to stop loading), we
1782 * call resetTitleAndRevertLockIcon.
1783 */
1784 /* package */ void resetTitleAndRevertLockIcon() {
1785 revertLockIcon();
1786 resetTitleIconAndProgress();
1787 }
1788
1789 /**
1790 * Reset the title, favicon, and progress.
1791 */
1792 private void resetTitleIconAndProgress() {
1793 WebView current = mTabControl.getCurrentWebView();
1794 if (current == null) {
1795 return;
1796 }
1797 resetTitleAndIcon(current);
1798 int progress = current.getProgress();
The Android Open Source Project0c908882009-03-03 19:32:16 -08001799 mWebChromeClient.onProgressChanged(current, progress);
1800 }
1801
1802 // Reset the title and the icon based on the given item.
1803 private void resetTitleAndIcon(WebView view) {
1804 WebHistoryItem item = view.copyBackForwardList().getCurrentItem();
1805 if (item != null) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001806 setUrlTitle(item.getUrl(), item.getTitle(), view);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001807 setFavicon(item.getFavicon());
1808 } else {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001809 setUrlTitle(null, null, view);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001810 setFavicon(null);
1811 }
1812 }
1813
1814 /**
1815 * Sets a title composed of the URL and the title string.
1816 * @param url The URL of the site being loaded.
1817 * @param title The title of the site being loaded.
1818 */
Leon Scroggins1f005d32009-08-10 17:36:42 -04001819 private void setUrlTitle(String url, String title, WebView view) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001820 mUrl = url;
1821 mTitle = title;
1822
Leon Scroggins1f005d32009-08-10 17:36:42 -04001823 if (CUSTOM_BROWSER_BAR) {
1824 mTitleBar.setTitleAndUrl(title, url, view);
1825 } else {
1826 setTitle(buildUrlTitle(url, title));
The Android Open Source Project0c908882009-03-03 19:32:16 -08001827 }
1828 }
1829
1830 /**
1831 * Builds and returns the page title, which is some
1832 * combination of the page URL and title.
1833 * @param url The URL of the site being loaded.
1834 * @param title The title of the site being loaded.
1835 * @return The page title.
1836 */
1837 private String buildUrlTitle(String url, String title) {
1838 String urlTitle = "";
1839
1840 if (url != null) {
1841 String titleUrl = buildTitleUrl(url);
1842
1843 if (title != null && 0 < title.length()) {
1844 if (titleUrl != null && 0 < titleUrl.length()) {
1845 urlTitle = titleUrl + ": " + title;
1846 } else {
1847 urlTitle = title;
1848 }
1849 } else {
1850 if (titleUrl != null) {
1851 urlTitle = titleUrl;
1852 }
1853 }
1854 }
1855
1856 return urlTitle;
1857 }
1858
1859 /**
1860 * @param url The URL to build a title version of the URL from.
1861 * @return The title version of the URL or null if fails.
1862 * The title version of the URL can be either the URL hostname,
1863 * or the hostname with an "https://" prefix (for secure URLs),
1864 * or an empty string if, for example, the URL in question is a
1865 * file:// URL with no hostname.
1866 */
Leon Scroggins32e14a62009-06-11 10:26:34 -04001867 /* package */ static String buildTitleUrl(String url) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001868 String titleUrl = null;
1869
1870 if (url != null) {
1871 try {
1872 // parse the url string
1873 URL urlObj = new URL(url);
1874 if (urlObj != null) {
1875 titleUrl = "";
1876
1877 String protocol = urlObj.getProtocol();
1878 String host = urlObj.getHost();
1879
1880 if (host != null && 0 < host.length()) {
1881 titleUrl = host;
1882 if (protocol != null) {
1883 // if a secure site, add an "https://" prefix!
1884 if (protocol.equalsIgnoreCase("https")) {
1885 titleUrl = protocol + "://" + host;
1886 }
1887 }
1888 }
1889 }
1890 } catch (MalformedURLException e) {}
1891 }
1892
1893 return titleUrl;
1894 }
1895
1896 // Set the favicon in the title bar.
1897 private void setFavicon(Bitmap icon) {
Leon Scroggins81db3662009-06-04 17:45:11 -04001898 if (CUSTOM_BROWSER_BAR) {
1899 Drawable[] array = new Drawable[3];
1900 array[0] = new PaintDrawable(Color.BLACK);
1901 PaintDrawable p = new PaintDrawable(Color.WHITE);
1902 array[1] = p;
1903 if (icon == null) {
1904 array[2] = mGenericFavicon;
1905 } else {
1906 array[2] = new BitmapDrawable(icon);
1907 }
1908 LayerDrawable d = new LayerDrawable(array);
1909 d.setLayerInset(1, 1, 1, 1, 1);
1910 d.setLayerInset(2, 2, 2, 2, 2);
Leon Scroggins1f005d32009-08-10 17:36:42 -04001911 mTitleBar.setFavicon(d, getTopWindow());
The Android Open Source Project0c908882009-03-03 19:32:16 -08001912 } else {
Leon Scroggins81db3662009-06-04 17:45:11 -04001913 Drawable[] array = new Drawable[2];
1914 PaintDrawable p = new PaintDrawable(Color.WHITE);
1915 p.setCornerRadius(3f);
1916 array[0] = p;
1917 if (icon == null) {
1918 array[1] = mGenericFavicon;
1919 } else {
1920 array[1] = new BitmapDrawable(icon);
1921 }
1922 LayerDrawable d = new LayerDrawable(array);
1923 d.setLayerInset(1, 2, 2, 2, 2);
1924 getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, d);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001925 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08001926 }
1927
1928 /**
1929 * Saves the current lock-icon state before resetting
1930 * the lock icon. If we have an error, we may need to
1931 * roll back to the previous state.
1932 */
1933 private void saveLockIcon() {
1934 mPrevLockType = mLockIconType;
1935 }
1936
1937 /**
1938 * Reverts the lock-icon state to the last saved state,
1939 * for example, if we had an error, and need to cancel
1940 * the load.
1941 */
1942 private void revertLockIcon() {
1943 mLockIconType = mPrevLockType;
1944
Dave Bort31a6d1c2009-04-13 15:56:49 -07001945 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08001946 Log.v(LOGTAG, "BrowserActivity.revertLockIcon:" +
1947 " revert lock icon to " + mLockIconType);
1948 }
1949
1950 updateLockIconImage(mLockIconType);
1951 }
1952
Leon Scroggins1f005d32009-08-10 17:36:42 -04001953 /**
1954 * Close the tab after removing its associated title bar.
1955 */
1956 private void closeTab(TabControl.Tab t) {
Leon Scrogginsa11b75a2009-08-18 10:22:05 -04001957 if (CUSTOM_BROWSER_BAR) {
1958 mTitleBar.removeTab(mTabControl.getTabIndex(t));
1959 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04001960 mTabControl.removeTab(t);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001961 }
1962
1963 private void goBackOnePageOrQuit() {
1964 TabControl.Tab current = mTabControl.getCurrentTab();
1965 if (current == null) {
1966 /*
1967 * Instead of finishing the activity, simply push this to the back
1968 * of the stack and let ActivityManager to choose the foreground
1969 * activity. As BrowserActivity is singleTask, it will be always the
1970 * root of the task. So we can use either true or false for
1971 * moveTaskToBack().
1972 */
1973 moveTaskToBack(true);
1974 }
1975 WebView w = current.getWebView();
1976 if (w.canGoBack()) {
1977 w.goBack();
1978 } else {
1979 // Check to see if we are closing a window that was created by
1980 // another window. If so, we switch back to that window.
1981 TabControl.Tab parent = current.getParentTab();
1982 if (parent != null) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04001983 switchToTab(mTabControl.getTabIndex(parent));
1984 // Now we close the other tab
1985 closeTab(current);
The Android Open Source Project0c908882009-03-03 19:32:16 -08001986 } else {
1987 if (current.closeOnExit()) {
1988 if (mTabControl.getTabCount() == 1) {
1989 finish();
1990 return;
1991 }
Mike Reed7bfa63b2009-05-28 11:08:32 -04001992 // call pauseWebViewTimers() now, we won't be able to call
1993 // it in onPause() as the WebView won't be valid.
Grace Klobaec1b5ad2009-08-18 08:42:32 -07001994 // Temporarily change mActivityInPause to be true as
1995 // pauseWebViewTimers() will do nothing if mActivityInPause
1996 // is false.
Grace Kloba918e1d72009-08-13 14:55:06 -07001997 boolean savedState = mActivityInPause;
1998 if (savedState) {
Grace Klobaec1b5ad2009-08-18 08:42:32 -07001999 Log.e(LOGTAG, "BrowserActivity is already paused "
2000 + "while handing goBackOnePageOrQuit.");
Grace Kloba918e1d72009-08-13 14:55:06 -07002001 }
2002 mActivityInPause = true;
Mike Reed7bfa63b2009-05-28 11:08:32 -04002003 pauseWebViewTimers();
Grace Kloba918e1d72009-08-13 14:55:06 -07002004 mActivityInPause = savedState;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002005 removeTabFromContentView(current);
2006 mTabControl.removeTab(current);
2007 }
2008 /*
2009 * Instead of finishing the activity, simply push this to the back
2010 * of the stack and let ActivityManager to choose the foreground
2011 * activity. As BrowserActivity is singleTask, it will be always the
2012 * root of the task. So we can use either true or false for
2013 * moveTaskToBack().
2014 */
2015 moveTaskToBack(true);
2016 }
2017 }
2018 }
2019
2020 public KeyTracker.State onKeyTracker(int keyCode,
2021 KeyEvent event,
2022 KeyTracker.Stage stage,
2023 int duration) {
2024 // if onKeyTracker() is called after activity onStop()
2025 // because of accumulated key events,
2026 // we should ignore it as browser is not active any more.
2027 WebView topWindow = getTopWindow();
Andrei Popescuadc008d2009-06-26 14:11:30 +01002028 if (topWindow == null && mCustomView == null)
The Android Open Source Project0c908882009-03-03 19:32:16 -08002029 return KeyTracker.State.NOT_TRACKING;
2030
2031 if (keyCode == KeyEvent.KEYCODE_BACK) {
Andrei Popescuadc008d2009-06-26 14:11:30 +01002032 // Check if a custom view is currently showing and, if it is, hide it.
2033 if (mCustomView != null) {
2034 mWebChromeClient.onHideCustomView();
2035 return KeyTracker.State.DONE_TRACKING;
2036 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002037 if (stage == KeyTracker.Stage.LONG_REPEAT) {
Leon Scroggins160a7e72009-08-14 18:28:01 -04002038 bookmarksOrHistoryPicker(true, false);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002039 return KeyTracker.State.DONE_TRACKING;
2040 } else if (stage == KeyTracker.Stage.UP) {
2041 // FIXME: Currently, we do not have a notion of the
2042 // history picker for the subwindow, but maybe we
2043 // should?
2044 WebView subwindow = mTabControl.getCurrentSubWindow();
2045 if (subwindow != null) {
2046 if (subwindow.canGoBack()) {
2047 subwindow.goBack();
2048 } else {
2049 dismissSubWindow(mTabControl.getCurrentTab());
2050 }
2051 } else {
2052 goBackOnePageOrQuit();
2053 }
2054 return KeyTracker.State.DONE_TRACKING;
2055 }
2056 return KeyTracker.State.KEEP_TRACKING;
2057 }
2058 return KeyTracker.State.NOT_TRACKING;
2059 }
2060
2061 @Override public boolean onKeyDown(int keyCode, KeyEvent event) {
2062 if (keyCode == KeyEvent.KEYCODE_MENU) {
2063 mMenuIsDown = true;
Grace Kloba6ee9c492009-07-13 10:04:34 -07002064 } else if (mMenuIsDown) {
2065 // The default key mode is DEFAULT_KEYS_SEARCH_LOCAL. As the MENU is
2066 // still down, we don't want to trigger the search. Pretend to
2067 // consume the key and do nothing.
2068 return true;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002069 }
2070 boolean handled = mKeyTracker.doKeyDown(keyCode, event);
2071 if (!handled) {
2072 switch (keyCode) {
2073 case KeyEvent.KEYCODE_SPACE:
2074 if (event.isShiftPressed()) {
2075 getTopWindow().pageUp(false);
2076 } else {
2077 getTopWindow().pageDown(false);
2078 }
2079 handled = true;
2080 break;
2081
2082 default:
2083 break;
2084 }
2085 }
2086 return handled || super.onKeyDown(keyCode, event);
2087 }
2088
2089 @Override public boolean onKeyUp(int keyCode, KeyEvent event) {
2090 if (keyCode == KeyEvent.KEYCODE_MENU) {
2091 mMenuIsDown = false;
2092 }
2093 return mKeyTracker.doKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
2094 }
2095
2096 private void stopLoading() {
2097 resetTitleAndRevertLockIcon();
2098 WebView w = getTopWindow();
2099 w.stopLoading();
2100 mWebViewClient.onPageFinished(w, w.getUrl());
2101
2102 cancelStopToast();
2103 mStopToast = Toast
2104 .makeText(this, R.string.stopping, Toast.LENGTH_SHORT);
2105 mStopToast.show();
2106 }
2107
2108 private void cancelStopToast() {
2109 if (mStopToast != null) {
2110 mStopToast.cancel();
2111 mStopToast = null;
2112 }
2113 }
2114
2115 // called by a non-UI thread to post the message
2116 public void postMessage(int what, int arg1, int arg2, Object obj) {
2117 mHandler.sendMessage(mHandler.obtainMessage(what, arg1, arg2, obj));
2118 }
2119
2120 // public message ids
2121 public final static int LOAD_URL = 1001;
2122 public final static int STOP_LOAD = 1002;
2123
2124 // Message Ids
2125 private static final int FOCUS_NODE_HREF = 102;
2126 private static final int CANCEL_CREDS_REQUEST = 103;
Grace Kloba92c18a52009-07-31 23:48:32 -07002127 private static final int RELEASE_WAKELOCK = 107;
The Android Open Source Project0c908882009-03-03 19:32:16 -08002128
2129 // Private handler for handling javascript and saving passwords
2130 private Handler mHandler = new Handler() {
2131
2132 public void handleMessage(Message msg) {
2133 switch (msg.what) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002134 case FOCUS_NODE_HREF:
2135 String url = (String) msg.getData().get("url");
2136 if (url == null || url.length() == 0) {
2137 break;
2138 }
2139 HashMap focusNodeMap = (HashMap) msg.obj;
2140 WebView view = (WebView) focusNodeMap.get("webview");
2141 // Only apply the action if the top window did not change.
2142 if (getTopWindow() != view) {
2143 break;
2144 }
2145 switch (msg.arg1) {
2146 case R.id.open_context_menu_id:
2147 case R.id.view_image_context_menu_id:
2148 loadURL(getTopWindow(), url);
2149 break;
2150 case R.id.open_newtab_context_menu_id:
Grace Klobac9181842009-04-14 08:53:22 -07002151 final TabControl.Tab parent = mTabControl
2152 .getCurrentTab();
2153 final TabControl.Tab newTab = openTab(url);
2154 if (newTab != parent) {
2155 parent.addChildTab(newTab);
2156 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002157 break;
2158 case R.id.bookmark_context_menu_id:
2159 Intent intent = new Intent(BrowserActivity.this,
2160 AddBookmarkPage.class);
2161 intent.putExtra("url", url);
2162 startActivity(intent);
2163 break;
2164 case R.id.share_link_context_menu_id:
2165 Browser.sendString(BrowserActivity.this, url);
2166 break;
2167 case R.id.copy_link_context_menu_id:
2168 copy(url);
2169 break;
2170 case R.id.save_link_context_menu_id:
2171 case R.id.download_context_menu_id:
2172 onDownloadStartNoStream(url, null, null, null, -1);
2173 break;
2174 }
2175 break;
2176
2177 case LOAD_URL:
2178 loadURL(getTopWindow(), (String) msg.obj);
2179 break;
2180
2181 case STOP_LOAD:
2182 stopLoading();
2183 break;
2184
2185 case CANCEL_CREDS_REQUEST:
2186 resumeAfterCredentials();
2187 break;
2188
The Android Open Source Project0c908882009-03-03 19:32:16 -08002189 case RELEASE_WAKELOCK:
2190 if (mWakeLock.isHeld()) {
2191 mWakeLock.release();
2192 }
2193 break;
2194 }
2195 }
2196 };
2197
Leon Scroggins89c6d362009-07-15 16:54:37 -04002198 private void updateScreenshot(WebView view) {
2199 // If this is a bookmarked site, add a screenshot to the database.
2200 // FIXME: When should we update? Every time?
2201 // FIXME: Would like to make sure there is actually something to
2202 // draw, but the API for that (WebViewCore.pictureReady()) is not
2203 // currently accessible here.
Patrick Scott3918d442009-08-04 13:22:29 -04002204 ContentResolver cr = getContentResolver();
2205 final Cursor c = BrowserBookmarksAdapter.queryBookmarksForUrl(
Leon Scrogginsa5d669e2009-08-05 14:07:58 -04002206 cr, view.getOriginalUrl(), view.getUrl(), false);
Patrick Scott3918d442009-08-04 13:22:29 -04002207 if (c != null) {
Leon Scroggins89c6d362009-07-15 16:54:37 -04002208 boolean succeed = c.moveToFirst();
2209 ContentValues values = null;
2210 while (succeed) {
2211 if (values == null) {
2212 final ByteArrayOutputStream os
2213 = new ByteArrayOutputStream();
2214 Picture thumbnail = view.capturePicture();
2215 // Keep width and height in sync with BrowserBookmarksPage
2216 // and bookmark_thumb
2217 Bitmap bm = Bitmap.createBitmap(100, 80,
2218 Bitmap.Config.ARGB_4444);
2219 Canvas canvas = new Canvas(bm);
2220 // May need to tweak these values to determine what is the
2221 // best scale factor
2222 canvas.scale(.5f, .5f);
2223 thumbnail.draw(canvas);
2224 bm.compress(Bitmap.CompressFormat.PNG, 100, os);
2225 values = new ContentValues();
2226 values.put(Browser.BookmarkColumns.THUMBNAIL,
2227 os.toByteArray());
2228 }
2229 cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI,
2230 c.getInt(0)), values, null, null);
2231 succeed = c.moveToNext();
2232 }
2233 c.close();
2234 }
2235 }
2236
The Android Open Source Project0c908882009-03-03 19:32:16 -08002237 // -------------------------------------------------------------------------
2238 // WebViewClient implementation.
2239 //-------------------------------------------------------------------------
2240
2241 // Use in overrideUrlLoading
2242 /* package */ final static String SCHEME_WTAI = "wtai://wp/";
2243 /* package */ final static String SCHEME_WTAI_MC = "wtai://wp/mc;";
2244 /* package */ final static String SCHEME_WTAI_SD = "wtai://wp/sd;";
2245 /* package */ final static String SCHEME_WTAI_AP = "wtai://wp/ap;";
2246
2247 /* package */ WebViewClient getWebViewClient() {
2248 return mWebViewClient;
2249 }
2250
Patrick Scott3918d442009-08-04 13:22:29 -04002251 private void updateIcon(WebView view, Bitmap icon) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002252 if (icon != null) {
2253 BrowserBookmarksAdapter.updateBookmarkFavicon(mResolver,
Patrick Scott3918d442009-08-04 13:22:29 -04002254 view, icon);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002255 }
2256 setFavicon(icon);
2257 }
2258
2259 private final WebViewClient mWebViewClient = new WebViewClient() {
2260 @Override
2261 public void onPageStarted(WebView view, String url, Bitmap favicon) {
2262 resetLockIcon(url);
Leon Scroggins1f005d32009-08-10 17:36:42 -04002263 setUrlTitle(url, null, view);
Ben Murdochbff2d602009-07-01 20:19:05 +01002264
2265 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(false);
2266 if (errorConsole != null) {
2267 errorConsole.clearErrorMessages();
2268 if (mShouldShowErrorConsole) {
2269 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
2270 }
2271 }
2272
The Android Open Source Project0c908882009-03-03 19:32:16 -08002273 // Call updateIcon instead of setFavicon so the bookmark
2274 // database can be updated.
Patrick Scott3918d442009-08-04 13:22:29 -04002275 updateIcon(view, favicon);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002276
Grace Kloba4d7880f2009-08-12 09:35:42 -07002277 if (mSettings.isTracing()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002278 String host;
2279 try {
2280 WebAddress uri = new WebAddress(url);
2281 host = uri.mHost;
2282 } catch (android.net.ParseException ex) {
Grace Kloba4d7880f2009-08-12 09:35:42 -07002283 host = "browser";
The Android Open Source Project0c908882009-03-03 19:32:16 -08002284 }
2285 host = host.replace('.', '_');
Grace Kloba4d7880f2009-08-12 09:35:42 -07002286 host += ".trace";
The Android Open Source Project0c908882009-03-03 19:32:16 -08002287 mInTrace = true;
Grace Kloba4d7880f2009-08-12 09:35:42 -07002288 Debug.startMethodTracing(host, 20 * 1024 * 1024);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002289 }
2290
2291 // Performance probe
2292 if (false) {
2293 mStart = SystemClock.uptimeMillis();
2294 mProcessStart = Process.getElapsedCpuTime();
2295 long[] sysCpu = new long[7];
2296 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2297 sysCpu, null)) {
2298 mUserStart = sysCpu[0] + sysCpu[1];
2299 mSystemStart = sysCpu[2];
2300 mIdleStart = sysCpu[3];
2301 mIrqStart = sysCpu[4] + sysCpu[5] + sysCpu[6];
2302 }
2303 mUiStart = SystemClock.currentThreadTimeMillis();
2304 }
2305
2306 if (!mPageStarted) {
2307 mPageStarted = true;
Mike Reed7bfa63b2009-05-28 11:08:32 -04002308 // if onResume() has been called, resumeWebViewTimers() does
2309 // nothing.
2310 resumeWebViewTimers();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002311 }
2312
2313 // reset sync timer to avoid sync starts during loading a page
2314 CookieSyncManager.getInstance().resetSync();
2315
2316 mInLoad = true;
Leon Scroggins416b7cb2009-08-19 16:26:12 -04002317 if (CUSTOM_BROWSER_BAR) {
2318 mTitleBar.setVisibility(View.VISIBLE);
2319 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002320 updateInLoadMenuItems();
2321 if (!mIsNetworkUp) {
2322 if ( mAlertDialog == null) {
2323 mAlertDialog = new AlertDialog.Builder(BrowserActivity.this)
2324 .setTitle(R.string.loadSuspendedTitle)
2325 .setMessage(R.string.loadSuspended)
2326 .setPositiveButton(R.string.ok, null)
2327 .show();
2328 }
2329 if (view != null) {
2330 view.setNetworkAvailable(false);
2331 }
2332 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002333 }
2334
2335 @Override
2336 public void onPageFinished(WebView view, String url) {
2337 // Reset the title and icon in case we stopped a provisional
2338 // load.
2339 resetTitleAndIcon(view);
2340
2341 // Update the lock icon image only once we are done loading
2342 updateLockIconImage(mLockIconType);
Leon Scroggins89c6d362009-07-15 16:54:37 -04002343 updateScreenshot(view);
Leon Scrogginsb6b7f9e2009-06-18 12:05:28 -04002344
The Android Open Source Project0c908882009-03-03 19:32:16 -08002345 // Performance probe
The Android Open Source Projectcb9a0bb2009-03-11 12:11:58 -07002346 if (false) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002347 long[] sysCpu = new long[7];
2348 if (Process.readProcFile("/proc/stat", SYSTEM_CPU_FORMAT, null,
2349 sysCpu, null)) {
2350 String uiInfo = "UI thread used "
2351 + (SystemClock.currentThreadTimeMillis() - mUiStart)
2352 + " ms";
Dave Bort31a6d1c2009-04-13 15:56:49 -07002353 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002354 Log.d(LOGTAG, uiInfo);
2355 }
2356 //The string that gets written to the log
2357 String performanceString = "It took total "
2358 + (SystemClock.uptimeMillis() - mStart)
2359 + " ms clock time to load the page."
2360 + "\nbrowser process used "
2361 + (Process.getElapsedCpuTime() - mProcessStart)
2362 + " ms, user processes used "
2363 + (sysCpu[0] + sysCpu[1] - mUserStart) * 10
2364 + " ms, kernel used "
2365 + (sysCpu[2] - mSystemStart) * 10
2366 + " ms, idle took " + (sysCpu[3] - mIdleStart) * 10
2367 + " ms and irq took "
2368 + (sysCpu[4] + sysCpu[5] + sysCpu[6] - mIrqStart)
2369 * 10 + " ms, " + uiInfo;
Dave Bort31a6d1c2009-04-13 15:56:49 -07002370 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002371 Log.d(LOGTAG, performanceString + "\nWebpage: " + url);
2372 }
2373 if (url != null) {
2374 // strip the url to maintain consistency
2375 String newUrl = new String(url);
2376 if (newUrl.startsWith("http://www.")) {
2377 newUrl = newUrl.substring(11);
2378 } else if (newUrl.startsWith("http://")) {
2379 newUrl = newUrl.substring(7);
2380 } else if (newUrl.startsWith("https://www.")) {
2381 newUrl = newUrl.substring(12);
2382 } else if (newUrl.startsWith("https://")) {
2383 newUrl = newUrl.substring(8);
2384 }
Dave Bort31a6d1c2009-04-13 15:56:49 -07002385 if (LOGD_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002386 Log.d(LOGTAG, newUrl + " loaded");
2387 }
2388 /*
2389 if (sWhiteList.contains(newUrl)) {
2390 // The string that gets pushed to the statistcs
2391 // service
2392 performanceString = performanceString
2393 + "\nWebpage: "
2394 + newUrl
2395 + "\nCarrier: "
2396 + android.os.SystemProperties
2397 .get("gsm.sim.operator.alpha");
2398 if (mWebView != null
2399 && mWebView.getContext() != null
2400 && mWebView.getContext().getSystemService(
2401 Context.CONNECTIVITY_SERVICE) != null) {
2402 ConnectivityManager cManager =
2403 (ConnectivityManager) mWebView
2404 .getContext().getSystemService(
2405 Context.CONNECTIVITY_SERVICE);
2406 NetworkInfo nInfo = cManager
2407 .getActiveNetworkInfo();
2408 if (nInfo != null) {
2409 performanceString = performanceString
2410 + "\nNetwork Type: "
2411 + nInfo.getType().toString();
2412 }
2413 }
2414 Checkin.logEvent(mResolver,
2415 Checkin.Events.Tag.WEBPAGE_LOAD,
2416 performanceString);
2417 Log.w(LOGTAG, "pushed to the statistics service");
2418 }
2419 */
2420 }
2421 }
2422 }
2423
2424 if (mInTrace) {
2425 mInTrace = false;
2426 Debug.stopMethodTracing();
2427 }
2428
2429 if (mPageStarted) {
2430 mPageStarted = false;
Mike Reed7bfa63b2009-05-28 11:08:32 -04002431 // pauseWebViewTimers() will do nothing and return false if
2432 // onPause() is not called yet.
2433 if (pauseWebViewTimers()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002434 if (mWakeLock.isHeld()) {
2435 mHandler.removeMessages(RELEASE_WAKELOCK);
2436 mWakeLock.release();
2437 }
2438 }
2439 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002440 }
2441
2442 // return true if want to hijack the url to let another app to handle it
2443 @Override
2444 public boolean shouldOverrideUrlLoading(WebView view, String url) {
2445 if (url.startsWith(SCHEME_WTAI)) {
2446 // wtai://wp/mc;number
2447 // number=string(phone-number)
2448 if (url.startsWith(SCHEME_WTAI_MC)) {
2449 Intent intent = new Intent(Intent.ACTION_VIEW,
2450 Uri.parse(WebView.SCHEME_TEL +
2451 url.substring(SCHEME_WTAI_MC.length())));
2452 startActivity(intent);
2453 return true;
2454 }
2455 // wtai://wp/sd;dtmf
2456 // dtmf=string(dialstring)
2457 if (url.startsWith(SCHEME_WTAI_SD)) {
2458 // TODO
2459 // only send when there is active voice connection
2460 return false;
2461 }
2462 // wtai://wp/ap;number;name
2463 // number=string(phone-number)
2464 // name=string
2465 if (url.startsWith(SCHEME_WTAI_AP)) {
2466 // TODO
2467 return false;
2468 }
2469 }
2470
Dianne Hackborn99189432009-06-17 18:06:18 -07002471 // The "about:" schemes are internal to the browser; don't
2472 // want these to be dispatched to other apps.
2473 if (url.startsWith("about:")) {
2474 return false;
2475 }
Ben Murdochbff2d602009-07-01 20:19:05 +01002476
Dianne Hackborn99189432009-06-17 18:06:18 -07002477 Intent intent;
Ben Murdochbff2d602009-07-01 20:19:05 +01002478
Dianne Hackborn99189432009-06-17 18:06:18 -07002479 // perform generic parsing of the URI to turn it into an Intent.
The Android Open Source Project0c908882009-03-03 19:32:16 -08002480 try {
Dianne Hackborn99189432009-06-17 18:06:18 -07002481 intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
2482 } catch (URISyntaxException ex) {
2483 Log.w("Browser", "Bad URI " + url + ": " + ex.getMessage());
The Android Open Source Project0c908882009-03-03 19:32:16 -08002484 return false;
2485 }
2486
Grace Kloba5b078b52009-06-24 20:23:41 -07002487 // check whether the intent can be resolved. If not, we will see
2488 // whether we can download it from the Market.
2489 if (getPackageManager().resolveActivity(intent, 0) == null) {
2490 String packagename = intent.getPackage();
2491 if (packagename != null) {
2492 intent = new Intent(Intent.ACTION_VIEW, Uri
2493 .parse("market://search?q=pname:" + packagename));
2494 intent.addCategory(Intent.CATEGORY_BROWSABLE);
2495 startActivity(intent);
2496 return true;
2497 } else {
2498 return false;
2499 }
2500 }
2501
Dianne Hackborn99189432009-06-17 18:06:18 -07002502 // sanitize the Intent, ensuring web pages can not bypass browser
2503 // security (only access to BROWSABLE activities).
The Android Open Source Project0c908882009-03-03 19:32:16 -08002504 intent.addCategory(Intent.CATEGORY_BROWSABLE);
Dianne Hackborn99189432009-06-17 18:06:18 -07002505 intent.setComponent(null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002506 try {
2507 if (startActivityIfNeeded(intent, -1)) {
2508 return true;
2509 }
2510 } catch (ActivityNotFoundException ex) {
2511 // ignore the error. If no application can handle the URL,
2512 // eg about:blank, assume the browser can handle it.
2513 }
2514
2515 if (mMenuIsDown) {
2516 openTab(url);
2517 closeOptionsMenu();
2518 return true;
2519 }
2520
2521 return false;
2522 }
2523
2524 /**
2525 * Updates the lock icon. This method is called when we discover another
2526 * resource to be loaded for this page (for example, javascript). While
2527 * we update the icon type, we do not update the lock icon itself until
2528 * we are done loading, it is slightly more secure this way.
2529 */
2530 @Override
2531 public void onLoadResource(WebView view, String url) {
2532 if (url != null && url.length() > 0) {
2533 // It is only if the page claims to be secure
2534 // that we may have to update the lock:
2535 if (mLockIconType == LOCK_ICON_SECURE) {
2536 // If NOT a 'safe' url, change the lock to mixed content!
2537 if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url) || URLUtil.isAboutUrl(url))) {
2538 mLockIconType = LOCK_ICON_MIXED;
Dave Bort31a6d1c2009-04-13 15:56:49 -07002539 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002540 Log.v(LOGTAG, "BrowserActivity.updateLockIcon:" +
2541 " updated lock icon to " + mLockIconType + " due to " + url);
2542 }
2543 }
2544 }
2545 }
2546 }
2547
2548 /**
2549 * Show the dialog, asking the user if they would like to continue after
2550 * an excessive number of HTTP redirects.
2551 */
2552 @Override
2553 public void onTooManyRedirects(WebView view, final Message cancelMsg,
2554 final Message continueMsg) {
2555 new AlertDialog.Builder(BrowserActivity.this)
2556 .setTitle(R.string.browserFrameRedirect)
2557 .setMessage(R.string.browserFrame307Post)
2558 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
2559 public void onClick(DialogInterface dialog, int which) {
2560 continueMsg.sendToTarget();
2561 }})
2562 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
2563 public void onClick(DialogInterface dialog, int which) {
2564 cancelMsg.sendToTarget();
2565 }})
2566 .setOnCancelListener(new OnCancelListener() {
2567 public void onCancel(DialogInterface dialog) {
2568 cancelMsg.sendToTarget();
2569 }})
2570 .show();
2571 }
2572
Patrick Scott37911c72009-03-24 18:02:58 -07002573 // Container class for the next error dialog that needs to be
2574 // displayed.
2575 class ErrorDialog {
2576 public final int mTitle;
2577 public final String mDescription;
2578 public final int mError;
2579 ErrorDialog(int title, String desc, int error) {
2580 mTitle = title;
2581 mDescription = desc;
2582 mError = error;
2583 }
2584 };
2585
2586 private void processNextError() {
2587 if (mQueuedErrors == null) {
2588 return;
2589 }
2590 // The first one is currently displayed so just remove it.
2591 mQueuedErrors.removeFirst();
2592 if (mQueuedErrors.size() == 0) {
2593 mQueuedErrors = null;
2594 return;
2595 }
2596 showError(mQueuedErrors.getFirst());
2597 }
2598
2599 private DialogInterface.OnDismissListener mDialogListener =
2600 new DialogInterface.OnDismissListener() {
2601 public void onDismiss(DialogInterface d) {
2602 processNextError();
2603 }
2604 };
2605 private LinkedList<ErrorDialog> mQueuedErrors;
2606
2607 private void queueError(int err, String desc) {
2608 if (mQueuedErrors == null) {
2609 mQueuedErrors = new LinkedList<ErrorDialog>();
2610 }
2611 for (ErrorDialog d : mQueuedErrors) {
2612 if (d.mError == err) {
2613 // Already saw a similar error, ignore the new one.
2614 return;
2615 }
2616 }
2617 ErrorDialog errDialog = new ErrorDialog(
Patrick Scott5d61a6c2009-08-25 13:52:46 -04002618 err == WebViewClient.ERROR_FILE_NOT_FOUND ?
Patrick Scott37911c72009-03-24 18:02:58 -07002619 R.string.browserFrameFileErrorLabel :
2620 R.string.browserFrameNetworkErrorLabel,
2621 desc, err);
2622 mQueuedErrors.addLast(errDialog);
2623
2624 // Show the dialog now if the queue was empty.
2625 if (mQueuedErrors.size() == 1) {
2626 showError(errDialog);
2627 }
2628 }
2629
2630 private void showError(ErrorDialog errDialog) {
2631 AlertDialog d = new AlertDialog.Builder(BrowserActivity.this)
2632 .setTitle(errDialog.mTitle)
2633 .setMessage(errDialog.mDescription)
2634 .setPositiveButton(R.string.ok, null)
2635 .create();
2636 d.setOnDismissListener(mDialogListener);
2637 d.show();
2638 }
2639
The Android Open Source Project0c908882009-03-03 19:32:16 -08002640 /**
2641 * Show a dialog informing the user of the network error reported by
2642 * WebCore.
2643 */
2644 @Override
2645 public void onReceivedError(WebView view, int errorCode,
2646 String description, String failingUrl) {
Patrick Scott5d61a6c2009-08-25 13:52:46 -04002647 if (errorCode != WebViewClient.ERROR_HOST_LOOKUP &&
2648 errorCode != WebViewClient.ERROR_CONNECT &&
2649 errorCode != WebViewClient.ERROR_BAD_URL &&
2650 errorCode != WebViewClient.ERROR_UNSUPPORTED_SCHEME &&
2651 errorCode != WebViewClient.ERROR_FILE) {
Patrick Scott37911c72009-03-24 18:02:58 -07002652 queueError(errorCode, description);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002653 }
Patrick Scott37911c72009-03-24 18:02:58 -07002654 Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
2655 + " " + description);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002656
2657 // We need to reset the title after an error.
2658 resetTitleAndRevertLockIcon();
2659 }
2660
2661 /**
2662 * Check with the user if it is ok to resend POST data as the page they
2663 * are trying to navigate to is the result of a POST.
2664 */
2665 @Override
2666 public void onFormResubmission(WebView view, final Message dontResend,
2667 final Message resend) {
2668 new AlertDialog.Builder(BrowserActivity.this)
2669 .setTitle(R.string.browserFrameFormResubmitLabel)
2670 .setMessage(R.string.browserFrameFormResubmitMessage)
2671 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
2672 public void onClick(DialogInterface dialog, int which) {
2673 resend.sendToTarget();
2674 }})
2675 .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
2676 public void onClick(DialogInterface dialog, int which) {
2677 dontResend.sendToTarget();
2678 }})
2679 .setOnCancelListener(new OnCancelListener() {
2680 public void onCancel(DialogInterface dialog) {
2681 dontResend.sendToTarget();
2682 }})
2683 .show();
2684 }
2685
2686 /**
2687 * Insert the url into the visited history database.
2688 * @param url The url to be inserted.
2689 * @param isReload True if this url is being reloaded.
2690 * FIXME: Not sure what to do when reloading the page.
2691 */
2692 @Override
2693 public void doUpdateVisitedHistory(WebView view, String url,
2694 boolean isReload) {
2695 if (url.regionMatches(true, 0, "about:", 0, 6)) {
2696 return;
2697 }
2698 Browser.updateVisitedHistory(mResolver, url, true);
2699 WebIconDatabase.getInstance().retainIconForPageUrl(url);
2700 }
2701
2702 /**
2703 * Displays SSL error(s) dialog to the user.
2704 */
2705 @Override
2706 public void onReceivedSslError(
2707 final WebView view, final SslErrorHandler handler, final SslError error) {
2708
2709 if (mSettings.showSecurityWarnings()) {
2710 final LayoutInflater factory =
2711 LayoutInflater.from(BrowserActivity.this);
2712 final View warningsView =
2713 factory.inflate(R.layout.ssl_warnings, null);
2714 final LinearLayout placeholder =
2715 (LinearLayout)warningsView.findViewById(R.id.placeholder);
2716
2717 if (error.hasError(SslError.SSL_UNTRUSTED)) {
2718 LinearLayout ll = (LinearLayout)factory
2719 .inflate(R.layout.ssl_warning, null);
2720 ((TextView)ll.findViewById(R.id.warning))
2721 .setText(R.string.ssl_untrusted);
2722 placeholder.addView(ll);
2723 }
2724
2725 if (error.hasError(SslError.SSL_IDMISMATCH)) {
2726 LinearLayout ll = (LinearLayout)factory
2727 .inflate(R.layout.ssl_warning, null);
2728 ((TextView)ll.findViewById(R.id.warning))
2729 .setText(R.string.ssl_mismatch);
2730 placeholder.addView(ll);
2731 }
2732
2733 if (error.hasError(SslError.SSL_EXPIRED)) {
2734 LinearLayout ll = (LinearLayout)factory
2735 .inflate(R.layout.ssl_warning, null);
2736 ((TextView)ll.findViewById(R.id.warning))
2737 .setText(R.string.ssl_expired);
2738 placeholder.addView(ll);
2739 }
2740
2741 if (error.hasError(SslError.SSL_NOTYETVALID)) {
2742 LinearLayout ll = (LinearLayout)factory
2743 .inflate(R.layout.ssl_warning, null);
2744 ((TextView)ll.findViewById(R.id.warning))
2745 .setText(R.string.ssl_not_yet_valid);
2746 placeholder.addView(ll);
2747 }
2748
2749 new AlertDialog.Builder(BrowserActivity.this)
2750 .setTitle(R.string.security_warning)
2751 .setIcon(android.R.drawable.ic_dialog_alert)
2752 .setView(warningsView)
2753 .setPositiveButton(R.string.ssl_continue,
2754 new DialogInterface.OnClickListener() {
2755 public void onClick(DialogInterface dialog, int whichButton) {
2756 handler.proceed();
2757 }
2758 })
2759 .setNeutralButton(R.string.view_certificate,
2760 new DialogInterface.OnClickListener() {
2761 public void onClick(DialogInterface dialog, int whichButton) {
2762 showSSLCertificateOnError(view, handler, error);
2763 }
2764 })
2765 .setNegativeButton(R.string.cancel,
2766 new DialogInterface.OnClickListener() {
2767 public void onClick(DialogInterface dialog, int whichButton) {
2768 handler.cancel();
2769 BrowserActivity.this.resetTitleAndRevertLockIcon();
2770 }
2771 })
2772 .setOnCancelListener(
2773 new DialogInterface.OnCancelListener() {
2774 public void onCancel(DialogInterface dialog) {
2775 handler.cancel();
2776 BrowserActivity.this.resetTitleAndRevertLockIcon();
2777 }
2778 })
2779 .show();
2780 } else {
2781 handler.proceed();
2782 }
2783 }
2784
2785 /**
2786 * Handles an HTTP authentication request.
2787 *
2788 * @param handler The authentication handler
2789 * @param host The host
2790 * @param realm The realm
2791 */
2792 @Override
2793 public void onReceivedHttpAuthRequest(WebView view,
2794 final HttpAuthHandler handler, final String host, final String realm) {
2795 String username = null;
2796 String password = null;
2797
2798 boolean reuseHttpAuthUsernamePassword =
2799 handler.useHttpAuthUsernamePassword();
2800
2801 if (reuseHttpAuthUsernamePassword &&
2802 (mTabControl.getCurrentWebView() != null)) {
2803 String[] credentials =
2804 mTabControl.getCurrentWebView()
2805 .getHttpAuthUsernamePassword(host, realm);
2806 if (credentials != null && credentials.length == 2) {
2807 username = credentials[0];
2808 password = credentials[1];
2809 }
2810 }
2811
2812 if (username != null && password != null) {
2813 handler.proceed(username, password);
2814 } else {
2815 showHttpAuthentication(handler, host, realm, null, null, null, 0);
2816 }
2817 }
2818
2819 @Override
2820 public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
2821 if (mMenuIsDown) {
2822 // only check shortcut key when MENU is held
2823 return getWindow().isShortcutKey(event.getKeyCode(), event);
2824 } else {
2825 return false;
2826 }
2827 }
2828
2829 @Override
2830 public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
2831 if (view != mTabControl.getCurrentTopWebView()) {
2832 return;
2833 }
2834 if (event.isDown()) {
2835 BrowserActivity.this.onKeyDown(event.getKeyCode(), event);
2836 } else {
2837 BrowserActivity.this.onKeyUp(event.getKeyCode(), event);
2838 }
2839 }
2840 };
2841
2842 //--------------------------------------------------------------------------
2843 // WebChromeClient implementation
2844 //--------------------------------------------------------------------------
2845
2846 /* package */ WebChromeClient getWebChromeClient() {
2847 return mWebChromeClient;
2848 }
2849
2850 private final WebChromeClient mWebChromeClient = new WebChromeClient() {
2851 // Helper method to create a new tab or sub window.
2852 private void createWindow(final boolean dialog, final Message msg) {
2853 if (dialog) {
2854 mTabControl.createSubWindow();
2855 final TabControl.Tab t = mTabControl.getCurrentTab();
2856 attachSubWindow(t);
2857 WebView.WebViewTransport transport =
2858 (WebView.WebViewTransport) msg.obj;
2859 transport.setWebView(t.getSubWebView());
2860 msg.sendToTarget();
2861 } else {
2862 final TabControl.Tab parent = mTabControl.getCurrentTab();
Leon Scroggins1f005d32009-08-10 17:36:42 -04002863 final TabControl.Tab newTab
2864 = openTabAndShow(EMPTY_URL_DATA, false, null);
Grace Klobac9181842009-04-14 08:53:22 -07002865 if (newTab != parent) {
2866 parent.addChildTab(newTab);
2867 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002868 WebView.WebViewTransport transport =
2869 (WebView.WebViewTransport) msg.obj;
2870 transport.setWebView(mTabControl.getCurrentWebView());
Leon Scroggins1f005d32009-08-10 17:36:42 -04002871 msg.sendToTarget();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002872 }
2873 }
2874
2875 @Override
Leon Scroggins416b7cb2009-08-19 16:26:12 -04002876 public void onChangeViewingMode(WebView view, int newViewingMode) {
2877 if (!CUSTOM_BROWSER_BAR || view != getTopWindow()) {
Leon Scroggins4943a312009-08-07 16:12:57 -04002878 return;
2879 }
Leon Scroggins416b7cb2009-08-19 16:26:12 -04002880 switch (newViewingMode) {
2881 case WebView.NO_VIEWING_MODE:
2882 break;
2883 case WebView.OVERVIEW_MODE:
2884 case WebView.READING_MODE_WITH_TITLE_BAR:
2885 case WebView.TITLE_BAR_DISMISS_MODE:
Leon Scroggins4943a312009-08-07 16:12:57 -04002886 mTitleBar.setVisibility(View.VISIBLE);
Leon Scroggins416b7cb2009-08-19 16:26:12 -04002887 break;
2888 case WebView.READING_MODE:
Leon Scroggins4943a312009-08-07 16:12:57 -04002889 mTitleBar.setVisibility(View.GONE);
Leon Scroggins416b7cb2009-08-19 16:26:12 -04002890 break;
2891 default:
2892 break;
Leon Scroggins4943a312009-08-07 16:12:57 -04002893 }
2894 }
2895
2896 @Override
The Android Open Source Project0c908882009-03-03 19:32:16 -08002897 public boolean onCreateWindow(WebView view, final boolean dialog,
2898 final boolean userGesture, final Message resultMsg) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002899 // Short-circuit if we can't create any more tabs or sub windows.
2900 if (dialog && mTabControl.getCurrentSubWindow() != null) {
2901 new AlertDialog.Builder(BrowserActivity.this)
2902 .setTitle(R.string.too_many_subwindows_dialog_title)
2903 .setIcon(android.R.drawable.ic_dialog_alert)
2904 .setMessage(R.string.too_many_subwindows_dialog_message)
2905 .setPositiveButton(R.string.ok, null)
2906 .show();
2907 return false;
2908 } else if (mTabControl.getTabCount() >= TabControl.MAX_TABS) {
2909 new AlertDialog.Builder(BrowserActivity.this)
2910 .setTitle(R.string.too_many_windows_dialog_title)
2911 .setIcon(android.R.drawable.ic_dialog_alert)
2912 .setMessage(R.string.too_many_windows_dialog_message)
2913 .setPositiveButton(R.string.ok, null)
2914 .show();
2915 return false;
2916 }
2917
2918 // Short-circuit if this was a user gesture.
2919 if (userGesture) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002920 createWindow(dialog, resultMsg);
2921 return true;
2922 }
2923
2924 // Allow the popup and create the appropriate window.
2925 final AlertDialog.OnClickListener allowListener =
2926 new AlertDialog.OnClickListener() {
2927 public void onClick(DialogInterface d,
2928 int which) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08002929 createWindow(dialog, resultMsg);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002930 }
2931 };
2932
2933 // Block the popup by returning a null WebView.
2934 final AlertDialog.OnClickListener blockListener =
2935 new AlertDialog.OnClickListener() {
2936 public void onClick(DialogInterface d, int which) {
2937 resultMsg.sendToTarget();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002938 }
2939 };
2940
2941 // Build a confirmation dialog to display to the user.
2942 final AlertDialog d =
2943 new AlertDialog.Builder(BrowserActivity.this)
2944 .setTitle(R.string.attention)
2945 .setIcon(android.R.drawable.ic_dialog_alert)
2946 .setMessage(R.string.popup_window_attempt)
2947 .setPositiveButton(R.string.allow, allowListener)
2948 .setNegativeButton(R.string.block, blockListener)
2949 .setCancelable(false)
2950 .create();
2951
2952 // Show the confirmation dialog.
2953 d.show();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002954 return true;
2955 }
2956
2957 @Override
2958 public void onCloseWindow(WebView window) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04002959 final TabControl.Tab current = mTabControl.getCurrentTab();
2960 final TabControl.Tab parent = current.getParentTab();
The Android Open Source Project0c908882009-03-03 19:32:16 -08002961 if (parent != null) {
2962 // JavaScript can only close popup window.
Leon Scroggins1f005d32009-08-10 17:36:42 -04002963 switchToTab(mTabControl.getTabIndex(parent));
2964 // Now we need to close the window
2965 closeTab(current);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002966 }
2967 }
2968
2969 @Override
2970 public void onProgressChanged(WebView view, int newProgress) {
Leon Scroggins1f005d32009-08-10 17:36:42 -04002971 if (CUSTOM_BROWSER_BAR) {
2972 mTitleBar.setProgress(newProgress, view);
2973 } else {
2974 getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
2975 newProgress * 100);
The Android Open Source Project0c908882009-03-03 19:32:16 -08002976 }
2977
2978 if (newProgress == 100) {
2979 // onProgressChanged() is called for sub-frame too while
2980 // onPageFinished() is only called for the main frame. sync
2981 // cookie and cache promptly here.
2982 CookieSyncManager.getInstance().sync();
The Android Open Source Projectcb9a0bb2009-03-11 12:11:58 -07002983 if (mInLoad) {
2984 mInLoad = false;
2985 updateInLoadMenuItems();
2986 }
2987 } else {
2988 // onPageFinished may have already been called but a subframe
2989 // is still loading and updating the progress. Reset mInLoad
2990 // and update the menu items.
2991 if (!mInLoad) {
2992 mInLoad = true;
2993 updateInLoadMenuItems();
2994 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08002995 }
2996 }
2997
2998 @Override
2999 public void onReceivedTitle(WebView view, String title) {
Patrick Scott598c9cc2009-06-04 11:10:38 -04003000 String url = view.getUrl();
The Android Open Source Project0c908882009-03-03 19:32:16 -08003001
3002 // here, if url is null, we want to reset the title
Leon Scroggins1f005d32009-08-10 17:36:42 -04003003 setUrlTitle(url, title, view);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003004
3005 if (url == null ||
3006 url.length() >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) {
3007 return;
3008 }
Leon Scrogginsfce182b2009-05-08 13:54:52 -04003009 // See if we can find the current url in our history database and
3010 // add the new title to it.
The Android Open Source Project0c908882009-03-03 19:32:16 -08003011 if (url.startsWith("http://www.")) {
3012 url = url.substring(11);
3013 } else if (url.startsWith("http://")) {
3014 url = url.substring(4);
3015 }
3016 try {
3017 url = "%" + url;
3018 String [] selArgs = new String[] { url };
3019
3020 String where = Browser.BookmarkColumns.URL + " LIKE ? AND "
3021 + Browser.BookmarkColumns.BOOKMARK + " = 0";
3022 Cursor c = mResolver.query(Browser.BOOKMARKS_URI,
3023 Browser.HISTORY_PROJECTION, where, selArgs, null);
3024 if (c.moveToFirst()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003025 // Current implementation of database only has one entry per
3026 // url.
Leon Scrogginsfce182b2009-05-08 13:54:52 -04003027 ContentValues map = new ContentValues();
3028 map.put(Browser.BookmarkColumns.TITLE, title);
3029 mResolver.update(Browser.BOOKMARKS_URI, map,
3030 "_id = " + c.getInt(0), null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003031 }
3032 c.close();
3033 } catch (IllegalStateException e) {
3034 Log.e(LOGTAG, "BrowserActivity onReceived title", e);
3035 } catch (SQLiteException ex) {
3036 Log.e(LOGTAG, "onReceivedTitle() caught SQLiteException: ", ex);
3037 }
3038 }
3039
3040 @Override
3041 public void onReceivedIcon(WebView view, Bitmap icon) {
Patrick Scott3918d442009-08-04 13:22:29 -04003042 updateIcon(view, icon);
3043 }
3044
3045 @Override
3046 public void onReceivedTouchIconUrl(WebView view, String url) {
3047 final ContentResolver cr = getContentResolver();
3048 final Cursor c =
3049 BrowserBookmarksAdapter.queryBookmarksForUrl(cr,
Leon Scrogginsa5d669e2009-08-05 14:07:58 -04003050 view.getOriginalUrl(), view.getUrl(), true);
Patrick Scott3918d442009-08-04 13:22:29 -04003051 if (c != null) {
3052 if (c.getCount() > 0) {
3053 new DownloadTouchIcon(cr, c, view).execute(url);
3054 } else {
3055 c.close();
3056 }
3057 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003058 }
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003059
Andrei Popescuadc008d2009-06-26 14:11:30 +01003060 @Override
Andrei Popescuc9b55562009-07-07 10:51:15 +01003061 public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
Andrei Popescuadc008d2009-06-26 14:11:30 +01003062 if (mCustomView != null)
3063 return;
3064
3065 // Add the custom view to its container.
3066 mCustomViewContainer.addView(view, COVER_SCREEN_GRAVITY_CENTER);
3067 mCustomView = view;
Andrei Popescuc9b55562009-07-07 10:51:15 +01003068 mCustomViewCallback = callback;
Andrei Popescuadc008d2009-06-26 14:11:30 +01003069 // Save the menu state and set it to empty while the custom
3070 // view is showing.
3071 mOldMenuState = mMenuState;
3072 mMenuState = EMPTY_MENU;
Andrei Popescuc9b55562009-07-07 10:51:15 +01003073 // Hide the content view.
3074 mContentView.setVisibility(View.GONE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003075 // Finally show the custom view container.
Andrei Popescuc9b55562009-07-07 10:51:15 +01003076 mCustomViewContainer.setVisibility(View.VISIBLE);
3077 mCustomViewContainer.bringToFront();
Andrei Popescuadc008d2009-06-26 14:11:30 +01003078 }
3079
3080 @Override
3081 public void onHideCustomView() {
3082 if (mCustomView == null)
3083 return;
3084
Andrei Popescuc9b55562009-07-07 10:51:15 +01003085 // Hide the custom view.
3086 mCustomView.setVisibility(View.GONE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003087 // Remove the custom view from its container.
3088 mCustomViewContainer.removeView(mCustomView);
3089 mCustomView = null;
3090 // Reset the old menu state.
3091 mMenuState = mOldMenuState;
3092 mOldMenuState = EMPTY_MENU;
3093 mCustomViewContainer.setVisibility(View.GONE);
Andrei Popescuc9b55562009-07-07 10:51:15 +01003094 mCustomViewCallback.onCustomViewHidden();
3095 // Show the content view.
3096 mContentView.setVisibility(View.VISIBLE);
Andrei Popescuadc008d2009-06-26 14:11:30 +01003097 }
3098
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003099 /**
Andrei Popescu79e82b72009-07-27 12:01:59 +01003100 * The origin has exceeded its database quota.
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003101 * @param url the URL that exceeded the quota
3102 * @param databaseIdentifier the identifier of the database on
3103 * which the transaction that caused the quota overflow was run
3104 * @param currentQuota the current quota for the origin.
Ben Murdoch25a15232009-08-25 19:38:07 +01003105 * @param estimatedSize the estimated size of the database.
Andrei Popescu79e82b72009-07-27 12:01:59 +01003106 * @param totalUsedQuota is the sum of all origins' quota.
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003107 * @param quotaUpdater The callback to run when a decision to allow or
3108 * deny quota has been made. Don't forget to call this!
3109 */
3110 @Override
3111 public void onExceededDatabaseQuota(String url,
Ben Murdoch25a15232009-08-25 19:38:07 +01003112 String databaseIdentifier, long currentQuota, long estimatedSize,
3113 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
Andrei Popescu79e82b72009-07-27 12:01:59 +01003114 mSettings.getWebStorageSizeManager().onExceededDatabaseQuota(
Ben Murdoch25a15232009-08-25 19:38:07 +01003115 url, databaseIdentifier, currentQuota, estimatedSize,
3116 totalUsedQuota, quotaUpdater);
Andrei Popescu79e82b72009-07-27 12:01:59 +01003117 }
3118
3119 /**
3120 * The Application Cache has exceeded its max size.
3121 * @param spaceNeeded is the amount of disk space that would be needed
3122 * in order for the last appcache operation to succeed.
3123 * @param totalUsedQuota is the sum of all origins' quota.
3124 * @param quotaUpdater A callback to inform the WebCore thread that a new
3125 * app cache size is available. This callback must always be executed at
3126 * some point to ensure that the sleeping WebCore thread is woken up.
3127 */
3128 @Override
3129 public void onReachedMaxAppCacheSize(long spaceNeeded,
3130 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
3131 mSettings.getWebStorageSizeManager().onReachedMaxAppCacheSize(
3132 spaceNeeded, totalUsedQuota, quotaUpdater);
Ben Murdoch092dd5d2009-04-22 12:34:12 +01003133 }
Ben Murdoch7db26342009-06-03 18:21:19 +01003134
Steve Block2bc69912009-07-30 14:45:13 +01003135 /**
3136 * Instructs the browser to show a prompt to ask the user to set the
3137 * Geolocation permission state for the specified origin.
3138 * @param origin The origin for which Geolocation permissions are
3139 * requested.
3140 * @param callback The callback to call once the user has set the
3141 * Geolocation permission state.
3142 */
3143 @Override
3144 public void onGeolocationPermissionsShowPrompt(String origin,
3145 GeolocationPermissions.Callback callback) {
3146 mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().show(
3147 origin, callback);
3148 }
3149
3150 /**
3151 * Instructs the browser to hide the Geolocation permissions prompt.
3152 */
3153 @Override
3154 public void onGeolocationPermissionsHidePrompt() {
3155 mTabControl.getCurrentTab().getGeolocationPermissionsPrompt().hide();
3156 }
3157
Ben Murdoch7db26342009-06-03 18:21:19 +01003158 /* Adds a JavaScript error message to the system log.
3159 * @param message The error message to report.
3160 * @param lineNumber The line number of the error.
3161 * @param sourceID The name of the source file that caused the error.
3162 */
3163 @Override
3164 public void addMessageToConsole(String message, int lineNumber, String sourceID) {
Ben Murdochbff2d602009-07-01 20:19:05 +01003165 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
3166 errorConsole.addErrorMessage(message, sourceID, lineNumber);
3167 if (mShouldShowErrorConsole &&
3168 errorConsole.getShowState() != ErrorConsoleView.SHOW_MAXIMIZED) {
3169 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
3170 }
3171 Log.w(LOGTAG, "Console: " + message + " " + sourceID + ":" + lineNumber);
Ben Murdoch7db26342009-06-03 18:21:19 +01003172 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003173 };
3174
3175 /**
3176 * Notify the host application a download should be done, or that
3177 * the data should be streamed if a streaming viewer is available.
3178 * @param url The full url to the content that should be downloaded
3179 * @param contentDisposition Content-disposition http header, if
3180 * present.
3181 * @param mimetype The mimetype of the content reported by the server
3182 * @param contentLength The file size reported by the server
3183 */
3184 public void onDownloadStart(String url, String userAgent,
3185 String contentDisposition, String mimetype, long contentLength) {
3186 // if we're dealing wih A/V content that's not explicitly marked
3187 // for download, check if it's streamable.
3188 if (contentDisposition == null
Patrick Scotte1fb9662009-08-31 14:31:52 -04003189 || !contentDisposition.regionMatches(
3190 true, 0, "attachment", 0, 10)) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003191 // query the package manager to see if there's a registered handler
3192 // that matches.
3193 Intent intent = new Intent(Intent.ACTION_VIEW);
3194 intent.setDataAndType(Uri.parse(url), mimetype);
Patrick Scotte1fb9662009-08-31 14:31:52 -04003195 ResolveInfo info = getPackageManager().resolveActivity(intent,
3196 PackageManager.MATCH_DEFAULT_ONLY);
3197 if (info != null) {
3198 ComponentName myName = getComponentName();
3199 // If we resolved to ourselves, we don't want to attempt to
3200 // load the url only to try and download it again.
3201 if (!myName.getPackageName().equals(
3202 info.activityInfo.packageName)
3203 || !myName.getClassName().equals(
3204 info.activityInfo.name)) {
3205 // someone (other than us) knows how to handle this mime
3206 // type with this scheme, don't download.
3207 try {
3208 startActivity(intent);
3209 return;
3210 } catch (ActivityNotFoundException ex) {
3211 if (LOGD_ENABLED) {
3212 Log.d(LOGTAG, "activity not found for " + mimetype
3213 + " over " + Uri.parse(url).getScheme(),
3214 ex);
3215 }
3216 // Best behavior is to fall back to a download in this
3217 // case
The Android Open Source Project0c908882009-03-03 19:32:16 -08003218 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003219 }
3220 }
3221 }
3222 onDownloadStartNoStream(url, userAgent, contentDisposition, mimetype, contentLength);
3223 }
3224
3225 /**
3226 * Notify the host application a download should be done, even if there
3227 * is a streaming viewer available for thise type.
3228 * @param url The full url to the content that should be downloaded
3229 * @param contentDisposition Content-disposition http header, if
3230 * present.
3231 * @param mimetype The mimetype of the content reported by the server
3232 * @param contentLength The file size reported by the server
3233 */
3234 /*package */ void onDownloadStartNoStream(String url, String userAgent,
3235 String contentDisposition, String mimetype, long contentLength) {
3236
3237 String filename = URLUtil.guessFileName(url,
3238 contentDisposition, mimetype);
3239
3240 // Check to see if we have an SDCard
3241 String status = Environment.getExternalStorageState();
3242 if (!status.equals(Environment.MEDIA_MOUNTED)) {
3243 int title;
3244 String msg;
3245
3246 // Check to see if the SDCard is busy, same as the music app
3247 if (status.equals(Environment.MEDIA_SHARED)) {
3248 msg = getString(R.string.download_sdcard_busy_dlg_msg);
3249 title = R.string.download_sdcard_busy_dlg_title;
3250 } else {
3251 msg = getString(R.string.download_no_sdcard_dlg_msg, filename);
3252 title = R.string.download_no_sdcard_dlg_title;
3253 }
3254
3255 new AlertDialog.Builder(this)
3256 .setTitle(title)
3257 .setIcon(android.R.drawable.ic_dialog_alert)
3258 .setMessage(msg)
3259 .setPositiveButton(R.string.ok, null)
3260 .show();
3261 return;
3262 }
3263
3264 // java.net.URI is a lot stricter than KURL so we have to undo
3265 // KURL's percent-encoding and redo the encoding using java.net.URI.
3266 URI uri = null;
3267 try {
3268 // Undo the percent-encoding that KURL may have done.
3269 String newUrl = new String(URLUtil.decode(url.getBytes()));
3270 // Parse the url into pieces
3271 WebAddress w = new WebAddress(newUrl);
3272 String frag = null;
3273 String query = null;
3274 String path = w.mPath;
3275 // Break the path into path, query, and fragment
3276 if (path.length() > 0) {
3277 // Strip the fragment
3278 int idx = path.lastIndexOf('#');
3279 if (idx != -1) {
3280 frag = path.substring(idx + 1);
3281 path = path.substring(0, idx);
3282 }
3283 idx = path.lastIndexOf('?');
3284 if (idx != -1) {
3285 query = path.substring(idx + 1);
3286 path = path.substring(0, idx);
3287 }
3288 }
3289 uri = new URI(w.mScheme, w.mAuthInfo, w.mHost, w.mPort, path,
3290 query, frag);
3291 } catch (Exception e) {
3292 Log.e(LOGTAG, "Could not parse url for download: " + url, e);
3293 return;
3294 }
3295
3296 // XXX: Have to use the old url since the cookies were stored using the
3297 // old percent-encoded url.
3298 String cookies = CookieManager.getInstance().getCookie(url);
3299
3300 ContentValues values = new ContentValues();
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003301 values.put(Downloads.COLUMN_URI, uri.toString());
3302 values.put(Downloads.COLUMN_COOKIE_DATA, cookies);
3303 values.put(Downloads.COLUMN_USER_AGENT, userAgent);
3304 values.put(Downloads.COLUMN_NOTIFICATION_PACKAGE,
The Android Open Source Project0c908882009-03-03 19:32:16 -08003305 getPackageName());
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003306 values.put(Downloads.COLUMN_NOTIFICATION_CLASS,
The Android Open Source Project0c908882009-03-03 19:32:16 -08003307 BrowserDownloadPage.class.getCanonicalName());
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003308 values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
3309 values.put(Downloads.COLUMN_MIME_TYPE, mimetype);
3310 values.put(Downloads.COLUMN_FILE_NAME_HINT, filename);
3311 values.put(Downloads.COLUMN_DESCRIPTION, uri.getHost());
The Android Open Source Project0c908882009-03-03 19:32:16 -08003312 if (contentLength > 0) {
Jean-Baptiste Queru3dc09b22009-03-31 16:49:44 -07003313 values.put(Downloads.COLUMN_TOTAL_BYTES, contentLength);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003314 }
3315 if (mimetype == null) {
3316 // We must have long pressed on a link or image to download it. We
3317 // are not sure of the mimetype in this case, so do a head request
3318 new FetchUrlMimeType(this).execute(values);
3319 } else {
3320 final Uri contentUri =
3321 getContentResolver().insert(Downloads.CONTENT_URI, values);
3322 viewDownloads(contentUri);
3323 }
3324
3325 }
3326
3327 /**
3328 * Resets the lock icon. This method is called when we start a new load and
3329 * know the url to be loaded.
3330 */
3331 private void resetLockIcon(String url) {
3332 // Save the lock-icon state (we revert to it if the load gets cancelled)
3333 saveLockIcon();
3334
3335 mLockIconType = LOCK_ICON_UNSECURE;
3336 if (URLUtil.isHttpsUrl(url)) {
3337 mLockIconType = LOCK_ICON_SECURE;
Dave Bort31a6d1c2009-04-13 15:56:49 -07003338 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003339 Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3340 " reset lock icon to " + mLockIconType);
3341 }
3342 }
3343
3344 updateLockIconImage(LOCK_ICON_UNSECURE);
3345 }
3346
3347 /**
3348 * Resets the lock icon. This method is called when the icon needs to be
3349 * reset but we do not know whether we are loading a secure or not secure
3350 * page.
3351 */
3352 private void resetLockIcon() {
3353 // Save the lock-icon state (we revert to it if the load gets cancelled)
3354 saveLockIcon();
3355
3356 mLockIconType = LOCK_ICON_UNSECURE;
3357
Dave Bort31a6d1c2009-04-13 15:56:49 -07003358 if (LOGV_ENABLED) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003359 Log.v(LOGTAG, "BrowserActivity.resetLockIcon:" +
3360 " reset lock icon to " + mLockIconType);
3361 }
3362
3363 updateLockIconImage(LOCK_ICON_UNSECURE);
3364 }
3365
3366 /**
3367 * Updates the lock-icon image in the title-bar.
3368 */
3369 private void updateLockIconImage(int lockIconType) {
3370 Drawable d = null;
3371 if (lockIconType == LOCK_ICON_SECURE) {
3372 d = mSecLockIcon;
3373 } else if (lockIconType == LOCK_ICON_MIXED) {
3374 d = mMixLockIcon;
3375 }
Leon Scroggins1f005d32009-08-10 17:36:42 -04003376 if (CUSTOM_BROWSER_BAR) {
3377 mTitleBar.setLock(d, getTopWindow());
3378 } else {
3379 getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, d);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003380 }
3381 }
3382
3383 /**
3384 * Displays a page-info dialog.
3385 * @param tab The tab to show info about
3386 * @param fromShowSSLCertificateOnError The flag that indicates whether
3387 * this dialog was opened from the SSL-certificate-on-error dialog or
3388 * not. This is important, since we need to know whether to return to
3389 * the parent dialog or simply dismiss.
3390 */
3391 private void showPageInfo(final TabControl.Tab tab,
3392 final boolean fromShowSSLCertificateOnError) {
3393 final LayoutInflater factory = LayoutInflater
3394 .from(this);
3395
3396 final View pageInfoView = factory.inflate(R.layout.page_info, null);
3397
3398 final WebView view = tab.getWebView();
3399
3400 String url = null;
3401 String title = null;
3402
3403 if (view == null) {
3404 url = tab.getUrl();
3405 title = tab.getTitle();
3406 } else if (view == mTabControl.getCurrentWebView()) {
3407 // Use the cached title and url if this is the current WebView
3408 url = mUrl;
3409 title = mTitle;
3410 } else {
3411 url = view.getUrl();
3412 title = view.getTitle();
3413 }
3414
3415 if (url == null) {
3416 url = "";
3417 }
3418 if (title == null) {
3419 title = "";
3420 }
3421
3422 ((TextView) pageInfoView.findViewById(R.id.address)).setText(url);
3423 ((TextView) pageInfoView.findViewById(R.id.title)).setText(title);
3424
3425 mPageInfoView = tab;
3426 mPageInfoFromShowSSLCertificateOnError = new Boolean(fromShowSSLCertificateOnError);
3427
3428 AlertDialog.Builder alertDialogBuilder =
3429 new AlertDialog.Builder(this)
3430 .setTitle(R.string.page_info).setIcon(android.R.drawable.ic_dialog_info)
3431 .setView(pageInfoView)
3432 .setPositiveButton(
3433 R.string.ok,
3434 new DialogInterface.OnClickListener() {
3435 public void onClick(DialogInterface dialog,
3436 int whichButton) {
3437 mPageInfoDialog = null;
3438 mPageInfoView = null;
3439 mPageInfoFromShowSSLCertificateOnError = null;
3440
3441 // if we came here from the SSL error dialog
3442 if (fromShowSSLCertificateOnError) {
3443 // go back to the SSL error dialog
3444 showSSLCertificateOnError(
3445 mSSLCertificateOnErrorView,
3446 mSSLCertificateOnErrorHandler,
3447 mSSLCertificateOnErrorError);
3448 }
3449 }
3450 })
3451 .setOnCancelListener(
3452 new DialogInterface.OnCancelListener() {
3453 public void onCancel(DialogInterface dialog) {
3454 mPageInfoDialog = null;
3455 mPageInfoView = null;
3456 mPageInfoFromShowSSLCertificateOnError = null;
3457
3458 // if we came here from the SSL error dialog
3459 if (fromShowSSLCertificateOnError) {
3460 // go back to the SSL error dialog
3461 showSSLCertificateOnError(
3462 mSSLCertificateOnErrorView,
3463 mSSLCertificateOnErrorHandler,
3464 mSSLCertificateOnErrorError);
3465 }
3466 }
3467 });
3468
3469 // if we have a main top-level page SSL certificate set or a certificate
3470 // error
3471 if (fromShowSSLCertificateOnError ||
3472 (view != null && view.getCertificate() != null)) {
3473 // add a 'View Certificate' button
3474 alertDialogBuilder.setNeutralButton(
3475 R.string.view_certificate,
3476 new DialogInterface.OnClickListener() {
3477 public void onClick(DialogInterface dialog,
3478 int whichButton) {
3479 mPageInfoDialog = null;
3480 mPageInfoView = null;
3481 mPageInfoFromShowSSLCertificateOnError = null;
3482
3483 // if we came here from the SSL error dialog
3484 if (fromShowSSLCertificateOnError) {
3485 // go back to the SSL error dialog
3486 showSSLCertificateOnError(
3487 mSSLCertificateOnErrorView,
3488 mSSLCertificateOnErrorHandler,
3489 mSSLCertificateOnErrorError);
3490 } else {
3491 // otherwise, display the top-most certificate from
3492 // the chain
3493 if (view.getCertificate() != null) {
3494 showSSLCertificate(tab);
3495 }
3496 }
3497 }
3498 });
3499 }
3500
3501 mPageInfoDialog = alertDialogBuilder.show();
3502 }
3503
3504 /**
3505 * Displays the main top-level page SSL certificate dialog
3506 * (accessible from the Page-Info dialog).
3507 * @param tab The tab to show certificate for.
3508 */
3509 private void showSSLCertificate(final TabControl.Tab tab) {
3510 final View certificateView =
3511 inflateCertificateView(tab.getWebView().getCertificate());
3512 if (certificateView == null) {
3513 return;
3514 }
3515
3516 LayoutInflater factory = LayoutInflater.from(this);
3517
3518 final LinearLayout placeholder =
3519 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3520
3521 LinearLayout ll = (LinearLayout) factory.inflate(
3522 R.layout.ssl_success, placeholder);
3523 ((TextView)ll.findViewById(R.id.success))
3524 .setText(R.string.ssl_certificate_is_valid);
3525
3526 mSSLCertificateView = tab;
3527 mSSLCertificateDialog =
3528 new AlertDialog.Builder(this)
3529 .setTitle(R.string.ssl_certificate).setIcon(
3530 R.drawable.ic_dialog_browser_certificate_secure)
3531 .setView(certificateView)
3532 .setPositiveButton(R.string.ok,
3533 new DialogInterface.OnClickListener() {
3534 public void onClick(DialogInterface dialog,
3535 int whichButton) {
3536 mSSLCertificateDialog = null;
3537 mSSLCertificateView = null;
3538
3539 showPageInfo(tab, false);
3540 }
3541 })
3542 .setOnCancelListener(
3543 new DialogInterface.OnCancelListener() {
3544 public void onCancel(DialogInterface dialog) {
3545 mSSLCertificateDialog = null;
3546 mSSLCertificateView = null;
3547
3548 showPageInfo(tab, false);
3549 }
3550 })
3551 .show();
3552 }
3553
3554 /**
3555 * Displays the SSL error certificate dialog.
3556 * @param view The target web-view.
3557 * @param handler The SSL error handler responsible for cancelling the
3558 * connection that resulted in an SSL error or proceeding per user request.
3559 * @param error The SSL error object.
3560 */
3561 private void showSSLCertificateOnError(
3562 final WebView view, final SslErrorHandler handler, final SslError error) {
3563
3564 final View certificateView =
3565 inflateCertificateView(error.getCertificate());
3566 if (certificateView == null) {
3567 return;
3568 }
3569
3570 LayoutInflater factory = LayoutInflater.from(this);
3571
3572 final LinearLayout placeholder =
3573 (LinearLayout)certificateView.findViewById(R.id.placeholder);
3574
3575 if (error.hasError(SslError.SSL_UNTRUSTED)) {
3576 LinearLayout ll = (LinearLayout)factory
3577 .inflate(R.layout.ssl_warning, placeholder);
3578 ((TextView)ll.findViewById(R.id.warning))
3579 .setText(R.string.ssl_untrusted);
3580 }
3581
3582 if (error.hasError(SslError.SSL_IDMISMATCH)) {
3583 LinearLayout ll = (LinearLayout)factory
3584 .inflate(R.layout.ssl_warning, placeholder);
3585 ((TextView)ll.findViewById(R.id.warning))
3586 .setText(R.string.ssl_mismatch);
3587 }
3588
3589 if (error.hasError(SslError.SSL_EXPIRED)) {
3590 LinearLayout ll = (LinearLayout)factory
3591 .inflate(R.layout.ssl_warning, placeholder);
3592 ((TextView)ll.findViewById(R.id.warning))
3593 .setText(R.string.ssl_expired);
3594 }
3595
3596 if (error.hasError(SslError.SSL_NOTYETVALID)) {
3597 LinearLayout ll = (LinearLayout)factory
3598 .inflate(R.layout.ssl_warning, placeholder);
3599 ((TextView)ll.findViewById(R.id.warning))
3600 .setText(R.string.ssl_not_yet_valid);
3601 }
3602
3603 mSSLCertificateOnErrorHandler = handler;
3604 mSSLCertificateOnErrorView = view;
3605 mSSLCertificateOnErrorError = error;
3606 mSSLCertificateOnErrorDialog =
3607 new AlertDialog.Builder(this)
3608 .setTitle(R.string.ssl_certificate).setIcon(
3609 R.drawable.ic_dialog_browser_certificate_partially_secure)
3610 .setView(certificateView)
3611 .setPositiveButton(R.string.ok,
3612 new DialogInterface.OnClickListener() {
3613 public void onClick(DialogInterface dialog,
3614 int whichButton) {
3615 mSSLCertificateOnErrorDialog = null;
3616 mSSLCertificateOnErrorView = null;
3617 mSSLCertificateOnErrorHandler = null;
3618 mSSLCertificateOnErrorError = null;
3619
3620 mWebViewClient.onReceivedSslError(
3621 view, handler, error);
3622 }
3623 })
3624 .setNeutralButton(R.string.page_info_view,
3625 new DialogInterface.OnClickListener() {
3626 public void onClick(DialogInterface dialog,
3627 int whichButton) {
3628 mSSLCertificateOnErrorDialog = null;
3629
3630 // do not clear the dialog state: we will
3631 // need to show the dialog again once the
3632 // user is done exploring the page-info details
3633
3634 showPageInfo(mTabControl.getTabFromView(view),
3635 true);
3636 }
3637 })
3638 .setOnCancelListener(
3639 new DialogInterface.OnCancelListener() {
3640 public void onCancel(DialogInterface dialog) {
3641 mSSLCertificateOnErrorDialog = null;
3642 mSSLCertificateOnErrorView = null;
3643 mSSLCertificateOnErrorHandler = null;
3644 mSSLCertificateOnErrorError = null;
3645
3646 mWebViewClient.onReceivedSslError(
3647 view, handler, error);
3648 }
3649 })
3650 .show();
3651 }
3652
3653 /**
3654 * Inflates the SSL certificate view (helper method).
3655 * @param certificate The SSL certificate.
3656 * @return The resultant certificate view with issued-to, issued-by,
3657 * issued-on, expires-on, and possibly other fields set.
3658 * If the input certificate is null, returns null.
3659 */
3660 private View inflateCertificateView(SslCertificate certificate) {
3661 if (certificate == null) {
3662 return null;
3663 }
3664
3665 LayoutInflater factory = LayoutInflater.from(this);
3666
3667 View certificateView = factory.inflate(
3668 R.layout.ssl_certificate, null);
3669
3670 // issued to:
3671 SslCertificate.DName issuedTo = certificate.getIssuedTo();
3672 if (issuedTo != null) {
3673 ((TextView) certificateView.findViewById(R.id.to_common))
3674 .setText(issuedTo.getCName());
3675 ((TextView) certificateView.findViewById(R.id.to_org))
3676 .setText(issuedTo.getOName());
3677 ((TextView) certificateView.findViewById(R.id.to_org_unit))
3678 .setText(issuedTo.getUName());
3679 }
3680
3681 // issued by:
3682 SslCertificate.DName issuedBy = certificate.getIssuedBy();
3683 if (issuedBy != null) {
3684 ((TextView) certificateView.findViewById(R.id.by_common))
3685 .setText(issuedBy.getCName());
3686 ((TextView) certificateView.findViewById(R.id.by_org))
3687 .setText(issuedBy.getOName());
3688 ((TextView) certificateView.findViewById(R.id.by_org_unit))
3689 .setText(issuedBy.getUName());
3690 }
3691
3692 // issued on:
3693 String issuedOn = reformatCertificateDate(
3694 certificate.getValidNotBefore());
3695 ((TextView) certificateView.findViewById(R.id.issued_on))
3696 .setText(issuedOn);
3697
3698 // expires on:
3699 String expiresOn = reformatCertificateDate(
3700 certificate.getValidNotAfter());
3701 ((TextView) certificateView.findViewById(R.id.expires_on))
3702 .setText(expiresOn);
3703
3704 return certificateView;
3705 }
3706
3707 /**
3708 * Re-formats the certificate date (Date.toString()) string to
3709 * a properly localized date string.
3710 * @return Properly localized version of the certificate date string and
3711 * the original certificate date string if fails to localize.
3712 * If the original string is null, returns an empty string "".
3713 */
3714 private String reformatCertificateDate(String certificateDate) {
3715 String reformattedDate = null;
3716
3717 if (certificateDate != null) {
3718 Date date = null;
3719 try {
3720 date = java.text.DateFormat.getInstance().parse(certificateDate);
3721 } catch (ParseException e) {
3722 date = null;
3723 }
3724
3725 if (date != null) {
3726 reformattedDate =
3727 DateFormat.getDateFormat(this).format(date);
3728 }
3729 }
3730
3731 return reformattedDate != null ? reformattedDate :
3732 (certificateDate != null ? certificateDate : "");
3733 }
3734
3735 /**
3736 * Displays an http-authentication dialog.
3737 */
3738 private void showHttpAuthentication(final HttpAuthHandler handler,
3739 final String host, final String realm, final String title,
3740 final String name, final String password, int focusId) {
3741 LayoutInflater factory = LayoutInflater.from(this);
3742 final View v = factory
3743 .inflate(R.layout.http_authentication, null);
3744 if (name != null) {
3745 ((EditText) v.findViewById(R.id.username_edit)).setText(name);
3746 }
3747 if (password != null) {
3748 ((EditText) v.findViewById(R.id.password_edit)).setText(password);
3749 }
3750
3751 String titleText = title;
3752 if (titleText == null) {
3753 titleText = getText(R.string.sign_in_to).toString().replace(
3754 "%s1", host).replace("%s2", realm);
3755 }
3756
3757 mHttpAuthHandler = handler;
3758 AlertDialog dialog = new AlertDialog.Builder(this)
3759 .setTitle(titleText)
3760 .setIcon(android.R.drawable.ic_dialog_alert)
3761 .setView(v)
3762 .setPositiveButton(R.string.action,
3763 new DialogInterface.OnClickListener() {
3764 public void onClick(DialogInterface dialog,
3765 int whichButton) {
3766 String nm = ((EditText) v
3767 .findViewById(R.id.username_edit))
3768 .getText().toString();
3769 String pw = ((EditText) v
3770 .findViewById(R.id.password_edit))
3771 .getText().toString();
3772 BrowserActivity.this.setHttpAuthUsernamePassword
3773 (host, realm, nm, pw);
3774 handler.proceed(nm, pw);
3775 mHttpAuthenticationDialog = null;
3776 mHttpAuthHandler = null;
3777 }})
3778 .setNegativeButton(R.string.cancel,
3779 new DialogInterface.OnClickListener() {
3780 public void onClick(DialogInterface dialog,
3781 int whichButton) {
3782 handler.cancel();
3783 BrowserActivity.this.resetTitleAndRevertLockIcon();
3784 mHttpAuthenticationDialog = null;
3785 mHttpAuthHandler = null;
3786 }})
3787 .setOnCancelListener(new DialogInterface.OnCancelListener() {
3788 public void onCancel(DialogInterface dialog) {
3789 handler.cancel();
3790 BrowserActivity.this.resetTitleAndRevertLockIcon();
3791 mHttpAuthenticationDialog = null;
3792 mHttpAuthHandler = null;
3793 }})
3794 .create();
3795 // Make the IME appear when the dialog is displayed if applicable.
3796 dialog.getWindow().setSoftInputMode(
3797 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
3798 dialog.show();
3799 if (focusId != 0) {
3800 dialog.findViewById(focusId).requestFocus();
3801 } else {
3802 v.findViewById(R.id.username_edit).requestFocus();
3803 }
3804 mHttpAuthenticationDialog = dialog;
3805 }
3806
3807 public int getProgress() {
3808 WebView w = mTabControl.getCurrentWebView();
3809 if (w != null) {
3810 return w.getProgress();
3811 } else {
3812 return 100;
3813 }
3814 }
3815
3816 /**
3817 * Set HTTP authentication password.
3818 *
3819 * @param host The host for the password
3820 * @param realm The realm for the password
3821 * @param username The username for the password. If it is null, it means
3822 * password can't be saved.
3823 * @param password The password
3824 */
3825 public void setHttpAuthUsernamePassword(String host, String realm,
3826 String username,
3827 String password) {
3828 WebView w = mTabControl.getCurrentWebView();
3829 if (w != null) {
3830 w.setHttpAuthUsernamePassword(host, realm, username, password);
3831 }
3832 }
3833
3834 /**
3835 * connectivity manager says net has come or gone... inform the user
3836 * @param up true if net has come up, false if net has gone down
3837 */
3838 public void onNetworkToggle(boolean up) {
3839 if (up == mIsNetworkUp) {
3840 return;
3841 } else if (up) {
3842 mIsNetworkUp = true;
3843 if (mAlertDialog != null) {
3844 mAlertDialog.cancel();
3845 mAlertDialog = null;
3846 }
3847 } else {
3848 mIsNetworkUp = false;
3849 if (mInLoad && mAlertDialog == null) {
3850 mAlertDialog = new AlertDialog.Builder(this)
3851 .setTitle(R.string.loadSuspendedTitle)
3852 .setMessage(R.string.loadSuspended)
3853 .setPositiveButton(R.string.ok, null)
3854 .show();
3855 }
3856 }
3857 WebView w = mTabControl.getCurrentWebView();
3858 if (w != null) {
3859 w.setNetworkAvailable(up);
3860 }
3861 }
3862
3863 @Override
3864 protected void onActivityResult(int requestCode, int resultCode,
3865 Intent intent) {
3866 switch (requestCode) {
3867 case COMBO_PAGE:
3868 if (resultCode == RESULT_OK && intent != null) {
3869 String data = intent.getAction();
3870 Bundle extras = intent.getExtras();
3871 if (extras != null && extras.getBoolean("new_window", false)) {
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003872 final TabControl.Tab newTab = openTab(data);
3873 if (mSettings.openInBackground() &&
Leon Scroggins1f005d32009-08-10 17:36:42 -04003874 newTab != null) {
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003875 mTabControl.populatePickerData(newTab);
3876 mTabControl.setCurrentTab(newTab);
Leon Scroggins1f005d32009-08-10 17:36:42 -04003877 int newIndex = mTabControl.getCurrentIndex();
3878 if (CUSTOM_BROWSER_BAR) {
3879 mTitleBar.setCurrentTab(newIndex);
3880 }
Patrick Scottb0e4fc72009-07-14 10:49:22 -04003881 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003882 } else {
3883 final TabControl.Tab currentTab =
3884 mTabControl.getCurrentTab();
Leon Scroggins1f005d32009-08-10 17:36:42 -04003885 dismissSubWindow(currentTab);
3886 if (data != null && data.length() != 0) {
3887 getTopWindow().loadUrl(data);
The Android Open Source Project0c908882009-03-03 19:32:16 -08003888 }
3889 }
Leon Scroggins39362092009-08-18 19:47:09 -04003890/*
3891 FIXME: Removing this breaks the behavior of pressing BACK from
3892 the Go page resulting in the window being closed. However, it
3893 needs to be removed so that the user can use the Search bar to
3894 enter a URL. Further, the Go behavior is going to change
3895 drastically, so this behavior may not last anyway.
Leon Scroggins160a7e72009-08-14 18:28:01 -04003896 } else if (resultCode == RESULT_CANCELED
3897 && mCancelGoPageMeansClose) {
3898 if (mTabControl.getTabCount() == 1) {
3899 // finish the Browser. When the Browser opens up again,
3900 // we will go through onCreate and once again open up
3901 // the Go page.
3902 finish();
3903 return;
3904 }
3905 closeCurrentWindow();
Leon Scroggins39362092009-08-18 19:47:09 -04003906*/
The Android Open Source Project0c908882009-03-03 19:32:16 -08003907 }
3908 break;
3909 default:
3910 break;
3911 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04003912 mCancelGoPageMeansClose = false;
3913 if (getTopWindow() != null) {
3914 getTopWindow().requestFocus();
3915 }
The Android Open Source Project0c908882009-03-03 19:32:16 -08003916 }
3917
3918 /*
3919 * This method is called as a result of the user selecting the options
3920 * menu to see the download window, or when a download changes state. It
3921 * shows the download window ontop of the current window.
3922 */
3923 /* package */ void viewDownloads(Uri downloadRecord) {
3924 Intent intent = new Intent(this,
3925 BrowserDownloadPage.class);
3926 intent.setData(downloadRecord);
3927 startActivityForResult(intent, this.DOWNLOAD_PAGE);
3928
3929 }
3930
Leon Scroggins160a7e72009-08-14 18:28:01 -04003931 // True if canceling the "Go" screen should result in closing the current
3932 // window/browser.
3933 private boolean mCancelGoPageMeansClose;
3934
3935 /**
3936 * Open the Go page.
3937 * @param startWithHistory If true, open starting on the history tab.
3938 * Otherwise, start with the bookmarks tab.
3939 * @param cancelGoPageMeansClose Set to true if this came from a new tab, or
3940 * from the only tab, and canceling means to
3941 * close the tab (and possibly the browser)
3942 */
3943 /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory,
3944 boolean cancelGoPageMeansClose) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08003945 WebView current = mTabControl.getCurrentWebView();
3946 if (current == null) {
3947 return;
3948 }
3949 Intent intent = new Intent(this,
3950 CombinedBookmarkHistoryActivity.class);
3951 String title = current.getTitle();
3952 String url = current.getUrl();
3953 // Just in case the user opens bookmarks before a page finishes loading
3954 // so the current history item, and therefore the page, is null.
3955 if (null == url) {
3956 url = mLastEnteredUrl;
3957 // This can happen.
3958 if (null == url) {
3959 url = mSettings.getHomePage();
3960 }
3961 }
3962 // In case the web page has not yet received its associated title.
3963 if (title == null) {
3964 title = url;
3965 }
3966 intent.putExtra("title", title);
3967 intent.putExtra("url", url);
Leon Scroggins190095d2009-08-17 17:01:38 -04003968 // If this is opening in a new window, then disable opening in a
3969 // (different) new window. Also disable it if we have maxed out the
3970 // windows.
3971 intent.putExtra("disable_new_window", cancelGoPageMeansClose
3972 || mTabControl.getTabCount() >= TabControl.MAX_TABS);
Patrick Scott3918d442009-08-04 13:22:29 -04003973 intent.putExtra("touch_icon_url", current.getTouchIconUrl());
The Android Open Source Project0c908882009-03-03 19:32:16 -08003974 if (startWithHistory) {
3975 intent.putExtra(CombinedBookmarkHistoryActivity.STARTING_TAB,
3976 CombinedBookmarkHistoryActivity.HISTORY_TAB);
3977 }
Leon Scroggins160a7e72009-08-14 18:28:01 -04003978 mCancelGoPageMeansClose = cancelGoPageMeansClose;
The Android Open Source Project0c908882009-03-03 19:32:16 -08003979 startActivityForResult(intent, COMBO_PAGE);
3980 }
3981
3982 // Called when loading from context menu or LOAD_URL message
3983 private void loadURL(WebView view, String url) {
3984 // In case the user enters nothing.
3985 if (url != null && url.length() != 0 && view != null) {
3986 url = smartUrlFilter(url);
3987 if (!mWebViewClient.shouldOverrideUrlLoading(view, url)) {
3988 view.loadUrl(url);
3989 }
3990 }
3991 }
3992
The Android Open Source Project0c908882009-03-03 19:32:16 -08003993 private String smartUrlFilter(Uri inUri) {
3994 if (inUri != null) {
3995 return smartUrlFilter(inUri.toString());
3996 }
3997 return null;
3998 }
3999
4000
4001 // get window count
4002
4003 int getWindowCount(){
4004 if(mTabControl != null){
4005 return mTabControl.getTabCount();
4006 }
4007 return 0;
4008 }
4009
Feng Qianb34f87a2009-03-24 21:27:26 -07004010 protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile(
The Android Open Source Project0c908882009-03-03 19:32:16 -08004011 "(?i)" + // switch on case insensitive matching
4012 "(" + // begin group for schema
4013 "(?:http|https|file):\\/\\/" +
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004014 "|(?:inline|data|about|content|javascript):" +
The Android Open Source Project0c908882009-03-03 19:32:16 -08004015 ")" +
4016 "(.*)" );
4017
4018 /**
4019 * Attempts to determine whether user input is a URL or search
4020 * terms. Anything with a space is passed to search.
4021 *
4022 * Converts to lowercase any mistakenly uppercased schema (i.e.,
4023 * "Http://" converts to "http://"
4024 *
4025 * @return Original or modified URL
4026 *
4027 */
4028 String smartUrlFilter(String url) {
4029
4030 String inUrl = url.trim();
4031 boolean hasSpace = inUrl.indexOf(' ') != -1;
4032
4033 Matcher matcher = ACCEPTED_URI_SCHEMA.matcher(inUrl);
4034 if (matcher.matches()) {
The Android Open Source Project0c908882009-03-03 19:32:16 -08004035 // force scheme to lowercase
4036 String scheme = matcher.group(1);
4037 String lcScheme = scheme.toLowerCase();
4038 if (!lcScheme.equals(scheme)) {
Mitsuru Oshima123ecfb2009-05-18 19:11:14 -07004039 inUrl = lcScheme + matcher.group(2);
4040 }
4041 if (hasSpace) {
4042 inUrl = inUrl.replace(" ", "%20");
The Android Open Source Project0c908882009-03-03 19:32:16 -08004043 }
4044 return inUrl;
4045 }
4046 if (hasSpace) {
Satish Sampath565505b2009-05-29 15:37:27 +01004047 // FIXME: Is this the correct place to add to searches?
4048 // what if someone else calls this function?
4049 int shortcut = parseUrlShortcut(inUrl);
4050 if (shortcut != SHORTCUT_INVALID) {
4051 Browser.addSearchUrl(mResolver, inUrl);
4052 String query = inUrl.substring(2);
4053 switch (shortcut) {
4054 case SHORTCUT_GOOGLE_SEARCH:
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004055 return URLUtil.composeSearchUrl(query, QuickSearch_G, QUERY_PLACE_HOLDER);
Satish Sampath565505b2009-05-29 15:37:27 +01004056 case SHORTCUT_WIKIPEDIA_SEARCH:
4057 return URLUtil.composeSearchUrl(query, QuickSearch_W, QUERY_PLACE_HOLDER);
4058 case SHORTCUT_DICTIONARY_SEARCH:
4059 return URLUtil.composeSearchUrl(query, QuickSearch_D, QUERY_PLACE_HOLDER);
4060 case SHORTCUT_GOOGLE_MOBILE_LOCAL_SEARCH:
The Android Open Source Project0c908882009-03-03 19:32:16 -08004061 // FIXME: we need location in this case
Satish Sampath565505b2009-05-29 15:37:27 +01004062 return URLUtil.composeSearchUrl(query, QuickSearch_L, QUERY_PLACE_HOLDER);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004063 }
4064 }
4065 } else {
4066 if (Regex.WEB_URL_PATTERN.matcher(inUrl).matches()) {
4067 return URLUtil.guessUrl(inUrl);
4068 }
4069 }
4070
4071 Browser.addSearchUrl(mResolver, inUrl);
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004072 return URLUtil.composeSearchUrl(inUrl, QuickSearch_G, QUERY_PLACE_HOLDER);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004073 }
4074
Ben Murdochbff2d602009-07-01 20:19:05 +01004075 /* package */ void setShouldShowErrorConsole(boolean flag) {
4076 if (flag == mShouldShowErrorConsole) {
4077 // Nothing to do.
4078 return;
4079 }
4080
4081 mShouldShowErrorConsole = flag;
4082
4083 ErrorConsoleView errorConsole = mTabControl.getCurrentErrorConsole(true);
4084
4085 if (flag) {
4086 // Setting the show state of the console will cause it's the layout to be inflated.
4087 if (errorConsole.numberOfErrors() > 0) {
4088 errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
4089 } else {
4090 errorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
4091 }
4092
4093 // Now we can add it to the main view.
4094 mErrorConsoleContainer.addView(errorConsole,
4095 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
4096 ViewGroup.LayoutParams.WRAP_CONTENT));
4097 } else {
4098 mErrorConsoleContainer.removeView(errorConsole);
4099 }
4100
4101 }
4102
The Android Open Source Project0c908882009-03-03 19:32:16 -08004103 private final static int LOCK_ICON_UNSECURE = 0;
4104 private final static int LOCK_ICON_SECURE = 1;
4105 private final static int LOCK_ICON_MIXED = 2;
4106
4107 private int mLockIconType = LOCK_ICON_UNSECURE;
4108 private int mPrevLockType = LOCK_ICON_UNSECURE;
4109
4110 private BrowserSettings mSettings;
4111 private TabControl mTabControl;
4112 private ContentResolver mResolver;
4113 private FrameLayout mContentView;
Andrei Popescuadc008d2009-06-26 14:11:30 +01004114 private View mCustomView;
4115 private FrameLayout mCustomViewContainer;
Andrei Popescuc9b55562009-07-07 10:51:15 +01004116 private WebChromeClient.CustomViewCallback mCustomViewCallback;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004117
4118 // FIXME, temp address onPrepareMenu performance problem. When we move everything out of
4119 // view, we should rewrite this.
4120 private int mCurrentMenuState = 0;
4121 private int mMenuState = R.id.MAIN_MENU;
Andrei Popescuadc008d2009-06-26 14:11:30 +01004122 private int mOldMenuState = EMPTY_MENU;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004123 private static final int EMPTY_MENU = -1;
4124 private Menu mMenu;
4125
4126 private FindDialog mFindDialog;
4127 // Used to prevent chording to result in firing two shortcuts immediately
4128 // one after another. Fixes bug 1211714.
4129 boolean mCanChord;
4130
4131 private boolean mInLoad;
4132 private boolean mIsNetworkUp;
4133
4134 private boolean mPageStarted;
4135 private boolean mActivityInPause = true;
4136
4137 private boolean mMenuIsDown;
4138
4139 private final KeyTracker mKeyTracker = new KeyTracker(this);
4140
4141 // As trackball doesn't send repeat down, we have to track it ourselves
4142 private boolean mTrackTrackball;
4143
4144 private static boolean mInTrace;
4145
4146 // Performance probe
4147 private static final int[] SYSTEM_CPU_FORMAT = new int[] {
4148 Process.PROC_SPACE_TERM | Process.PROC_COMBINE,
4149 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 1: user time
4150 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 2: nice time
4151 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 3: sys time
4152 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 4: idle time
4153 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 5: iowait time
4154 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG, // 6: irq time
4155 Process.PROC_SPACE_TERM | Process.PROC_OUT_LONG // 7: softirq time
4156 };
4157
4158 private long mStart;
4159 private long mProcessStart;
4160 private long mUserStart;
4161 private long mSystemStart;
4162 private long mIdleStart;
4163 private long mIrqStart;
4164
4165 private long mUiStart;
4166
4167 private Drawable mMixLockIcon;
4168 private Drawable mSecLockIcon;
4169 private Drawable mGenericFavicon;
4170
4171 /* hold a ref so we can auto-cancel if necessary */
4172 private AlertDialog mAlertDialog;
4173
4174 // Wait for credentials before loading google.com
4175 private ProgressDialog mCredsDlg;
4176
4177 // The up-to-date URL and title (these can be different from those stored
4178 // in WebView, since it takes some time for the information in WebView to
4179 // get updated)
4180 private String mUrl;
4181 private String mTitle;
4182
4183 // As PageInfo has different style for landscape / portrait, we have
4184 // to re-open it when configuration changed
4185 private AlertDialog mPageInfoDialog;
4186 private TabControl.Tab mPageInfoView;
4187 // If the Page-Info dialog is launched from the SSL-certificate-on-error
4188 // dialog, we should not just dismiss it, but should get back to the
4189 // SSL-certificate-on-error dialog. This flag is used to store this state
4190 private Boolean mPageInfoFromShowSSLCertificateOnError;
4191
4192 // as SSLCertificateOnError has different style for landscape / portrait,
4193 // we have to re-open it when configuration changed
4194 private AlertDialog mSSLCertificateOnErrorDialog;
4195 private WebView mSSLCertificateOnErrorView;
4196 private SslErrorHandler mSSLCertificateOnErrorHandler;
4197 private SslError mSSLCertificateOnErrorError;
4198
4199 // as SSLCertificate has different style for landscape / portrait, we
4200 // have to re-open it when configuration changed
4201 private AlertDialog mSSLCertificateDialog;
4202 private TabControl.Tab mSSLCertificateView;
4203
4204 // as HttpAuthentication has different style for landscape / portrait, we
4205 // have to re-open it when configuration changed
4206 private AlertDialog mHttpAuthenticationDialog;
4207 private HttpAuthHandler mHttpAuthHandler;
4208
4209 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS =
4210 new FrameLayout.LayoutParams(
4211 ViewGroup.LayoutParams.FILL_PARENT,
4212 ViewGroup.LayoutParams.FILL_PARENT);
Andrei Popescuadc008d2009-06-26 14:11:30 +01004213 /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
4214 new FrameLayout.LayoutParams(
4215 ViewGroup.LayoutParams.FILL_PARENT,
4216 ViewGroup.LayoutParams.FILL_PARENT,
4217 Gravity.CENTER);
Grace Kloba47fdfdb2009-06-30 11:15:34 -07004218 // Google search
4219 final static String QuickSearch_G = "http://www.google.com/m?q=%s";
The Android Open Source Project0c908882009-03-03 19:32:16 -08004220 // Wikipedia search
4221 final static String QuickSearch_W = "http://en.wikipedia.org/w/index.php?search=%s&go=Go";
4222 // Dictionary search
4223 final static String QuickSearch_D = "http://dictionary.reference.com/search?q=%s";
4224 // Google Mobile Local search
4225 final static String QuickSearch_L = "http://www.google.com/m/search?site=local&q=%s&near=mountain+view";
4226
4227 final static String QUERY_PLACE_HOLDER = "%s";
4228
4229 // "source" parameter for Google search through search key
4230 final static String GOOGLE_SEARCH_SOURCE_SEARCHKEY = "browser-key";
4231 // "source" parameter for Google search through goto menu
4232 final static String GOOGLE_SEARCH_SOURCE_GOTO = "browser-goto";
4233 // "source" parameter for Google search through simplily type
4234 final static String GOOGLE_SEARCH_SOURCE_TYPE = "browser-type";
4235 // "source" parameter for Google search suggested by the browser
4236 final static String GOOGLE_SEARCH_SOURCE_SUGGEST = "browser-suggest";
4237 // "source" parameter for Google search from unknown source
4238 final static String GOOGLE_SEARCH_SOURCE_UNKNOWN = "unknown";
4239
4240 private final static String LOGTAG = "browser";
4241
The Android Open Source Project0c908882009-03-03 19:32:16 -08004242 private String mLastEnteredUrl;
4243
4244 private PowerManager.WakeLock mWakeLock;
4245 private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
4246
4247 private Toast mStopToast;
4248
Leon Scroggins1f005d32009-08-10 17:36:42 -04004249 private TitleBarSet mTitleBar;
Leon Scroggins81db3662009-06-04 17:45:11 -04004250
Ben Murdochbff2d602009-07-01 20:19:05 +01004251 private LinearLayout mErrorConsoleContainer = null;
4252 private boolean mShouldShowErrorConsole = false;
4253
The Android Open Source Project0c908882009-03-03 19:32:16 -08004254 // As the ids are dynamically created, we can't guarantee that they will
4255 // be in sequence, so this static array maps ids to a window number.
4256 final static private int[] WINDOW_SHORTCUT_ID_ARRAY =
4257 { R.id.window_one_menu_id, R.id.window_two_menu_id, R.id.window_three_menu_id,
4258 R.id.window_four_menu_id, R.id.window_five_menu_id, R.id.window_six_menu_id,
4259 R.id.window_seven_menu_id, R.id.window_eight_menu_id };
4260
4261 // monitor platform changes
4262 private IntentFilter mNetworkStateChangedFilter;
4263 private BroadcastReceiver mNetworkStateIntentReceiver;
4264
Grace Klobab4da0ad2009-05-14 14:45:40 -07004265 private BroadcastReceiver mPackageInstallationReceiver;
4266
The Android Open Source Project0c908882009-03-03 19:32:16 -08004267 // activity requestCode
Nicolas Roard78a98e42009-05-11 13:34:17 +01004268 final static int COMBO_PAGE = 1;
4269 final static int DOWNLOAD_PAGE = 2;
4270 final static int PREFERENCES_PAGE = 3;
The Android Open Source Project0c908882009-03-03 19:32:16 -08004271
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004272 /**
4273 * A UrlData class to abstract how the content will be set to WebView.
4274 * This base class uses loadUrl to show the content.
4275 */
4276 private static class UrlData {
4277 String mUrl;
Grace Kloba60e095c2009-06-16 11:50:55 -07004278 byte[] mPostData;
4279
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004280 UrlData(String url) {
4281 this.mUrl = url;
4282 }
Grace Kloba60e095c2009-06-16 11:50:55 -07004283
4284 void setPostData(byte[] postData) {
4285 mPostData = postData;
4286 }
4287
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004288 boolean isEmpty() {
4289 return mUrl == null || mUrl.length() == 0;
4290 }
4291
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004292 public void loadIn(WebView webView) {
Grace Kloba60e095c2009-06-16 11:50:55 -07004293 if (mPostData != null) {
4294 webView.postUrl(mUrl, mPostData);
4295 } else {
4296 webView.loadUrl(mUrl);
4297 }
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004298 }
4299 };
4300
4301 /**
4302 * A subclass of UrlData class that can display inlined content using
4303 * {@link WebView#loadDataWithBaseURL(String, String, String, String, String)}.
4304 */
4305 private static class InlinedUrlData extends UrlData {
4306 InlinedUrlData(String inlined, String mimeType, String encoding, String failUrl) {
4307 super(failUrl);
4308 mInlined = inlined;
4309 mMimeType = mimeType;
4310 mEncoding = encoding;
4311 }
4312 String mMimeType;
4313 String mInlined;
4314 String mEncoding;
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004315 @Override
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004316 boolean isEmpty() {
Ben Murdochbff2d602009-07-01 20:19:05 +01004317 return mInlined == null || mInlined.length() == 0 || super.isEmpty();
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004318 }
4319
Mitsuru Oshima7944b7d2009-06-16 16:34:51 -07004320 @Override
4321 public void loadIn(WebView webView) {
Mitsuru Oshima25ad8ab2009-06-10 16:26:07 -07004322 webView.loadDataWithBaseURL(null, mInlined, mMimeType, mEncoding, mUrl);
4323 }
4324 }
4325
Leon Scroggins1f005d32009-08-10 17:36:42 -04004326 /* package */ static final UrlData EMPTY_URL_DATA = new UrlData(null);
The Android Open Source Project0c908882009-03-03 19:32:16 -08004327}