mobile/android/base/GeckoApp.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
michael@0 2 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 package org.mozilla.gecko;
michael@0 7
michael@0 8 import java.io.BufferedOutputStream;
michael@0 9 import java.io.ByteArrayOutputStream;
michael@0 10 import java.io.File;
michael@0 11 import java.io.IOException;
michael@0 12 import java.io.InputStream;
michael@0 13 import java.io.OutputStream;
michael@0 14 import java.net.HttpURLConnection;
michael@0 15 import java.net.URL;
michael@0 16 import java.text.DateFormat;
michael@0 17 import java.text.SimpleDateFormat;
michael@0 18 import java.util.ArrayList;
michael@0 19 import java.util.Arrays;
michael@0 20 import java.util.Date;
michael@0 21 import java.util.HashMap;
michael@0 22 import java.util.Iterator;
michael@0 23 import java.util.LinkedList;
michael@0 24 import java.util.List;
michael@0 25 import java.util.Locale;
michael@0 26 import java.util.Map;
michael@0 27 import java.util.Set;
michael@0 28 import java.util.regex.Matcher;
michael@0 29 import java.util.regex.Pattern;
michael@0 30
michael@0 31 import org.json.JSONArray;
michael@0 32 import org.json.JSONException;
michael@0 33 import org.json.JSONObject;
michael@0 34 import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
michael@0 35 import org.mozilla.gecko.background.announcements.AnnouncementsBroadcastService;
michael@0 36 import org.mozilla.gecko.db.BrowserDB;
michael@0 37 import org.mozilla.gecko.favicons.Favicons;
michael@0 38 import org.mozilla.gecko.gfx.BitmapUtils;
michael@0 39 import org.mozilla.gecko.gfx.Layer;
michael@0 40 import org.mozilla.gecko.gfx.LayerView;
michael@0 41 import org.mozilla.gecko.gfx.PluginLayer;
michael@0 42 import org.mozilla.gecko.health.HealthRecorder;
michael@0 43 import org.mozilla.gecko.health.SessionInformation;
michael@0 44 import org.mozilla.gecko.health.StubbedHealthRecorder;
michael@0 45 import org.mozilla.gecko.menu.GeckoMenu;
michael@0 46 import org.mozilla.gecko.menu.GeckoMenuInflater;
michael@0 47 import org.mozilla.gecko.menu.MenuPanel;
michael@0 48 import org.mozilla.gecko.mozglue.GeckoLoader;
michael@0 49 import org.mozilla.gecko.preferences.GeckoPreferences;
michael@0 50 import org.mozilla.gecko.prompts.PromptService;
michael@0 51 import org.mozilla.gecko.updater.UpdateService;
michael@0 52 import org.mozilla.gecko.updater.UpdateServiceHelper;
michael@0 53 import org.mozilla.gecko.util.ActivityResultHandler;
michael@0 54 import org.mozilla.gecko.util.FileUtils;
michael@0 55 import org.mozilla.gecko.util.GeckoEventListener;
michael@0 56 import org.mozilla.gecko.util.HardwareUtils;
michael@0 57 import org.mozilla.gecko.util.ThreadUtils;
michael@0 58 import org.mozilla.gecko.util.UiAsyncTask;
michael@0 59 import org.mozilla.gecko.webapp.EventListener;
michael@0 60 import org.mozilla.gecko.webapp.UninstallListener;
michael@0 61 import org.mozilla.gecko.widget.ButtonToast;
michael@0 62
michael@0 63 import android.app.Activity;
michael@0 64 import android.app.AlertDialog;
michael@0 65 import android.app.Dialog;
michael@0 66 import android.content.Context;
michael@0 67 import android.content.DialogInterface;
michael@0 68 import android.content.Intent;
michael@0 69 import android.content.SharedPreferences;
michael@0 70 import android.content.pm.PackageManager.NameNotFoundException;
michael@0 71 import android.content.res.Configuration;
michael@0 72 import android.graphics.Bitmap;
michael@0 73 import android.graphics.BitmapFactory;
michael@0 74 import android.graphics.RectF;
michael@0 75 import android.graphics.drawable.Drawable;
michael@0 76 import android.hardware.Sensor;
michael@0 77 import android.hardware.SensorEvent;
michael@0 78 import android.hardware.SensorEventListener;
michael@0 79 import android.location.Location;
michael@0 80 import android.location.LocationListener;
michael@0 81 import android.net.Uri;
michael@0 82 import android.net.wifi.ScanResult;
michael@0 83 import android.net.wifi.WifiManager;
michael@0 84 import android.os.Build;
michael@0 85 import android.os.Bundle;
michael@0 86 import android.os.Handler;
michael@0 87 import android.os.PowerManager;
michael@0 88 import android.os.StrictMode;
michael@0 89 import android.provider.ContactsContract;
michael@0 90 import android.provider.MediaStore.Images.Media;
michael@0 91 import android.telephony.CellLocation;
michael@0 92 import android.telephony.NeighboringCellInfo;
michael@0 93 import android.telephony.PhoneStateListener;
michael@0 94 import android.telephony.SignalStrength;
michael@0 95 import android.telephony.TelephonyManager;
michael@0 96 import android.telephony.gsm.GsmCellLocation;
michael@0 97 import android.text.TextUtils;
michael@0 98 import android.util.AttributeSet;
michael@0 99 import android.util.Base64;
michael@0 100 import android.util.Log;
michael@0 101 import android.util.SparseBooleanArray;
michael@0 102 import android.view.Gravity;
michael@0 103 import android.view.KeyEvent;
michael@0 104 import android.view.Menu;
michael@0 105 import android.view.MenuInflater;
michael@0 106 import android.view.MenuItem;
michael@0 107 import android.view.MotionEvent;
michael@0 108 import android.view.OrientationEventListener;
michael@0 109 import android.view.SurfaceHolder;
michael@0 110 import android.view.SurfaceView;
michael@0 111 import android.view.TextureView;
michael@0 112 import android.view.View;
michael@0 113 import android.view.ViewGroup;
michael@0 114 import android.view.ViewStub;
michael@0 115 import android.view.Window;
michael@0 116 import android.view.WindowManager;
michael@0 117 import android.widget.AbsoluteLayout;
michael@0 118 import android.widget.FrameLayout;
michael@0 119 import android.widget.ListView;
michael@0 120 import android.widget.RelativeLayout;
michael@0 121 import android.widget.SimpleAdapter;
michael@0 122 import android.widget.TextView;
michael@0 123 import android.widget.Toast;
michael@0 124
michael@0 125 public abstract class GeckoApp
michael@0 126 extends GeckoActivity
michael@0 127 implements
michael@0 128 ContextGetter,
michael@0 129 GeckoAppShell.GeckoInterface,
michael@0 130 GeckoEventListener,
michael@0 131 GeckoMenu.Callback,
michael@0 132 GeckoMenu.MenuPresenter,
michael@0 133 LocationListener,
michael@0 134 SensorEventListener,
michael@0 135 Tabs.OnTabsChangedListener
michael@0 136 {
michael@0 137 private static final String LOGTAG = "GeckoApp";
michael@0 138 private static final int ONE_DAY_MS = 1000*60*60*24;
michael@0 139
michael@0 140 private static enum StartupAction {
michael@0 141 NORMAL, /* normal application start */
michael@0 142 URL, /* launched with a passed URL */
michael@0 143 PREFETCH /* launched with a passed URL that we prefetch */
michael@0 144 }
michael@0 145
michael@0 146 public static final String ACTION_ALERT_CALLBACK = "org.mozilla.gecko.ACTION_ALERT_CALLBACK";
michael@0 147 public static final String ACTION_BOOKMARK = "org.mozilla.gecko.BOOKMARK";
michael@0 148 public static final String ACTION_DEBUG = "org.mozilla.gecko.DEBUG";
michael@0 149 public static final String ACTION_LAUNCH_SETTINGS = "org.mozilla.gecko.SETTINGS";
michael@0 150 public static final String ACTION_LOAD = "org.mozilla.gecko.LOAD";
michael@0 151 public static final String ACTION_INIT_PW = "org.mozilla.gecko.INIT_PW";
michael@0 152 public static final String ACTION_WEBAPP_PREFIX = "org.mozilla.gecko.WEBAPP";
michael@0 153
michael@0 154 public static final String EXTRA_STATE_BUNDLE = "stateBundle";
michael@0 155
michael@0 156 public static final String PREFS_ALLOW_STATE_BUNDLE = "allowStateBundle";
michael@0 157 public static final String PREFS_OOM_EXCEPTION = "OOMException";
michael@0 158 public static final String PREFS_VERSION_CODE = "versionCode";
michael@0 159 public static final String PREFS_WAS_STOPPED = "wasStopped";
michael@0 160 public static final String PREFS_CRASHED = "crashed";
michael@0 161 public static final String PREFS_CLEANUP_TEMP_FILES = "cleanupTempFiles";
michael@0 162
michael@0 163 public static final String SAVED_STATE_IN_BACKGROUND = "inBackground";
michael@0 164 public static final String SAVED_STATE_PRIVATE_SESSION = "privateSession";
michael@0 165
michael@0 166 static private final String LOCATION_URL = "https://location.services.mozilla.com/v1/submit";
michael@0 167
michael@0 168 // Delay before running one-time "cleanup" tasks that may be needed
michael@0 169 // after a version upgrade.
michael@0 170 private static final int CLEANUP_DEFERRAL_SECONDS = 15;
michael@0 171
michael@0 172 protected RelativeLayout mMainLayout;
michael@0 173 protected RelativeLayout mGeckoLayout;
michael@0 174 public View getView() { return mGeckoLayout; }
michael@0 175 private View mCameraView;
michael@0 176 private OrientationEventListener mCameraOrientationEventListener;
michael@0 177 public List<GeckoAppShell.AppStateListener> mAppStateListeners;
michael@0 178 protected MenuPanel mMenuPanel;
michael@0 179 protected Menu mMenu;
michael@0 180 protected GeckoProfile mProfile;
michael@0 181 protected boolean mIsRestoringActivity;
michael@0 182
michael@0 183 private ContactService mContactService;
michael@0 184 private PromptService mPromptService;
michael@0 185 private TextSelection mTextSelection;
michael@0 186
michael@0 187 protected DoorHangerPopup mDoorHangerPopup;
michael@0 188 protected FormAssistPopup mFormAssistPopup;
michael@0 189 protected ButtonToast mToast;
michael@0 190
michael@0 191 protected LayerView mLayerView;
michael@0 192 private AbsoluteLayout mPluginContainer;
michael@0 193
michael@0 194 private FullScreenHolder mFullScreenPluginContainer;
michael@0 195 private View mFullScreenPluginView;
michael@0 196
michael@0 197 private HashMap<String, PowerManager.WakeLock> mWakeLocks = new HashMap<String, PowerManager.WakeLock>();
michael@0 198
michael@0 199 protected boolean mShouldRestore;
michael@0 200 protected boolean mInitialized = false;
michael@0 201 private Telemetry.Timer mJavaUiStartupTimer;
michael@0 202 private Telemetry.Timer mGeckoReadyStartupTimer;
michael@0 203
michael@0 204 private String mPrivateBrowsingSession;
michael@0 205
michael@0 206 private volatile HealthRecorder mHealthRecorder = null;
michael@0 207
michael@0 208 private int mSignalStrenth;
michael@0 209 private PhoneStateListener mPhoneStateListener = null;
michael@0 210 private boolean mShouldReportGeoData;
michael@0 211 private EventListener mWebappEventListener;
michael@0 212
michael@0 213 abstract public int getLayout();
michael@0 214 abstract public boolean hasTabsSideBar();
michael@0 215 abstract protected String getDefaultProfileName() throws NoMozillaDirectoryException;
michael@0 216
michael@0 217 private static final String RESTARTER_ACTION = "org.mozilla.gecko.restart";
michael@0 218 private static final String RESTARTER_CLASS = "org.mozilla.gecko.Restarter";
michael@0 219
michael@0 220 @SuppressWarnings("serial")
michael@0 221 class SessionRestoreException extends Exception {
michael@0 222 public SessionRestoreException(Exception e) {
michael@0 223 super(e);
michael@0 224 }
michael@0 225
michael@0 226 public SessionRestoreException(String message) {
michael@0 227 super(message);
michael@0 228 }
michael@0 229 }
michael@0 230
michael@0 231 void toggleChrome(final boolean aShow) { }
michael@0 232
michael@0 233 void focusChrome() { }
michael@0 234
michael@0 235 @Override
michael@0 236 public Context getContext() {
michael@0 237 return this;
michael@0 238 }
michael@0 239
michael@0 240 @Override
michael@0 241 public SharedPreferences getSharedPreferences() {
michael@0 242 return GeckoSharedPrefs.forApp(this);
michael@0 243 }
michael@0 244
michael@0 245 public Activity getActivity() {
michael@0 246 return this;
michael@0 247 }
michael@0 248
michael@0 249 public LocationListener getLocationListener() {
michael@0 250 if (mShouldReportGeoData && mPhoneStateListener == null) {
michael@0 251 mPhoneStateListener = new PhoneStateListener() {
michael@0 252 public void onSignalStrengthsChanged(SignalStrength signalStrength) {
michael@0 253 setCurrentSignalStrenth(signalStrength);
michael@0 254 }
michael@0 255 };
michael@0 256 TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
michael@0 257 tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
michael@0 258 }
michael@0 259 return this;
michael@0 260 }
michael@0 261
michael@0 262 public SensorEventListener getSensorEventListener() {
michael@0 263 return this;
michael@0 264 }
michael@0 265
michael@0 266 public View getCameraView() {
michael@0 267 return mCameraView;
michael@0 268 }
michael@0 269
michael@0 270 public void addAppStateListener(GeckoAppShell.AppStateListener listener) {
michael@0 271 mAppStateListeners.add(listener);
michael@0 272 }
michael@0 273
michael@0 274 public void removeAppStateListener(GeckoAppShell.AppStateListener listener) {
michael@0 275 mAppStateListeners.remove(listener);
michael@0 276 }
michael@0 277
michael@0 278 public FormAssistPopup getFormAssistPopup() {
michael@0 279 return mFormAssistPopup;
michael@0 280 }
michael@0 281
michael@0 282 @Override
michael@0 283 public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
michael@0 284 // When a tab is closed, it is always unselected first.
michael@0 285 // When a tab is unselected, another tab is always selected first.
michael@0 286 switch(msg) {
michael@0 287 case UNSELECTED:
michael@0 288 hidePlugins(tab);
michael@0 289 break;
michael@0 290
michael@0 291 case LOCATION_CHANGE:
michael@0 292 // We only care about location change for the selected tab.
michael@0 293 if (!Tabs.getInstance().isSelectedTab(tab))
michael@0 294 break;
michael@0 295 // Fall through...
michael@0 296 case SELECTED:
michael@0 297 invalidateOptionsMenu();
michael@0 298 if (mFormAssistPopup != null)
michael@0 299 mFormAssistPopup.hide();
michael@0 300 break;
michael@0 301
michael@0 302 case LOADED:
michael@0 303 // Sync up the layer view and the tab if the tab is
michael@0 304 // currently displayed.
michael@0 305 LayerView layerView = mLayerView;
michael@0 306 if (layerView != null && Tabs.getInstance().isSelectedTab(tab))
michael@0 307 layerView.setBackgroundColor(tab.getBackgroundColor());
michael@0 308 break;
michael@0 309
michael@0 310 case DESKTOP_MODE_CHANGE:
michael@0 311 if (Tabs.getInstance().isSelectedTab(tab))
michael@0 312 invalidateOptionsMenu();
michael@0 313 break;
michael@0 314 }
michael@0 315 }
michael@0 316
michael@0 317 public void refreshChrome() { }
michael@0 318
michael@0 319 @Override
michael@0 320 public void invalidateOptionsMenu() {
michael@0 321 if (mMenu == null)
michael@0 322 return;
michael@0 323
michael@0 324 onPrepareOptionsMenu(mMenu);
michael@0 325
michael@0 326 if (Build.VERSION.SDK_INT >= 11)
michael@0 327 super.invalidateOptionsMenu();
michael@0 328 }
michael@0 329
michael@0 330 @Override
michael@0 331 public boolean onCreateOptionsMenu(Menu menu) {
michael@0 332 mMenu = menu;
michael@0 333
michael@0 334 MenuInflater inflater = getMenuInflater();
michael@0 335 inflater.inflate(R.menu.gecko_app_menu, mMenu);
michael@0 336 return true;
michael@0 337 }
michael@0 338
michael@0 339 @Override
michael@0 340 public MenuInflater getMenuInflater() {
michael@0 341 if (Build.VERSION.SDK_INT >= 11)
michael@0 342 return new GeckoMenuInflater(this);
michael@0 343 else
michael@0 344 return super.getMenuInflater();
michael@0 345 }
michael@0 346
michael@0 347 public MenuPanel getMenuPanel() {
michael@0 348 if (mMenuPanel == null) {
michael@0 349 onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, null);
michael@0 350 invalidateOptionsMenu();
michael@0 351 }
michael@0 352 return mMenuPanel;
michael@0 353 }
michael@0 354
michael@0 355 @Override
michael@0 356 public boolean onMenuItemSelected(MenuItem item) {
michael@0 357 return onOptionsItemSelected(item);
michael@0 358 }
michael@0 359
michael@0 360 @Override
michael@0 361 public void openMenu() {
michael@0 362 openOptionsMenu();
michael@0 363 }
michael@0 364
michael@0 365 @Override
michael@0 366 public void showMenu(final View menu) {
michael@0 367 // On devices using the custom menu, focus is cleared from the menu when its tapped.
michael@0 368 // Close and then reshow it to avoid these issues. See bug 794581 and bug 968182.
michael@0 369 closeMenu();
michael@0 370
michael@0 371 // Post the reshow code back to the UI thread to avoid some optimizations Android
michael@0 372 // has put in place for menus that hide/show themselves quickly. See bug 985400.
michael@0 373 ThreadUtils.postToUiThread(new Runnable() {
michael@0 374 @Override
michael@0 375 public void run() {
michael@0 376 mMenuPanel.removeAllViews();
michael@0 377 mMenuPanel.addView(menu);
michael@0 378 openOptionsMenu();
michael@0 379 }
michael@0 380 });
michael@0 381 }
michael@0 382
michael@0 383 @Override
michael@0 384 public void closeMenu() {
michael@0 385 closeOptionsMenu();
michael@0 386 }
michael@0 387
michael@0 388 @Override
michael@0 389 public View onCreatePanelView(int featureId) {
michael@0 390 if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL) {
michael@0 391 if (mMenuPanel == null) {
michael@0 392 mMenuPanel = new MenuPanel(this, null);
michael@0 393 } else {
michael@0 394 // Prepare the panel everytime before showing the menu.
michael@0 395 onPreparePanel(featureId, mMenuPanel, mMenu);
michael@0 396 }
michael@0 397
michael@0 398 return mMenuPanel;
michael@0 399 }
michael@0 400
michael@0 401 return super.onCreatePanelView(featureId);
michael@0 402 }
michael@0 403
michael@0 404 @Override
michael@0 405 public boolean onCreatePanelMenu(int featureId, Menu menu) {
michael@0 406 if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL) {
michael@0 407 if (mMenuPanel == null) {
michael@0 408 mMenuPanel = (MenuPanel) onCreatePanelView(featureId);
michael@0 409 }
michael@0 410
michael@0 411 GeckoMenu gMenu = new GeckoMenu(this, null);
michael@0 412 gMenu.setCallback(this);
michael@0 413 gMenu.setMenuPresenter(this);
michael@0 414 menu = gMenu;
michael@0 415 mMenuPanel.addView(gMenu);
michael@0 416
michael@0 417 return onCreateOptionsMenu(menu);
michael@0 418 }
michael@0 419
michael@0 420 return super.onCreatePanelMenu(featureId, menu);
michael@0 421 }
michael@0 422
michael@0 423 @Override
michael@0 424 public boolean onPreparePanel(int featureId, View view, Menu menu) {
michael@0 425 if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL)
michael@0 426 return onPrepareOptionsMenu(menu);
michael@0 427
michael@0 428 return super.onPreparePanel(featureId, view, menu);
michael@0 429 }
michael@0 430
michael@0 431 @Override
michael@0 432 public boolean onMenuOpened(int featureId, Menu menu) {
michael@0 433 // exit full-screen mode whenever the menu is opened
michael@0 434 if (mLayerView != null && mLayerView.isFullScreen()) {
michael@0 435 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FullScreen:Exit", null));
michael@0 436 }
michael@0 437
michael@0 438 if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL) {
michael@0 439 if (mMenu == null) {
michael@0 440 // getMenuPanel() will force the creation of the menu as well
michael@0 441 MenuPanel panel = getMenuPanel();
michael@0 442 onPreparePanel(featureId, panel, mMenu);
michael@0 443 }
michael@0 444
michael@0 445 // Scroll custom menu to the top
michael@0 446 if (mMenuPanel != null)
michael@0 447 mMenuPanel.scrollTo(0, 0);
michael@0 448
michael@0 449 return true;
michael@0 450 }
michael@0 451
michael@0 452 return super.onMenuOpened(featureId, menu);
michael@0 453 }
michael@0 454
michael@0 455 @Override
michael@0 456 public boolean onOptionsItemSelected(MenuItem item) {
michael@0 457 if (item.getItemId() == R.id.quit) {
michael@0 458 if (GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.GeckoRunning, GeckoThread.LaunchState.GeckoExiting)) {
michael@0 459 GeckoAppShell.notifyGeckoOfEvent(GeckoEvent.createBroadcastEvent("Browser:Quit", null));
michael@0 460 } else {
michael@0 461 GeckoAppShell.systemExit();
michael@0 462 }
michael@0 463 return true;
michael@0 464 }
michael@0 465
michael@0 466 return super.onOptionsItemSelected(item);
michael@0 467 }
michael@0 468
michael@0 469 @Override
michael@0 470 public void onOptionsMenuClosed(Menu menu) {
michael@0 471 if (Build.VERSION.SDK_INT >= 11) {
michael@0 472 mMenuPanel.removeAllViews();
michael@0 473 mMenuPanel.addView((GeckoMenu) mMenu);
michael@0 474 }
michael@0 475 }
michael@0 476
michael@0 477 @Override
michael@0 478 public boolean onKeyDown(int keyCode, KeyEvent event) {
michael@0 479 // Handle hardware menu key presses separately so that we can show a custom menu in some cases.
michael@0 480 if (keyCode == KeyEvent.KEYCODE_MENU) {
michael@0 481 openOptionsMenu();
michael@0 482 return true;
michael@0 483 }
michael@0 484
michael@0 485 return super.onKeyDown(keyCode, event);
michael@0 486 }
michael@0 487
michael@0 488 @Override
michael@0 489 protected void onSaveInstanceState(Bundle outState) {
michael@0 490 super.onSaveInstanceState(outState);
michael@0 491
michael@0 492 if (mToast != null) {
michael@0 493 mToast.onSaveInstanceState(outState);
michael@0 494 }
michael@0 495
michael@0 496 outState.putBoolean(SAVED_STATE_IN_BACKGROUND, isApplicationInBackground());
michael@0 497 outState.putString(SAVED_STATE_PRIVATE_SESSION, mPrivateBrowsingSession);
michael@0 498 }
michael@0 499
michael@0 500 void handleFaviconRequest(final String url) {
michael@0 501 (new UiAsyncTask<Void, Void, String>(ThreadUtils.getBackgroundHandler()) {
michael@0 502 @Override
michael@0 503 public String doInBackground(Void... params) {
michael@0 504 return Favicons.getFaviconURLForPageURL(url);
michael@0 505 }
michael@0 506
michael@0 507 @Override
michael@0 508 public void onPostExecute(String faviconUrl) {
michael@0 509 JSONObject args = new JSONObject();
michael@0 510
michael@0 511 if (faviconUrl != null) {
michael@0 512 try {
michael@0 513 args.put("url", url);
michael@0 514 args.put("faviconUrl", faviconUrl);
michael@0 515 } catch (JSONException e) {
michael@0 516 Log.w(LOGTAG, "Error building JSON favicon arguments.", e);
michael@0 517 }
michael@0 518 }
michael@0 519
michael@0 520 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:FaviconReturn", args.toString()));
michael@0 521 }
michael@0 522 }).execute();
michael@0 523 }
michael@0 524
michael@0 525 void handleClearHistory() {
michael@0 526 BrowserDB.clearHistory(getContentResolver());
michael@0 527 }
michael@0 528
michael@0 529 public void addTab() { }
michael@0 530
michael@0 531 public void addPrivateTab() { }
michael@0 532
michael@0 533 public void showNormalTabs() { }
michael@0 534
michael@0 535 public void showPrivateTabs() { }
michael@0 536
michael@0 537 public void hideTabs() { }
michael@0 538
michael@0 539 /**
michael@0 540 * Close the tab UI indirectly (not as the result of a direct user
michael@0 541 * action). This does not force the UI to close; for example in Firefox
michael@0 542 * tablet mode it will remain open unless the user explicitly closes it.
michael@0 543 *
michael@0 544 * @return True if the tab UI was hidden.
michael@0 545 */
michael@0 546 public boolean autoHideTabs() { return false; }
michael@0 547
michael@0 548 public boolean areTabsShown() { return false; }
michael@0 549
michael@0 550 @Override
michael@0 551 public void handleMessage(String event, JSONObject message) {
michael@0 552 try {
michael@0 553 if (event.equals("Toast:Show")) {
michael@0 554 final String msg = message.getString("message");
michael@0 555 final JSONObject button = message.optJSONObject("button");
michael@0 556 if (button != null) {
michael@0 557 final String label = button.optString("label");
michael@0 558 final String icon = button.optString("icon");
michael@0 559 final String id = button.optString("id");
michael@0 560 showButtonToast(msg, label, icon, id);
michael@0 561 } else {
michael@0 562 final String duration = message.getString("duration");
michael@0 563 showNormalToast(msg, duration);
michael@0 564 }
michael@0 565 } else if (event.equals("log")) {
michael@0 566 // generic log listener
michael@0 567 final String msg = message.getString("msg");
michael@0 568 Log.d(LOGTAG, "Log: " + msg);
michael@0 569 } else if (event.equals("Reader:FaviconRequest")) {
michael@0 570 final String url = message.getString("url");
michael@0 571 handleFaviconRequest(url);
michael@0 572 } else if (event.equals("Gecko:DelayedStartup")) {
michael@0 573 ThreadUtils.postToBackgroundThread(new UninstallListener.DelayedStartupTask(this));
michael@0 574 } else if (event.equals("Gecko:Ready")) {
michael@0 575 mGeckoReadyStartupTimer.stop();
michael@0 576 geckoConnected();
michael@0 577
michael@0 578 // This method is already running on the background thread, so we
michael@0 579 // know that mHealthRecorder will exist. That doesn't stop us being
michael@0 580 // paranoid.
michael@0 581 // This method is cheap, so don't spawn a new runnable.
michael@0 582 final HealthRecorder rec = mHealthRecorder;
michael@0 583 if (rec != null) {
michael@0 584 rec.recordGeckoStartupTime(mGeckoReadyStartupTimer.getElapsed());
michael@0 585 }
michael@0 586 } else if (event.equals("ToggleChrome:Hide")) {
michael@0 587 toggleChrome(false);
michael@0 588 } else if (event.equals("ToggleChrome:Show")) {
michael@0 589 toggleChrome(true);
michael@0 590 } else if (event.equals("ToggleChrome:Focus")) {
michael@0 591 focusChrome();
michael@0 592 } else if (event.equals("DOMFullScreen:Start")) {
michael@0 593 // Local ref to layerView for thread safety
michael@0 594 LayerView layerView = mLayerView;
michael@0 595 if (layerView != null) {
michael@0 596 layerView.setFullScreen(true);
michael@0 597 }
michael@0 598 } else if (event.equals("DOMFullScreen:Stop")) {
michael@0 599 // Local ref to layerView for thread safety
michael@0 600 LayerView layerView = mLayerView;
michael@0 601 if (layerView != null) {
michael@0 602 layerView.setFullScreen(false);
michael@0 603 }
michael@0 604 } else if (event.equals("Permissions:Data")) {
michael@0 605 String host = message.getString("host");
michael@0 606 JSONArray permissions = message.getJSONArray("permissions");
michael@0 607 showSiteSettingsDialog(host, permissions);
michael@0 608 } else if (event.equals("Session:StatePurged")) {
michael@0 609 onStatePurged();
michael@0 610 } else if (event.equals("Bookmark:Insert")) {
michael@0 611 final String url = message.getString("url");
michael@0 612 final String title = message.getString("title");
michael@0 613 final Context context = this;
michael@0 614 ThreadUtils.postToUiThread(new Runnable() {
michael@0 615 @Override
michael@0 616 public void run() {
michael@0 617 Toast.makeText(context, R.string.bookmark_added, Toast.LENGTH_SHORT).show();
michael@0 618 ThreadUtils.postToBackgroundThread(new Runnable() {
michael@0 619 @Override
michael@0 620 public void run() {
michael@0 621 BrowserDB.addBookmark(getContentResolver(), title, url);
michael@0 622 }
michael@0 623 });
michael@0 624 }
michael@0 625 });
michael@0 626 } else if (event.equals("Accessibility:Event")) {
michael@0 627 GeckoAccessibility.sendAccessibilityEvent(message);
michael@0 628 } else if (event.equals("Accessibility:Ready")) {
michael@0 629 GeckoAccessibility.updateAccessibilitySettings(this);
michael@0 630 } else if (event.equals("Shortcut:Remove")) {
michael@0 631 final String url = message.getString("url");
michael@0 632 final String origin = message.getString("origin");
michael@0 633 final String title = message.getString("title");
michael@0 634 final String type = message.getString("shortcutType");
michael@0 635 GeckoAppShell.removeShortcut(title, url, origin, type);
michael@0 636 } else if (event.equals("Share:Text")) {
michael@0 637 String text = message.getString("text");
michael@0 638 GeckoAppShell.openUriExternal(text, "text/plain", "", "", Intent.ACTION_SEND, "");
michael@0 639
michael@0 640 // Context: Sharing via chrome list (no explicit session is active)
michael@0 641 Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST);
michael@0 642 } else if (event.equals("Image:SetAs")) {
michael@0 643 String src = message.getString("url");
michael@0 644 setImageAs(src);
michael@0 645 } else if (event.equals("Sanitize:ClearHistory")) {
michael@0 646 handleClearHistory();
michael@0 647 } else if (event.equals("Update:Check")) {
michael@0 648 startService(new Intent(UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE, null, this, UpdateService.class));
michael@0 649 } else if (event.equals("Update:Download")) {
michael@0 650 startService(new Intent(UpdateServiceHelper.ACTION_DOWNLOAD_UPDATE, null, this, UpdateService.class));
michael@0 651 } else if (event.equals("Update:Install")) {
michael@0 652 startService(new Intent(UpdateServiceHelper.ACTION_APPLY_UPDATE, null, this, UpdateService.class));
michael@0 653 } else if (event.equals("PrivateBrowsing:Data")) {
michael@0 654 // null strings return "null" (http://code.google.com/p/android/issues/detail?id=13830)
michael@0 655 if (message.isNull("session")) {
michael@0 656 mPrivateBrowsingSession = null;
michael@0 657 } else {
michael@0 658 mPrivateBrowsingSession = message.getString("session");
michael@0 659 }
michael@0 660 } else if (event.equals("Contact:Add")) {
michael@0 661 if (!message.isNull("email")) {
michael@0 662 Uri contactUri = Uri.parse(message.getString("email"));
michael@0 663 Intent i = new Intent(ContactsContract.Intents.SHOW_OR_CREATE_CONTACT, contactUri);
michael@0 664 startActivity(i);
michael@0 665 } else if (!message.isNull("phone")) {
michael@0 666 Uri contactUri = Uri.parse(message.getString("phone"));
michael@0 667 Intent i = new Intent(ContactsContract.Intents.SHOW_OR_CREATE_CONTACT, contactUri);
michael@0 668 startActivity(i);
michael@0 669 } else {
michael@0 670 // something went wrong.
michael@0 671 Log.e(LOGTAG, "Received Contact:Add message with no email nor phone number");
michael@0 672 }
michael@0 673 } else if (event.equals("Intent:GetHandlers")) {
michael@0 674 Intent intent = GeckoAppShell.getOpenURIIntent((Context) this, message.optString("url"),
michael@0 675 message.optString("mime"), message.optString("action"), message.optString("title"));
michael@0 676 String[] handlers = GeckoAppShell.getHandlersForIntent(intent);
michael@0 677 List<String> appList = Arrays.asList(handlers);
michael@0 678 JSONObject handlersJSON = new JSONObject();
michael@0 679 handlersJSON.put("apps", new JSONArray(appList));
michael@0 680 EventDispatcher.sendResponse(message, handlersJSON);
michael@0 681 } else if (event.equals("Intent:Open")) {
michael@0 682 GeckoAppShell.openUriExternal(message.optString("url"),
michael@0 683 message.optString("mime"), message.optString("packageName"),
michael@0 684 message.optString("className"), message.optString("action"), message.optString("title"));
michael@0 685 } else if (event.equals("Intent:OpenForResult")) {
michael@0 686 Intent intent = GeckoAppShell.getOpenURIIntent(this,
michael@0 687 message.optString("url"),
michael@0 688 message.optString("mime"),
michael@0 689 message.optString("action"),
michael@0 690 message.optString("title"));
michael@0 691 intent.setClassName(message.optString("packageName"), message.optString("className"));
michael@0 692
michael@0 693 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
michael@0 694
michael@0 695 final JSONObject originalMessage = message;
michael@0 696 ActivityHandlerHelper.startIntentForActivity(this,
michael@0 697 intent,
michael@0 698 new ActivityResultHandler() {
michael@0 699 @Override
michael@0 700 public void onActivityResult (int resultCode, Intent data) {
michael@0 701 JSONObject response = new JSONObject();
michael@0 702
michael@0 703 try {
michael@0 704 if (data != null) {
michael@0 705 response.put("extras", bundleToJSON(data.getExtras()));
michael@0 706 }
michael@0 707 response.put("resultCode", resultCode);
michael@0 708 } catch (JSONException e) {
michael@0 709 Log.w(LOGTAG, "Error building JSON response.", e);
michael@0 710 }
michael@0 711
michael@0 712 EventDispatcher.sendResponse(originalMessage, response);
michael@0 713 }
michael@0 714 });
michael@0 715 } else if (event.equals("Locale:Set")) {
michael@0 716 setLocale(message.getString("locale"));
michael@0 717 } else if (event.equals("NativeApp:IsDebuggable")) {
michael@0 718 JSONObject ret = new JSONObject();
michael@0 719 ret.put("isDebuggable", getIsDebuggable());
michael@0 720 EventDispatcher.sendResponse(message, ret);
michael@0 721 } else if (event.equals("SystemUI:Visibility")) {
michael@0 722 setSystemUiVisible(message.getBoolean("visible"));
michael@0 723 }
michael@0 724 } catch (Exception e) {
michael@0 725 Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
michael@0 726 }
michael@0 727 }
michael@0 728
michael@0 729 void onStatePurged() { }
michael@0 730
michael@0 731 /**
michael@0 732 * @param aPermissions
michael@0 733 * Array of JSON objects to represent site permissions.
michael@0 734 * Example: { type: "offline-app", setting: "Store Offline Data", value: "Allow" }
michael@0 735 */
michael@0 736 private void showSiteSettingsDialog(String aHost, JSONArray aPermissions) {
michael@0 737 final AlertDialog.Builder builder = new AlertDialog.Builder(this);
michael@0 738
michael@0 739 View customTitleView = getLayoutInflater().inflate(R.layout.site_setting_title, null);
michael@0 740 ((TextView) customTitleView.findViewById(R.id.title)).setText(R.string.site_settings_title);
michael@0 741 ((TextView) customTitleView.findViewById(R.id.host)).setText(aHost);
michael@0 742 builder.setCustomTitle(customTitleView);
michael@0 743
michael@0 744 // If there are no permissions to clear, show the user a message about that.
michael@0 745 // In the future, we want to disable the menu item if there are no permissions to clear.
michael@0 746 if (aPermissions.length() == 0) {
michael@0 747 builder.setMessage(R.string.site_settings_no_settings);
michael@0 748 } else {
michael@0 749
michael@0 750 ArrayList <HashMap<String, String>> itemList = new ArrayList <HashMap<String, String>>();
michael@0 751 for (int i = 0; i < aPermissions.length(); i++) {
michael@0 752 try {
michael@0 753 JSONObject permObj = aPermissions.getJSONObject(i);
michael@0 754 HashMap<String, String> map = new HashMap<String, String>();
michael@0 755 map.put("setting", permObj.getString("setting"));
michael@0 756 map.put("value", permObj.getString("value"));
michael@0 757 itemList.add(map);
michael@0 758 } catch (JSONException e) {
michael@0 759 Log.w(LOGTAG, "Exception populating settings items.", e);
michael@0 760 }
michael@0 761 }
michael@0 762
michael@0 763 // setMultiChoiceItems doesn't support using an adapter, so we're creating a hack with
michael@0 764 // setSingleChoiceItems and changing the choiceMode below when we create the dialog
michael@0 765 builder.setSingleChoiceItems(new SimpleAdapter(
michael@0 766 GeckoApp.this,
michael@0 767 itemList,
michael@0 768 R.layout.site_setting_item,
michael@0 769 new String[] { "setting", "value" },
michael@0 770 new int[] { R.id.setting, R.id.value }
michael@0 771 ), -1, new DialogInterface.OnClickListener() {
michael@0 772 @Override
michael@0 773 public void onClick(DialogInterface dialog, int id) { }
michael@0 774 });
michael@0 775
michael@0 776 builder.setPositiveButton(R.string.site_settings_clear, new DialogInterface.OnClickListener() {
michael@0 777 @Override
michael@0 778 public void onClick(DialogInterface dialog, int id) {
michael@0 779 ListView listView = ((AlertDialog) dialog).getListView();
michael@0 780 SparseBooleanArray checkedItemPositions = listView.getCheckedItemPositions();
michael@0 781
michael@0 782 // An array of the indices of the permissions we want to clear
michael@0 783 JSONArray permissionsToClear = new JSONArray();
michael@0 784 for (int i = 0; i < checkedItemPositions.size(); i++)
michael@0 785 if (checkedItemPositions.get(i))
michael@0 786 permissionsToClear.put(i);
michael@0 787
michael@0 788 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(
michael@0 789 "Permissions:Clear", permissionsToClear.toString()));
michael@0 790 }
michael@0 791 });
michael@0 792 }
michael@0 793
michael@0 794 builder.setNegativeButton(R.string.site_settings_cancel, new DialogInterface.OnClickListener(){
michael@0 795 @Override
michael@0 796 public void onClick(DialogInterface dialog, int id) {
michael@0 797 dialog.cancel();
michael@0 798 }
michael@0 799 });
michael@0 800
michael@0 801 ThreadUtils.postToUiThread(new Runnable() {
michael@0 802 @Override
michael@0 803 public void run() {
michael@0 804 Dialog dialog = builder.create();
michael@0 805 dialog.show();
michael@0 806
michael@0 807 ListView listView = ((AlertDialog) dialog).getListView();
michael@0 808 if (listView != null) {
michael@0 809 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
michael@0 810 int listSize = listView.getAdapter().getCount();
michael@0 811 for (int i = 0; i < listSize; i++)
michael@0 812 listView.setItemChecked(i, true);
michael@0 813 }
michael@0 814 }
michael@0 815 });
michael@0 816 }
michael@0 817
michael@0 818 public void showToast(final int resId, final int duration) {
michael@0 819 ThreadUtils.postToUiThread(new Runnable() {
michael@0 820 @Override
michael@0 821 public void run() {
michael@0 822 Toast.makeText(GeckoApp.this, resId, duration).show();
michael@0 823 }
michael@0 824 });
michael@0 825 }
michael@0 826
michael@0 827 public void showNormalToast(final String message, final String duration) {
michael@0 828 ThreadUtils.postToUiThread(new Runnable() {
michael@0 829 @Override
michael@0 830 public void run() {
michael@0 831 Toast toast;
michael@0 832 if (duration.equals("long")) {
michael@0 833 toast = Toast.makeText(GeckoApp.this, message, Toast.LENGTH_LONG);
michael@0 834 } else {
michael@0 835 toast = Toast.makeText(GeckoApp.this, message, Toast.LENGTH_SHORT);
michael@0 836 }
michael@0 837 toast.show();
michael@0 838 }
michael@0 839 });
michael@0 840 }
michael@0 841
michael@0 842 protected ButtonToast getButtonToast() {
michael@0 843 if (mToast != null) {
michael@0 844 return mToast;
michael@0 845 }
michael@0 846
michael@0 847 ViewStub toastStub = (ViewStub) findViewById(R.id.toast_stub);
michael@0 848 mToast = new ButtonToast(toastStub.inflate());
michael@0 849
michael@0 850 return mToast;
michael@0 851 }
michael@0 852
michael@0 853 void showButtonToast(final String message, final String buttonText,
michael@0 854 final String buttonIcon, final String buttonId) {
michael@0 855 BitmapUtils.getDrawable(GeckoApp.this, buttonIcon, new BitmapUtils.BitmapLoader() {
michael@0 856 @Override
michael@0 857 public void onBitmapFound(final Drawable d) {
michael@0 858 getButtonToast().show(false, message, buttonText, d, new ButtonToast.ToastListener() {
michael@0 859 @Override
michael@0 860 public void onButtonClicked() {
michael@0 861 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Toast:Click", buttonId));
michael@0 862 }
michael@0 863
michael@0 864 @Override
michael@0 865 public void onToastHidden(ButtonToast.ReasonHidden reason) {
michael@0 866 if (reason == ButtonToast.ReasonHidden.TIMEOUT) {
michael@0 867 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Toast:Hidden", buttonId));
michael@0 868 }
michael@0 869 }
michael@0 870 });
michael@0 871 }
michael@0 872 });
michael@0 873 }
michael@0 874
michael@0 875 private JSONObject bundleToJSON(Bundle bundle) {
michael@0 876 JSONObject json = new JSONObject();
michael@0 877 if (bundle == null) {
michael@0 878 return json;
michael@0 879 }
michael@0 880
michael@0 881 for (String key : bundle.keySet()) {
michael@0 882 try {
michael@0 883 json.put(key, bundle.get(key));
michael@0 884 } catch (JSONException e) {
michael@0 885 Log.w(LOGTAG, "Error building JSON response.", e);
michael@0 886 }
michael@0 887 }
michael@0 888
michael@0 889 return json;
michael@0 890 }
michael@0 891
michael@0 892 private void addFullScreenPluginView(View view) {
michael@0 893 if (mFullScreenPluginView != null) {
michael@0 894 Log.w(LOGTAG, "Already have a fullscreen plugin view");
michael@0 895 return;
michael@0 896 }
michael@0 897
michael@0 898 setFullScreen(true);
michael@0 899
michael@0 900 view.setWillNotDraw(false);
michael@0 901 if (view instanceof SurfaceView) {
michael@0 902 ((SurfaceView) view).setZOrderOnTop(true);
michael@0 903 }
michael@0 904
michael@0 905 mFullScreenPluginContainer = new FullScreenHolder(this);
michael@0 906
michael@0 907 FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
michael@0 908 ViewGroup.LayoutParams.FILL_PARENT,
michael@0 909 ViewGroup.LayoutParams.FILL_PARENT,
michael@0 910 Gravity.CENTER);
michael@0 911 mFullScreenPluginContainer.addView(view, layoutParams);
michael@0 912
michael@0 913
michael@0 914 FrameLayout decor = (FrameLayout)getWindow().getDecorView();
michael@0 915 decor.addView(mFullScreenPluginContainer, layoutParams);
michael@0 916
michael@0 917 mFullScreenPluginView = view;
michael@0 918 }
michael@0 919
michael@0 920 public void addPluginView(final View view, final RectF rect, final boolean isFullScreen) {
michael@0 921 ThreadUtils.postToUiThread(new Runnable() {
michael@0 922 @Override
michael@0 923 public void run() {
michael@0 924 Tabs tabs = Tabs.getInstance();
michael@0 925 Tab tab = tabs.getSelectedTab();
michael@0 926
michael@0 927 if (isFullScreen) {
michael@0 928 addFullScreenPluginView(view);
michael@0 929 return;
michael@0 930 }
michael@0 931
michael@0 932 PluginLayer layer = (PluginLayer) tab.getPluginLayer(view);
michael@0 933 if (layer == null) {
michael@0 934 layer = new PluginLayer(view, rect, mLayerView.getRenderer().getMaxTextureSize());
michael@0 935 tab.addPluginLayer(view, layer);
michael@0 936 } else {
michael@0 937 layer.reset(rect);
michael@0 938 layer.setVisible(true);
michael@0 939 }
michael@0 940
michael@0 941 mLayerView.addLayer(layer);
michael@0 942 }
michael@0 943 });
michael@0 944 }
michael@0 945
michael@0 946 private void removeFullScreenPluginView(View view) {
michael@0 947 if (mFullScreenPluginView == null) {
michael@0 948 Log.w(LOGTAG, "Don't have a fullscreen plugin view");
michael@0 949 return;
michael@0 950 }
michael@0 951
michael@0 952 if (mFullScreenPluginView != view) {
michael@0 953 Log.w(LOGTAG, "Passed view is not the current full screen view");
michael@0 954 return;
michael@0 955 }
michael@0 956
michael@0 957 mFullScreenPluginContainer.removeView(mFullScreenPluginView);
michael@0 958
michael@0 959 // We need do do this on the next iteration in order to avoid
michael@0 960 // a deadlock, see comment below in FullScreenHolder
michael@0 961 ThreadUtils.postToUiThread(new Runnable() {
michael@0 962 @Override
michael@0 963 public void run() {
michael@0 964 mLayerView.showSurface();
michael@0 965 }
michael@0 966 });
michael@0 967
michael@0 968 FrameLayout decor = (FrameLayout)getWindow().getDecorView();
michael@0 969 decor.removeView(mFullScreenPluginContainer);
michael@0 970
michael@0 971 mFullScreenPluginView = null;
michael@0 972
michael@0 973 GeckoScreenOrientation.getInstance().unlock();
michael@0 974 setFullScreen(false);
michael@0 975 }
michael@0 976
michael@0 977 public void removePluginView(final View view, final boolean isFullScreen) {
michael@0 978 ThreadUtils.postToUiThread(new Runnable() {
michael@0 979 @Override
michael@0 980 public void run() {
michael@0 981 Tabs tabs = Tabs.getInstance();
michael@0 982 Tab tab = tabs.getSelectedTab();
michael@0 983
michael@0 984 if (isFullScreen) {
michael@0 985 removeFullScreenPluginView(view);
michael@0 986 return;
michael@0 987 }
michael@0 988
michael@0 989 PluginLayer layer = (PluginLayer) tab.removePluginLayer(view);
michael@0 990 if (layer != null) {
michael@0 991 layer.destroy();
michael@0 992 }
michael@0 993 }
michael@0 994 });
michael@0 995 }
michael@0 996
michael@0 997 // This method starts downloading an image synchronously and displays the Chooser activity to set the image as wallpaper.
michael@0 998 private void setImageAs(final String aSrc) {
michael@0 999 boolean isDataURI = aSrc.startsWith("data:");
michael@0 1000 Bitmap image = null;
michael@0 1001 InputStream is = null;
michael@0 1002 ByteArrayOutputStream os = null;
michael@0 1003 try {
michael@0 1004 if (isDataURI) {
michael@0 1005 int dataStart = aSrc.indexOf(",");
michael@0 1006 byte[] buf = Base64.decode(aSrc.substring(dataStart+1), Base64.DEFAULT);
michael@0 1007 image = BitmapUtils.decodeByteArray(buf);
michael@0 1008 } else {
michael@0 1009 int byteRead;
michael@0 1010 byte[] buf = new byte[4192];
michael@0 1011 os = new ByteArrayOutputStream();
michael@0 1012 URL url = new URL(aSrc);
michael@0 1013 is = url.openStream();
michael@0 1014
michael@0 1015 // Cannot read from same stream twice. Also, InputStream from
michael@0 1016 // URL does not support reset. So converting to byte array.
michael@0 1017
michael@0 1018 while((byteRead = is.read(buf)) != -1) {
michael@0 1019 os.write(buf, 0, byteRead);
michael@0 1020 }
michael@0 1021 byte[] imgBuffer = os.toByteArray();
michael@0 1022 image = BitmapUtils.decodeByteArray(imgBuffer);
michael@0 1023 }
michael@0 1024 if (image != null) {
michael@0 1025 String path = Media.insertImage(getContentResolver(),image, null, null);
michael@0 1026 final Intent intent = new Intent(Intent.ACTION_ATTACH_DATA);
michael@0 1027 intent.addCategory(Intent.CATEGORY_DEFAULT);
michael@0 1028 intent.setData(Uri.parse(path));
michael@0 1029
michael@0 1030 // Removes the image from storage once the chooser activity ends.
michael@0 1031 Intent chooser = Intent.createChooser(intent, getString(R.string.set_image_chooser_title));
michael@0 1032 ActivityResultHandler handler = new ActivityResultHandler() {
michael@0 1033 @Override
michael@0 1034 public void onActivityResult (int resultCode, Intent data) {
michael@0 1035 getContentResolver().delete(intent.getData(), null, null);
michael@0 1036 }
michael@0 1037 };
michael@0 1038 ActivityHandlerHelper.startIntentForActivity(this, chooser, handler);
michael@0 1039 } else {
michael@0 1040 Toast.makeText((Context) this, R.string.set_image_fail, Toast.LENGTH_SHORT).show();
michael@0 1041 }
michael@0 1042 } catch(OutOfMemoryError ome) {
michael@0 1043 Log.e(LOGTAG, "Out of Memory when converting to byte array", ome);
michael@0 1044 } catch(IOException ioe) {
michael@0 1045 Log.e(LOGTAG, "I/O Exception while setting wallpaper", ioe);
michael@0 1046 } finally {
michael@0 1047 if (is != null) {
michael@0 1048 try {
michael@0 1049 is.close();
michael@0 1050 } catch(IOException ioe) {
michael@0 1051 Log.w(LOGTAG, "I/O Exception while closing stream", ioe);
michael@0 1052 }
michael@0 1053 }
michael@0 1054 if (os != null) {
michael@0 1055 try {
michael@0 1056 os.close();
michael@0 1057 } catch(IOException ioe) {
michael@0 1058 Log.w(LOGTAG, "I/O Exception while closing stream", ioe);
michael@0 1059 }
michael@0 1060 }
michael@0 1061 }
michael@0 1062 }
michael@0 1063
michael@0 1064 private int getBitmapSampleSize(BitmapFactory.Options options, int idealWidth, int idealHeight) {
michael@0 1065 int width = options.outWidth;
michael@0 1066 int height = options.outHeight;
michael@0 1067 int inSampleSize = 1;
michael@0 1068 if (height > idealHeight || width > idealWidth) {
michael@0 1069 if (width > height) {
michael@0 1070 inSampleSize = Math.round((float)height / (float)idealHeight);
michael@0 1071 } else {
michael@0 1072 inSampleSize = Math.round((float)width / (float)idealWidth);
michael@0 1073 }
michael@0 1074 }
michael@0 1075 return inSampleSize;
michael@0 1076 }
michael@0 1077
michael@0 1078 private void hidePluginLayer(Layer layer) {
michael@0 1079 LayerView layerView = mLayerView;
michael@0 1080 layerView.removeLayer(layer);
michael@0 1081 layerView.requestRender();
michael@0 1082 }
michael@0 1083
michael@0 1084 private void showPluginLayer(Layer layer) {
michael@0 1085 LayerView layerView = mLayerView;
michael@0 1086 layerView.addLayer(layer);
michael@0 1087 layerView.requestRender();
michael@0 1088 }
michael@0 1089
michael@0 1090 public void requestRender() {
michael@0 1091 mLayerView.requestRender();
michael@0 1092 }
michael@0 1093
michael@0 1094 public void hidePlugins(Tab tab) {
michael@0 1095 for (Layer layer : tab.getPluginLayers()) {
michael@0 1096 if (layer instanceof PluginLayer) {
michael@0 1097 ((PluginLayer) layer).setVisible(false);
michael@0 1098 }
michael@0 1099
michael@0 1100 hidePluginLayer(layer);
michael@0 1101 }
michael@0 1102
michael@0 1103 requestRender();
michael@0 1104 }
michael@0 1105
michael@0 1106 public void showPlugins() {
michael@0 1107 Tabs tabs = Tabs.getInstance();
michael@0 1108 Tab tab = tabs.getSelectedTab();
michael@0 1109
michael@0 1110 showPlugins(tab);
michael@0 1111 }
michael@0 1112
michael@0 1113 public void showPlugins(Tab tab) {
michael@0 1114 for (Layer layer : tab.getPluginLayers()) {
michael@0 1115 showPluginLayer(layer);
michael@0 1116
michael@0 1117 if (layer instanceof PluginLayer) {
michael@0 1118 ((PluginLayer) layer).setVisible(true);
michael@0 1119 }
michael@0 1120 }
michael@0 1121
michael@0 1122 requestRender();
michael@0 1123 }
michael@0 1124
michael@0 1125 public void setFullScreen(final boolean fullscreen) {
michael@0 1126 ThreadUtils.postToUiThread(new Runnable() {
michael@0 1127 @Override
michael@0 1128 public void run() {
michael@0 1129 // Hide/show the system notification bar
michael@0 1130 Window window = getWindow();
michael@0 1131 window.setFlags(fullscreen ?
michael@0 1132 WindowManager.LayoutParams.FLAG_FULLSCREEN : 0,
michael@0 1133 WindowManager.LayoutParams.FLAG_FULLSCREEN);
michael@0 1134
michael@0 1135 if (Build.VERSION.SDK_INT >= 11)
michael@0 1136 window.getDecorView().setSystemUiVisibility(fullscreen ? 1 : 0);
michael@0 1137 }
michael@0 1138 });
michael@0 1139 }
michael@0 1140
michael@0 1141 /**
michael@0 1142 * Check and start the Java profiler if MOZ_PROFILER_STARTUP env var is specified
michael@0 1143 **/
michael@0 1144 protected void earlyStartJavaSampler(Intent intent)
michael@0 1145 {
michael@0 1146 String env = intent.getStringExtra("env0");
michael@0 1147 for (int i = 1; env != null; i++) {
michael@0 1148 if (env.startsWith("MOZ_PROFILER_STARTUP=")) {
michael@0 1149 if (!env.endsWith("=")) {
michael@0 1150 GeckoJavaSampler.start(10, 1000);
michael@0 1151 Log.d(LOGTAG, "Profiling Java on startup");
michael@0 1152 }
michael@0 1153 break;
michael@0 1154 }
michael@0 1155 env = intent.getStringExtra("env" + i);
michael@0 1156 }
michael@0 1157 }
michael@0 1158
michael@0 1159 /**
michael@0 1160 * Called when the activity is first created.
michael@0 1161 *
michael@0 1162 * Here we initialize all of our profile settings, Firefox Health Report,
michael@0 1163 * and other one-shot constructions.
michael@0 1164 **/
michael@0 1165 @Override
michael@0 1166 public void onCreate(Bundle savedInstanceState)
michael@0 1167 {
michael@0 1168 GeckoAppShell.registerGlobalExceptionHandler();
michael@0 1169
michael@0 1170 // Enable Android Strict Mode for developers' local builds (the "default" channel).
michael@0 1171 if ("default".equals(AppConstants.MOZ_UPDATE_CHANNEL)) {
michael@0 1172 enableStrictMode();
michael@0 1173 }
michael@0 1174
michael@0 1175 // The clock starts...now. Better hurry!
michael@0 1176 mJavaUiStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_JAVAUI");
michael@0 1177 mGeckoReadyStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_GECKOREADY");
michael@0 1178
michael@0 1179 final Intent intent = getIntent();
michael@0 1180 final String args = intent.getStringExtra("args");
michael@0 1181
michael@0 1182 earlyStartJavaSampler(intent);
michael@0 1183
michael@0 1184 // GeckoLoader wants to dig some environment variables out of the
michael@0 1185 // incoming intent, so pass it in here. GeckoLoader will do its
michael@0 1186 // business later and dispose of the reference.
michael@0 1187 GeckoLoader.setLastIntent(intent);
michael@0 1188
michael@0 1189 if (mProfile == null) {
michael@0 1190 String profileName = null;
michael@0 1191 String profilePath = null;
michael@0 1192 if (args != null) {
michael@0 1193 if (args.contains("-P")) {
michael@0 1194 Pattern p = Pattern.compile("(?:-P\\s*)(\\w*)(\\s*)");
michael@0 1195 Matcher m = p.matcher(args);
michael@0 1196 if (m.find()) {
michael@0 1197 profileName = m.group(1);
michael@0 1198 }
michael@0 1199 }
michael@0 1200
michael@0 1201 if (args.contains("-profile")) {
michael@0 1202 Pattern p = Pattern.compile("(?:-profile\\s*)(\\S*)(\\s*)");
michael@0 1203 Matcher m = p.matcher(args);
michael@0 1204 if (m.find()) {
michael@0 1205 profilePath = m.group(1);
michael@0 1206 }
michael@0 1207 if (profileName == null) {
michael@0 1208 try {
michael@0 1209 profileName = getDefaultProfileName();
michael@0 1210 } catch (NoMozillaDirectoryException e) {
michael@0 1211 Log.wtf(LOGTAG, "Unable to fetch default profile name!", e);
michael@0 1212 // There's nothing at all we can do now. If the Mozilla directory
michael@0 1213 // didn't exist, then we're screwed.
michael@0 1214 // Crash here so we can fix the bug.
michael@0 1215 throw new RuntimeException(e);
michael@0 1216 }
michael@0 1217 if (profileName == null)
michael@0 1218 profileName = GeckoProfile.DEFAULT_PROFILE;
michael@0 1219 }
michael@0 1220 GeckoProfile.sIsUsingCustomProfile = true;
michael@0 1221 }
michael@0 1222
michael@0 1223 if (profileName != null || profilePath != null) {
michael@0 1224 mProfile = GeckoProfile.get(this, profileName, profilePath);
michael@0 1225 }
michael@0 1226 }
michael@0 1227 }
michael@0 1228
michael@0 1229 BrowserDB.initialize(getProfile().getName());
michael@0 1230
michael@0 1231 // Workaround for <http://code.google.com/p/android/issues/detail?id=20915>.
michael@0 1232 try {
michael@0 1233 Class.forName("android.os.AsyncTask");
michael@0 1234 } catch (ClassNotFoundException e) {}
michael@0 1235
michael@0 1236 MemoryMonitor.getInstance().init(getApplicationContext());
michael@0 1237
michael@0 1238 // GeckoAppShell is tightly coupled to us, rather than
michael@0 1239 // the app context, because various parts of Fennec (e.g.,
michael@0 1240 // GeckoScreenOrientation) use GAS to access the Activity in
michael@0 1241 // the guise of fetching a Context.
michael@0 1242 // When that's fixed, `this` can change to
michael@0 1243 // `(GeckoApplication) getApplication()` here.
michael@0 1244 GeckoAppShell.setContextGetter(this);
michael@0 1245 GeckoAppShell.setGeckoInterface(this);
michael@0 1246
michael@0 1247 ThreadUtils.setUiThread(Thread.currentThread(), new Handler());
michael@0 1248
michael@0 1249 Tabs.getInstance().attachToContext(this);
michael@0 1250 try {
michael@0 1251 Favicons.attachToContext(this);
michael@0 1252 } catch (Exception e) {
michael@0 1253 Log.e(LOGTAG, "Exception starting favicon cache. Corrupt resources?", e);
michael@0 1254 }
michael@0 1255
michael@0 1256 // Did the OS locale change while we were backgrounded? If so,
michael@0 1257 // we need to die so that Gecko will re-init add-ons that touch
michael@0 1258 // the UI.
michael@0 1259 // This is using a sledgehammer to crack a nut, but it'll do for
michael@0 1260 // now.
michael@0 1261 if (BrowserLocaleManager.getInstance().systemLocaleDidChange()) {
michael@0 1262 Log.i(LOGTAG, "System locale changed. Restarting.");
michael@0 1263 doRestart();
michael@0 1264 GeckoAppShell.systemExit();
michael@0 1265 return;
michael@0 1266 }
michael@0 1267
michael@0 1268 if (GeckoThread.isCreated()) {
michael@0 1269 // This happens when the GeckoApp activity is destroyed by Android
michael@0 1270 // without killing the entire application (see Bug 769269).
michael@0 1271 mIsRestoringActivity = true;
michael@0 1272 Telemetry.HistogramAdd("FENNEC_RESTORING_ACTIVITY", 1);
michael@0 1273 }
michael@0 1274
michael@0 1275 // Fix for Bug 830557 on Tegra boards running Froyo.
michael@0 1276 // This fix must be done before doing layout.
michael@0 1277 // Assume the bug is fixed in Gingerbread and up.
michael@0 1278 if (Build.VERSION.SDK_INT < 9) {
michael@0 1279 try {
michael@0 1280 Class<?> inputBindResultClass =
michael@0 1281 Class.forName("com.android.internal.view.InputBindResult");
michael@0 1282 java.lang.reflect.Field creatorField =
michael@0 1283 inputBindResultClass.getField("CREATOR");
michael@0 1284 Log.i(LOGTAG, "froyo startup fix: " + String.valueOf(creatorField.get(null)));
michael@0 1285 } catch (Exception e) {
michael@0 1286 Log.w(LOGTAG, "froyo startup fix failed", e);
michael@0 1287 }
michael@0 1288 }
michael@0 1289
michael@0 1290 Bundle stateBundle = getIntent().getBundleExtra(EXTRA_STATE_BUNDLE);
michael@0 1291 if (stateBundle != null) {
michael@0 1292 // Use the state bundle if it was given as an intent extra. This is
michael@0 1293 // only intended to be used internally via Robocop, so a boolean
michael@0 1294 // is read from a private shared pref to prevent other apps from
michael@0 1295 // injecting states.
michael@0 1296 final SharedPreferences prefs = getSharedPreferences();
michael@0 1297 if (prefs.getBoolean(PREFS_ALLOW_STATE_BUNDLE, false)) {
michael@0 1298 Log.i(LOGTAG, "Restoring state from intent bundle");
michael@0 1299 prefs.edit().remove(PREFS_ALLOW_STATE_BUNDLE).commit();
michael@0 1300 savedInstanceState = stateBundle;
michael@0 1301 }
michael@0 1302 } else if (savedInstanceState != null) {
michael@0 1303 // Bug 896992 - This intent has already been handled; reset the intent.
michael@0 1304 setIntent(new Intent(Intent.ACTION_MAIN));
michael@0 1305 }
michael@0 1306
michael@0 1307 super.onCreate(savedInstanceState);
michael@0 1308
michael@0 1309 GeckoScreenOrientation.getInstance().update(getResources().getConfiguration().orientation);
michael@0 1310
michael@0 1311 setContentView(getLayout());
michael@0 1312
michael@0 1313 // Set up Gecko layout.
michael@0 1314 mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout);
michael@0 1315 mMainLayout = (RelativeLayout) findViewById(R.id.main_layout);
michael@0 1316
michael@0 1317 // Determine whether we should restore tabs.
michael@0 1318 mShouldRestore = getSessionRestoreState(savedInstanceState);
michael@0 1319 if (mShouldRestore && savedInstanceState != null) {
michael@0 1320 boolean wasInBackground =
michael@0 1321 savedInstanceState.getBoolean(SAVED_STATE_IN_BACKGROUND, false);
michael@0 1322
michael@0 1323 // Don't log OOM-kills if only one activity was destroyed. (For example
michael@0 1324 // from "Don't keep activities" on ICS)
michael@0 1325 if (!wasInBackground && !mIsRestoringActivity) {
michael@0 1326 Telemetry.HistogramAdd("FENNEC_WAS_KILLED", 1);
michael@0 1327 }
michael@0 1328
michael@0 1329 mPrivateBrowsingSession = savedInstanceState.getString(SAVED_STATE_PRIVATE_SESSION);
michael@0 1330 }
michael@0 1331
michael@0 1332 // Perform background initialization.
michael@0 1333 ThreadUtils.postToBackgroundThread(new Runnable() {
michael@0 1334 @Override
michael@0 1335 public void run() {
michael@0 1336 final SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
michael@0 1337
michael@0 1338 // Wait until now to set this, because we'd rather throw an exception than
michael@0 1339 // have a caller of BrowserLocaleManager regress startup.
michael@0 1340 BrowserLocaleManager.getInstance().initialize(getApplicationContext());
michael@0 1341
michael@0 1342 SessionInformation previousSession = SessionInformation.fromSharedPrefs(prefs);
michael@0 1343 if (previousSession.wasKilled()) {
michael@0 1344 Telemetry.HistogramAdd("FENNEC_WAS_KILLED", 1);
michael@0 1345 }
michael@0 1346
michael@0 1347 SharedPreferences.Editor editor = prefs.edit();
michael@0 1348 editor.putBoolean(GeckoApp.PREFS_OOM_EXCEPTION, false);
michael@0 1349
michael@0 1350 // Put a flag to check if we got a normal `onSaveInstanceState`
michael@0 1351 // on exit, or if we were suddenly killed (crash or native OOM).
michael@0 1352 editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
michael@0 1353
michael@0 1354 editor.commit();
michael@0 1355
michael@0 1356 // The lifecycle of mHealthRecorder is "shortly after onCreate"
michael@0 1357 // through "onDestroy" -- essentially the same as the lifecycle
michael@0 1358 // of the activity itself.
michael@0 1359 final String profilePath = getProfile().getDir().getAbsolutePath();
michael@0 1360 final EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher();
michael@0 1361 Log.i(LOGTAG, "Creating HealthRecorder.");
michael@0 1362
michael@0 1363 final String osLocale = Locale.getDefault().toString();
michael@0 1364 String appLocale = BrowserLocaleManager.getInstance().getAndApplyPersistedLocale(GeckoApp.this);
michael@0 1365 Log.d(LOGTAG, "OS locale is " + osLocale + ", app locale is " + appLocale);
michael@0 1366
michael@0 1367 if (appLocale == null) {
michael@0 1368 appLocale = osLocale;
michael@0 1369 }
michael@0 1370
michael@0 1371 mHealthRecorder = GeckoApp.this.createHealthRecorder(GeckoApp.this,
michael@0 1372 profilePath,
michael@0 1373 dispatcher,
michael@0 1374 osLocale,
michael@0 1375 appLocale,
michael@0 1376 previousSession);
michael@0 1377
michael@0 1378 final String uiLocale = appLocale;
michael@0 1379 ThreadUtils.postToUiThread(new Runnable() {
michael@0 1380 @Override
michael@0 1381 public void run() {
michael@0 1382 GeckoApp.this.onLocaleReady(uiLocale);
michael@0 1383 }
michael@0 1384 });
michael@0 1385 }
michael@0 1386 });
michael@0 1387
michael@0 1388 GeckoAppShell.setNotificationClient(makeNotificationClient());
michael@0 1389 NotificationHelper.init(getApplicationContext());
michael@0 1390 }
michael@0 1391
michael@0 1392 /**
michael@0 1393 * At this point, the resource system and the rest of the browser are
michael@0 1394 * aware of the locale.
michael@0 1395 *
michael@0 1396 * Now we can display strings!
michael@0 1397 *
michael@0 1398 * You can think of this as being something like a second phase of onCreate,
michael@0 1399 * where you can do string-related operations. Use this in place of embedding
michael@0 1400 * strings in view XML.
michael@0 1401 *
michael@0 1402 * By contrast, onConfigurationChanged does some locale operations, but is in
michael@0 1403 * response to device changes.
michael@0 1404 */
michael@0 1405 @Override
michael@0 1406 public void onLocaleReady(final String locale) {
michael@0 1407 if (!ThreadUtils.isOnUiThread()) {
michael@0 1408 throw new RuntimeException("onLocaleReady must always be called from the UI thread.");
michael@0 1409 }
michael@0 1410
michael@0 1411 // The URL bar hint needs to be populated.
michael@0 1412 TextView urlBar = (TextView) findViewById(R.id.url_bar_title);
michael@0 1413 if (urlBar != null) {
michael@0 1414 final String hint = getResources().getString(R.string.url_bar_default_text);
michael@0 1415 urlBar.setHint(hint);
michael@0 1416 } else {
michael@0 1417 Log.d(LOGTAG, "No URL bar in GeckoApp. Not loading localized hint string.");
michael@0 1418 }
michael@0 1419
michael@0 1420 // Allow onConfigurationChanged to take care of the rest.
michael@0 1421 onConfigurationChanged(getResources().getConfiguration());
michael@0 1422 }
michael@0 1423
michael@0 1424 protected void initializeChrome() {
michael@0 1425 mDoorHangerPopup = new DoorHangerPopup(this);
michael@0 1426 mPluginContainer = (AbsoluteLayout) findViewById(R.id.plugin_container);
michael@0 1427 mFormAssistPopup = (FormAssistPopup) findViewById(R.id.form_assist_popup);
michael@0 1428
michael@0 1429 if (mCameraView == null) {
michael@0 1430 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
michael@0 1431 mCameraView = new SurfaceView(this);
michael@0 1432 ((SurfaceView)mCameraView).getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
michael@0 1433 } else {
michael@0 1434 mCameraView = new TextureView(this);
michael@0 1435 }
michael@0 1436 }
michael@0 1437
michael@0 1438 if (mLayerView == null) {
michael@0 1439 LayerView layerView = (LayerView) findViewById(R.id.layer_view);
michael@0 1440 layerView.initializeView(GeckoAppShell.getEventDispatcher());
michael@0 1441 mLayerView = layerView;
michael@0 1442 GeckoAppShell.setLayerView(layerView);
michael@0 1443 // bind the GeckoEditable instance to the new LayerView
michael@0 1444 GeckoAppShell.notifyIMEContext(GeckoEditableListener.IME_STATE_DISABLED, "", "", "");
michael@0 1445 }
michael@0 1446 }
michael@0 1447
michael@0 1448 /**
michael@0 1449 * Loads the initial tab at Fennec startup.
michael@0 1450 *
michael@0 1451 * If Fennec was opened with an external URL, that URL will be loaded.
michael@0 1452 * Otherwise, unless there was a session restore, the default URL
michael@0 1453 * (about:home) be loaded.
michael@0 1454 *
michael@0 1455 * @param url External URL to load, or null to load the default URL
michael@0 1456 */
michael@0 1457 protected void loadStartupTab(String url) {
michael@0 1458 if (url == null) {
michael@0 1459 if (!mShouldRestore) {
michael@0 1460 // Show about:home if we aren't restoring previous session and
michael@0 1461 // there's no external URL.
michael@0 1462 Tabs.getInstance().loadUrl(AboutPages.HOME, Tabs.LOADURL_NEW_TAB);
michael@0 1463 }
michael@0 1464 } else {
michael@0 1465 // If given an external URL, load it
michael@0 1466 int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_USER_ENTERED | Tabs.LOADURL_EXTERNAL;
michael@0 1467 Tabs.getInstance().loadUrl(url, flags);
michael@0 1468 }
michael@0 1469 }
michael@0 1470
michael@0 1471 private void initialize() {
michael@0 1472 mInitialized = true;
michael@0 1473
michael@0 1474 Intent intent = getIntent();
michael@0 1475 String action = intent.getAction();
michael@0 1476
michael@0 1477 String passedUri = null;
michael@0 1478 final String uri = getURIFromIntent(intent);
michael@0 1479 if (!TextUtils.isEmpty(uri)) {
michael@0 1480 passedUri = uri;
michael@0 1481 }
michael@0 1482
michael@0 1483 final boolean isExternalURL = passedUri != null &&
michael@0 1484 !AboutPages.isAboutHome(passedUri);
michael@0 1485 StartupAction startupAction;
michael@0 1486 if (isExternalURL) {
michael@0 1487 startupAction = StartupAction.URL;
michael@0 1488 } else {
michael@0 1489 startupAction = StartupAction.NORMAL;
michael@0 1490 }
michael@0 1491
michael@0 1492 // Start migrating as early as possible, can do this in
michael@0 1493 // parallel with Gecko load.
michael@0 1494 checkMigrateProfile();
michael@0 1495
michael@0 1496 Uri data = intent.getData();
michael@0 1497 if (data != null && "http".equals(data.getScheme())) {
michael@0 1498 startupAction = StartupAction.PREFETCH;
michael@0 1499 ThreadUtils.postToBackgroundThread(new PrefetchRunnable(data.toString()));
michael@0 1500 }
michael@0 1501
michael@0 1502 Tabs.registerOnTabsChangedListener(this);
michael@0 1503
michael@0 1504 initializeChrome();
michael@0 1505
michael@0 1506 // If we are doing a restore, read the session data and send it to Gecko
michael@0 1507 if (!mIsRestoringActivity) {
michael@0 1508 String restoreMessage = null;
michael@0 1509 if (mShouldRestore) {
michael@0 1510 try {
michael@0 1511 // restoreSessionTabs() will create simple tab stubs with the
michael@0 1512 // URL and title for each page, but we also need to restore
michael@0 1513 // session history. restoreSessionTabs() will inject the IDs
michael@0 1514 // of the tab stubs into the JSON data (which holds the session
michael@0 1515 // history). This JSON data is then sent to Gecko so session
michael@0 1516 // history can be restored for each tab.
michael@0 1517 restoreMessage = restoreSessionTabs(isExternalURL);
michael@0 1518 } catch (SessionRestoreException e) {
michael@0 1519 // If restore failed, do a normal startup
michael@0 1520 Log.e(LOGTAG, "An error occurred during restore", e);
michael@0 1521 mShouldRestore = false;
michael@0 1522 }
michael@0 1523 }
michael@0 1524
michael@0 1525 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Session:Restore", restoreMessage));
michael@0 1526 }
michael@0 1527
michael@0 1528 // External URLs should always be loaded regardless of whether Gecko is
michael@0 1529 // already running.
michael@0 1530 if (isExternalURL) {
michael@0 1531 loadStartupTab(passedUri);
michael@0 1532 } else if (!mIsRestoringActivity) {
michael@0 1533 loadStartupTab(null);
michael@0 1534 }
michael@0 1535
michael@0 1536 // We now have tab stubs from the last session. Any future tabs should
michael@0 1537 // be animated.
michael@0 1538 Tabs.getInstance().notifyListeners(null, Tabs.TabEvents.RESTORED);
michael@0 1539
michael@0 1540 // If we're not restoring, move the session file so it can be read for
michael@0 1541 // the last tabs section.
michael@0 1542 if (!mShouldRestore) {
michael@0 1543 getProfile().moveSessionFile();
michael@0 1544 }
michael@0 1545
michael@0 1546 Telemetry.HistogramAdd("FENNEC_STARTUP_GECKOAPP_ACTION", startupAction.ordinal());
michael@0 1547
michael@0 1548 if (!mIsRestoringActivity) {
michael@0 1549 GeckoThread.setArgs(intent.getStringExtra("args"));
michael@0 1550 GeckoThread.setAction(intent.getAction());
michael@0 1551 GeckoThread.setUri(passedUri);
michael@0 1552 }
michael@0 1553 if (!ACTION_DEBUG.equals(action) &&
michael@0 1554 GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching, GeckoThread.LaunchState.Launched)) {
michael@0 1555 GeckoThread.createAndStart();
michael@0 1556 } else if (ACTION_DEBUG.equals(action) &&
michael@0 1557 GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching, GeckoThread.LaunchState.WaitForDebugger)) {
michael@0 1558 ThreadUtils.getUiHandler().postDelayed(new Runnable() {
michael@0 1559 @Override
michael@0 1560 public void run() {
michael@0 1561 GeckoThread.setLaunchState(GeckoThread.LaunchState.Launching);
michael@0 1562 GeckoThread.createAndStart();
michael@0 1563 }
michael@0 1564 }, 1000 * 5 /* 5 seconds */);
michael@0 1565 }
michael@0 1566
michael@0 1567 // Check if launched from data reporting notification.
michael@0 1568 if (ACTION_LAUNCH_SETTINGS.equals(action)) {
michael@0 1569 Intent settingsIntent = new Intent(GeckoApp.this, GeckoPreferences.class);
michael@0 1570 // Copy extras.
michael@0 1571 settingsIntent.putExtras(intent);
michael@0 1572 startActivity(settingsIntent);
michael@0 1573 }
michael@0 1574
michael@0 1575 //app state callbacks
michael@0 1576 mAppStateListeners = new LinkedList<GeckoAppShell.AppStateListener>();
michael@0 1577
michael@0 1578 //register for events
michael@0 1579 registerEventListener("log");
michael@0 1580 registerEventListener("Reader:ListStatusRequest");
michael@0 1581 registerEventListener("Reader:Added");
michael@0 1582 registerEventListener("Reader:Removed");
michael@0 1583 registerEventListener("Reader:Share");
michael@0 1584 registerEventListener("Reader:FaviconRequest");
michael@0 1585 registerEventListener("onCameraCapture");
michael@0 1586 registerEventListener("Gecko:Ready");
michael@0 1587 registerEventListener("Gecko:DelayedStartup");
michael@0 1588 registerEventListener("Toast:Show");
michael@0 1589 registerEventListener("DOMFullScreen:Start");
michael@0 1590 registerEventListener("DOMFullScreen:Stop");
michael@0 1591 registerEventListener("ToggleChrome:Hide");
michael@0 1592 registerEventListener("ToggleChrome:Show");
michael@0 1593 registerEventListener("ToggleChrome:Focus");
michael@0 1594 registerEventListener("Permissions:Data");
michael@0 1595 registerEventListener("Session:StatePurged");
michael@0 1596 registerEventListener("Bookmark:Insert");
michael@0 1597 registerEventListener("Accessibility:Event");
michael@0 1598 registerEventListener("Accessibility:Ready");
michael@0 1599 registerEventListener("Shortcut:Remove");
michael@0 1600 registerEventListener("Share:Text");
michael@0 1601 registerEventListener("Image:SetAs");
michael@0 1602 registerEventListener("Sanitize:ClearHistory");
michael@0 1603 registerEventListener("Update:Check");
michael@0 1604 registerEventListener("Update:Download");
michael@0 1605 registerEventListener("Update:Install");
michael@0 1606 registerEventListener("PrivateBrowsing:Data");
michael@0 1607 registerEventListener("Contact:Add");
michael@0 1608 registerEventListener("Intent:Open");
michael@0 1609 registerEventListener("Intent:OpenForResult");
michael@0 1610 registerEventListener("Intent:GetHandlers");
michael@0 1611 registerEventListener("Locale:Set");
michael@0 1612 registerEventListener("NativeApp:IsDebuggable");
michael@0 1613 registerEventListener("SystemUI:Visibility");
michael@0 1614
michael@0 1615 if (mWebappEventListener == null) {
michael@0 1616 mWebappEventListener = new EventListener();
michael@0 1617 mWebappEventListener.registerEvents();
michael@0 1618 }
michael@0 1619
michael@0 1620 if (SmsManager.getInstance() != null) {
michael@0 1621 SmsManager.getInstance().start();
michael@0 1622 }
michael@0 1623
michael@0 1624 mContactService = new ContactService(GeckoAppShell.getEventDispatcher(), this);
michael@0 1625
michael@0 1626 mPromptService = new PromptService(this);
michael@0 1627
michael@0 1628 mTextSelection = new TextSelection((TextSelectionHandle) findViewById(R.id.start_handle),
michael@0 1629 (TextSelectionHandle) findViewById(R.id.middle_handle),
michael@0 1630 (TextSelectionHandle) findViewById(R.id.end_handle),
michael@0 1631 GeckoAppShell.getEventDispatcher(),
michael@0 1632 this);
michael@0 1633
michael@0 1634 PrefsHelper.getPref("app.update.autodownload", new PrefsHelper.PrefHandlerBase() {
michael@0 1635 @Override public void prefValue(String pref, String value) {
michael@0 1636 UpdateServiceHelper.registerForUpdates(GeckoApp.this, value);
michael@0 1637 }
michael@0 1638 });
michael@0 1639
michael@0 1640 PrefsHelper.getPref("app.geo.reportdata", new PrefsHelper.PrefHandlerBase() {
michael@0 1641 @Override public void prefValue(String pref, int value) {
michael@0 1642 if (value == 1)
michael@0 1643 mShouldReportGeoData = true;
michael@0 1644 else
michael@0 1645 mShouldReportGeoData = false;
michael@0 1646 }
michael@0 1647 });
michael@0 1648
michael@0 1649 // Trigger the completion of the telemetry timer that wraps activity startup,
michael@0 1650 // then grab the duration to give to FHR.
michael@0 1651 mJavaUiStartupTimer.stop();
michael@0 1652 final long javaDuration = mJavaUiStartupTimer.getElapsed();
michael@0 1653
michael@0 1654 ThreadUtils.getBackgroundHandler().postDelayed(new Runnable() {
michael@0 1655 @Override
michael@0 1656 public void run() {
michael@0 1657 final HealthRecorder rec = mHealthRecorder;
michael@0 1658 if (rec != null) {
michael@0 1659 rec.recordJavaStartupTime(javaDuration);
michael@0 1660 }
michael@0 1661
michael@0 1662 // Record our launch time for the announcements service
michael@0 1663 // to use in assessing inactivity.
michael@0 1664 final Context context = GeckoApp.this;
michael@0 1665 AnnouncementsBroadcastService.recordLastLaunch(context);
michael@0 1666
michael@0 1667 // Kick off our background services. We do this by invoking the broadcast
michael@0 1668 // receiver, which uses the system alarm infrastructure to perform tasks at
michael@0 1669 // intervals.
michael@0 1670 GeckoPreferences.broadcastAnnouncementsPref(context);
michael@0 1671 GeckoPreferences.broadcastHealthReportUploadPref(context);
michael@0 1672 if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.Launched)) {
michael@0 1673 return;
michael@0 1674 }
michael@0 1675 }
michael@0 1676 }, 50);
michael@0 1677
michael@0 1678 if (mIsRestoringActivity) {
michael@0 1679 GeckoThread.setLaunchState(GeckoThread.LaunchState.GeckoRunning);
michael@0 1680 Tab selectedTab = Tabs.getInstance().getSelectedTab();
michael@0 1681 if (selectedTab != null)
michael@0 1682 Tabs.getInstance().notifyListeners(selectedTab, Tabs.TabEvents.SELECTED);
michael@0 1683 geckoConnected();
michael@0 1684 GeckoAppShell.setLayerClient(mLayerView.getLayerClientObject());
michael@0 1685 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Viewport:Flush", null));
michael@0 1686 }
michael@0 1687
michael@0 1688 if (ACTION_ALERT_CALLBACK.equals(action)) {
michael@0 1689 processAlertCallback(intent);
michael@0 1690 }
michael@0 1691 }
michael@0 1692
michael@0 1693 private String restoreSessionTabs(final boolean isExternalURL) throws SessionRestoreException {
michael@0 1694 try {
michael@0 1695 String sessionString = getProfile().readSessionFile(false);
michael@0 1696 if (sessionString == null) {
michael@0 1697 throw new SessionRestoreException("Could not read from session file");
michael@0 1698 }
michael@0 1699
michael@0 1700 // If we are doing an OOM restore, parse the session data and
michael@0 1701 // stub the restored tabs immediately. This allows the UI to be
michael@0 1702 // updated before Gecko has restored.
michael@0 1703 if (mShouldRestore) {
michael@0 1704 final JSONArray tabs = new JSONArray();
michael@0 1705 SessionParser parser = new SessionParser() {
michael@0 1706 @Override
michael@0 1707 public void onTabRead(SessionTab sessionTab) {
michael@0 1708 JSONObject tabObject = sessionTab.getTabObject();
michael@0 1709
michael@0 1710 int flags = Tabs.LOADURL_NEW_TAB;
michael@0 1711 flags |= ((isExternalURL || !sessionTab.isSelected()) ? Tabs.LOADURL_DELAY_LOAD : 0);
michael@0 1712 flags |= (tabObject.optBoolean("desktopMode") ? Tabs.LOADURL_DESKTOP : 0);
michael@0 1713 flags |= (tabObject.optBoolean("isPrivate") ? Tabs.LOADURL_PRIVATE : 0);
michael@0 1714
michael@0 1715 Tab tab = Tabs.getInstance().loadUrl(sessionTab.getUrl(), flags);
michael@0 1716 tab.updateTitle(sessionTab.getTitle());
michael@0 1717
michael@0 1718 try {
michael@0 1719 tabObject.put("tabId", tab.getId());
michael@0 1720 } catch (JSONException e) {
michael@0 1721 Log.e(LOGTAG, "JSON error", e);
michael@0 1722 }
michael@0 1723 tabs.put(tabObject);
michael@0 1724 }
michael@0 1725 };
michael@0 1726
michael@0 1727 if (mPrivateBrowsingSession == null) {
michael@0 1728 parser.parse(sessionString);
michael@0 1729 } else {
michael@0 1730 parser.parse(sessionString, mPrivateBrowsingSession);
michael@0 1731 }
michael@0 1732
michael@0 1733 if (tabs.length() > 0) {
michael@0 1734 sessionString = new JSONObject().put("windows", new JSONArray().put(new JSONObject().put("tabs", tabs))).toString();
michael@0 1735 } else {
michael@0 1736 throw new SessionRestoreException("No tabs could be read from session file");
michael@0 1737 }
michael@0 1738 }
michael@0 1739
michael@0 1740 JSONObject restoreData = new JSONObject();
michael@0 1741 restoreData.put("sessionString", sessionString);
michael@0 1742 return restoreData.toString();
michael@0 1743
michael@0 1744 } catch (JSONException e) {
michael@0 1745 throw new SessionRestoreException(e);
michael@0 1746 }
michael@0 1747 }
michael@0 1748
michael@0 1749 public GeckoProfile getProfile() {
michael@0 1750 // fall back to default profile if we didn't load a specific one
michael@0 1751 if (mProfile == null) {
michael@0 1752 mProfile = GeckoProfile.get(this);
michael@0 1753 }
michael@0 1754 return mProfile;
michael@0 1755 }
michael@0 1756
michael@0 1757 /**
michael@0 1758 * Determine whether the session should be restored.
michael@0 1759 *
michael@0 1760 * @param savedInstanceState Saved instance state given to the activity
michael@0 1761 * @return Whether to restore
michael@0 1762 */
michael@0 1763 protected boolean getSessionRestoreState(Bundle savedInstanceState) {
michael@0 1764 final SharedPreferences prefs = getSharedPreferences();
michael@0 1765 boolean shouldRestore = false;
michael@0 1766
michael@0 1767 final int versionCode = getVersionCode();
michael@0 1768 if (prefs.getInt(PREFS_VERSION_CODE, 0) != versionCode) {
michael@0 1769 // If the version has changed, the user has done an upgrade, so restore
michael@0 1770 // previous tabs.
michael@0 1771 ThreadUtils.postToBackgroundThread(new Runnable() {
michael@0 1772 @Override
michael@0 1773 public void run() {
michael@0 1774 prefs.edit()
michael@0 1775 .putInt(PREFS_VERSION_CODE, versionCode)
michael@0 1776 .commit();
michael@0 1777 }
michael@0 1778 });
michael@0 1779
michael@0 1780 shouldRestore = true;
michael@0 1781 } else if (savedInstanceState != null ||
michael@0 1782 getSessionRestorePreference().equals("always") ||
michael@0 1783 getRestartFromIntent()) {
michael@0 1784 // We're coming back from a background kill by the OS, the user
michael@0 1785 // has chosen to always restore, or we restarted.
michael@0 1786 shouldRestore = true;
michael@0 1787 } else if (prefs.getBoolean(GeckoApp.PREFS_CRASHED, false)) {
michael@0 1788 ThreadUtils.postToBackgroundThread(new Runnable() {
michael@0 1789 @Override
michael@0 1790 public void run() {
michael@0 1791 prefs.edit().putBoolean(PREFS_CRASHED, false).commit();
michael@0 1792 }
michael@0 1793 });
michael@0 1794 shouldRestore = true;
michael@0 1795 }
michael@0 1796
michael@0 1797 return shouldRestore;
michael@0 1798 }
michael@0 1799
michael@0 1800 private String getSessionRestorePreference() {
michael@0 1801 return getSharedPreferences().getString(GeckoPreferences.PREFS_RESTORE_SESSION, "quit");
michael@0 1802 }
michael@0 1803
michael@0 1804 private boolean getRestartFromIntent() {
michael@0 1805 return getIntent().getBooleanExtra("didRestart", false);
michael@0 1806 }
michael@0 1807
michael@0 1808 /**
michael@0 1809 * Enable Android StrictMode checks (for supported OS versions).
michael@0 1810 * http://developer.android.com/reference/android/os/StrictMode.html
michael@0 1811 */
michael@0 1812 private void enableStrictMode() {
michael@0 1813 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) {
michael@0 1814 return;
michael@0 1815 }
michael@0 1816
michael@0 1817 Log.d(LOGTAG, "Enabling Android StrictMode");
michael@0 1818
michael@0 1819 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
michael@0 1820 .detectAll()
michael@0 1821 .penaltyLog()
michael@0 1822 .build());
michael@0 1823
michael@0 1824 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
michael@0 1825 .detectAll()
michael@0 1826 .penaltyLog()
michael@0 1827 .build());
michael@0 1828 }
michael@0 1829
michael@0 1830 public void enableCameraView() {
michael@0 1831 // Start listening for orientation events
michael@0 1832 mCameraOrientationEventListener = new OrientationEventListener(this) {
michael@0 1833 @Override
michael@0 1834 public void onOrientationChanged(int orientation) {
michael@0 1835 if (mAppStateListeners != null) {
michael@0 1836 for (GeckoAppShell.AppStateListener listener: mAppStateListeners) {
michael@0 1837 listener.onOrientationChanged();
michael@0 1838 }
michael@0 1839 }
michael@0 1840 }
michael@0 1841 };
michael@0 1842 mCameraOrientationEventListener.enable();
michael@0 1843
michael@0 1844 // Try to make it fully transparent.
michael@0 1845 if (mCameraView instanceof SurfaceView) {
michael@0 1846 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
michael@0 1847 mCameraView.setAlpha(0.0f);
michael@0 1848 }
michael@0 1849 } else if (mCameraView instanceof TextureView) {
michael@0 1850 mCameraView.setAlpha(0.0f);
michael@0 1851 }
michael@0 1852 ViewGroup mCameraLayout = (ViewGroup) findViewById(R.id.camera_layout);
michael@0 1853 // Some phones (eg. nexus S) need at least a 8x16 preview size
michael@0 1854 mCameraLayout.addView(mCameraView,
michael@0 1855 new AbsoluteLayout.LayoutParams(8, 16, 0, 0));
michael@0 1856 }
michael@0 1857
michael@0 1858 public void disableCameraView() {
michael@0 1859 if (mCameraOrientationEventListener != null) {
michael@0 1860 mCameraOrientationEventListener.disable();
michael@0 1861 mCameraOrientationEventListener = null;
michael@0 1862 }
michael@0 1863 ViewGroup mCameraLayout = (ViewGroup) findViewById(R.id.camera_layout);
michael@0 1864 mCameraLayout.removeView(mCameraView);
michael@0 1865 }
michael@0 1866
michael@0 1867 public String getDefaultUAString() {
michael@0 1868 return HardwareUtils.isTablet() ? AppConstants.USER_AGENT_FENNEC_TABLET :
michael@0 1869 AppConstants.USER_AGENT_FENNEC_MOBILE;
michael@0 1870 }
michael@0 1871
michael@0 1872 public String getUAStringForHost(String host) {
michael@0 1873 // With our standard UA String, we get a 200 response code and
michael@0 1874 // client-side redirect from t.co. This bot-like UA gives us a
michael@0 1875 // 301 response code
michael@0 1876 if ("t.co".equals(host)) {
michael@0 1877 return AppConstants.USER_AGENT_BOT_LIKE;
michael@0 1878 }
michael@0 1879 return getDefaultUAString();
michael@0 1880 }
michael@0 1881
michael@0 1882 class PrefetchRunnable implements Runnable {
michael@0 1883 private String mPrefetchUrl;
michael@0 1884
michael@0 1885 PrefetchRunnable(String prefetchUrl) {
michael@0 1886 mPrefetchUrl = prefetchUrl;
michael@0 1887 }
michael@0 1888
michael@0 1889 @Override
michael@0 1890 public void run() {
michael@0 1891 HttpURLConnection connection = null;
michael@0 1892 try {
michael@0 1893 URL url = new URL(mPrefetchUrl);
michael@0 1894 // data url should have an http scheme
michael@0 1895 connection = (HttpURLConnection) url.openConnection();
michael@0 1896 connection.setRequestProperty("User-Agent", getUAStringForHost(url.getHost()));
michael@0 1897 connection.setInstanceFollowRedirects(false);
michael@0 1898 connection.setRequestMethod("GET");
michael@0 1899 connection.connect();
michael@0 1900 } catch (Exception e) {
michael@0 1901 Log.e(LOGTAG, "Exception prefetching URL", e);
michael@0 1902 } finally {
michael@0 1903 if (connection != null)
michael@0 1904 connection.disconnect();
michael@0 1905 }
michael@0 1906 }
michael@0 1907 }
michael@0 1908
michael@0 1909 private void processAlertCallback(Intent intent) {
michael@0 1910 String alertName = "";
michael@0 1911 String alertCookie = "";
michael@0 1912 Uri data = intent.getData();
michael@0 1913 if (data != null) {
michael@0 1914 alertName = data.getQueryParameter("name");
michael@0 1915 if (alertName == null)
michael@0 1916 alertName = "";
michael@0 1917 alertCookie = data.getQueryParameter("cookie");
michael@0 1918 if (alertCookie == null)
michael@0 1919 alertCookie = "";
michael@0 1920 }
michael@0 1921 handleNotification(ACTION_ALERT_CALLBACK, alertName, alertCookie);
michael@0 1922 }
michael@0 1923
michael@0 1924 @Override
michael@0 1925 protected void onNewIntent(Intent intent) {
michael@0 1926 if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoExiting)) {
michael@0 1927 // We're exiting and shouldn't try to do anything else. In the case
michael@0 1928 // where we are hung while exiting, we should force the process to exit.
michael@0 1929 GeckoAppShell.systemExit();
michael@0 1930 return;
michael@0 1931 }
michael@0 1932
michael@0 1933 // if we were previously OOM killed, we can end up here when launching
michael@0 1934 // from external shortcuts, so set this as the intent for initialization
michael@0 1935 if (!mInitialized) {
michael@0 1936 setIntent(intent);
michael@0 1937 return;
michael@0 1938 }
michael@0 1939
michael@0 1940 final String action = intent.getAction();
michael@0 1941
michael@0 1942 if (ACTION_LOAD.equals(action)) {
michael@0 1943 String uri = intent.getDataString();
michael@0 1944 Tabs.getInstance().loadUrl(uri);
michael@0 1945 } else if (Intent.ACTION_VIEW.equals(action)) {
michael@0 1946 String uri = intent.getDataString();
michael@0 1947 Tabs.getInstance().loadUrl(uri, Tabs.LOADURL_NEW_TAB |
michael@0 1948 Tabs.LOADURL_USER_ENTERED |
michael@0 1949 Tabs.LOADURL_EXTERNAL);
michael@0 1950 } else if (action != null && action.startsWith(ACTION_WEBAPP_PREFIX)) {
michael@0 1951 // A lightweight mechanism for loading a web page as a webapp
michael@0 1952 // without installing the app natively nor registering it in the DOM
michael@0 1953 // application registry.
michael@0 1954 String uri = getURIFromIntent(intent);
michael@0 1955 GeckoAppShell.sendEventToGecko(GeckoEvent.createWebappLoadEvent(uri));
michael@0 1956 } else if (ACTION_BOOKMARK.equals(action)) {
michael@0 1957 String uri = getURIFromIntent(intent);
michael@0 1958 GeckoAppShell.sendEventToGecko(GeckoEvent.createBookmarkLoadEvent(uri));
michael@0 1959 } else if (Intent.ACTION_SEARCH.equals(action)) {
michael@0 1960 String uri = getURIFromIntent(intent);
michael@0 1961 GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(uri));
michael@0 1962 } else if (ACTION_ALERT_CALLBACK.equals(action)) {
michael@0 1963 processAlertCallback(intent);
michael@0 1964 } else if (ACTION_LAUNCH_SETTINGS.equals(action)) {
michael@0 1965 // Check if launched from data reporting notification.
michael@0 1966 Intent settingsIntent = new Intent(GeckoApp.this, GeckoPreferences.class);
michael@0 1967 // Copy extras.
michael@0 1968 settingsIntent.putExtras(intent);
michael@0 1969 startActivity(settingsIntent);
michael@0 1970 }
michael@0 1971 }
michael@0 1972
michael@0 1973 /*
michael@0 1974 * Handles getting a uri from and intent in a way that is backwards
michael@0 1975 * compatable with our previous implementations
michael@0 1976 */
michael@0 1977 protected String getURIFromIntent(Intent intent) {
michael@0 1978 final String action = intent.getAction();
michael@0 1979 if (ACTION_ALERT_CALLBACK.equals(action))
michael@0 1980 return null;
michael@0 1981
michael@0 1982 String uri = intent.getDataString();
michael@0 1983 if (uri != null)
michael@0 1984 return uri;
michael@0 1985
michael@0 1986 if ((action != null && action.startsWith(ACTION_WEBAPP_PREFIX)) || ACTION_BOOKMARK.equals(action)) {
michael@0 1987 uri = intent.getStringExtra("args");
michael@0 1988 if (uri != null && uri.startsWith("--url=")) {
michael@0 1989 uri.replace("--url=", "");
michael@0 1990 }
michael@0 1991 }
michael@0 1992 return uri;
michael@0 1993 }
michael@0 1994
michael@0 1995 protected int getOrientation() {
michael@0 1996 return GeckoScreenOrientation.getInstance().getAndroidOrientation();
michael@0 1997 }
michael@0 1998
michael@0 1999 @Override
michael@0 2000 public void onResume()
michael@0 2001 {
michael@0 2002 // After an onPause, the activity is back in the foreground.
michael@0 2003 // Undo whatever we did in onPause.
michael@0 2004 super.onResume();
michael@0 2005
michael@0 2006 int newOrientation = getResources().getConfiguration().orientation;
michael@0 2007 if (GeckoScreenOrientation.getInstance().update(newOrientation)) {
michael@0 2008 refreshChrome();
michael@0 2009 }
michael@0 2010
michael@0 2011 // User may have enabled/disabled accessibility.
michael@0 2012 GeckoAccessibility.updateAccessibilitySettings(this);
michael@0 2013
michael@0 2014 if (mAppStateListeners != null) {
michael@0 2015 for (GeckoAppShell.AppStateListener listener: mAppStateListeners) {
michael@0 2016 listener.onResume();
michael@0 2017 }
michael@0 2018 }
michael@0 2019
michael@0 2020 // We use two times: a pseudo-unique wall-clock time to identify the
michael@0 2021 // current session across power cycles, and the elapsed realtime to
michael@0 2022 // track the duration of the session.
michael@0 2023 final long now = System.currentTimeMillis();
michael@0 2024 final long realTime = android.os.SystemClock.elapsedRealtime();
michael@0 2025
michael@0 2026 ThreadUtils.postToBackgroundThread(new Runnable() {
michael@0 2027 @Override
michael@0 2028 public void run() {
michael@0 2029 // Now construct the new session on HealthRecorder's behalf. We do this here
michael@0 2030 // so it can benefit from a single near-startup prefs commit.
michael@0 2031 SessionInformation currentSession = new SessionInformation(now, realTime);
michael@0 2032
michael@0 2033 SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
michael@0 2034 SharedPreferences.Editor editor = prefs.edit();
michael@0 2035 editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
michael@0 2036 currentSession.recordBegin(editor);
michael@0 2037 editor.commit();
michael@0 2038
michael@0 2039 final HealthRecorder rec = mHealthRecorder;
michael@0 2040 if (rec != null) {
michael@0 2041 rec.setCurrentSession(currentSession);
michael@0 2042 } else {
michael@0 2043 Log.w(LOGTAG, "Can't record session: rec is null.");
michael@0 2044 }
michael@0 2045 }
michael@0 2046 });
michael@0 2047 }
michael@0 2048
michael@0 2049 @Override
michael@0 2050 public void onWindowFocusChanged(boolean hasFocus) {
michael@0 2051 super.onWindowFocusChanged(hasFocus);
michael@0 2052
michael@0 2053 if (!mInitialized && hasFocus) {
michael@0 2054 initialize();
michael@0 2055 getWindow().setBackgroundDrawable(null);
michael@0 2056 }
michael@0 2057 }
michael@0 2058
michael@0 2059 @Override
michael@0 2060 public void onPause()
michael@0 2061 {
michael@0 2062 final HealthRecorder rec = mHealthRecorder;
michael@0 2063 final Context context = this;
michael@0 2064
michael@0 2065 // In some way it's sad that Android will trigger StrictMode warnings
michael@0 2066 // here as the whole point is to save to disk while the activity is not
michael@0 2067 // interacting with the user.
michael@0 2068 ThreadUtils.postToBackgroundThread(new Runnable() {
michael@0 2069 @Override
michael@0 2070 public void run() {
michael@0 2071 SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
michael@0 2072 SharedPreferences.Editor editor = prefs.edit();
michael@0 2073 editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, true);
michael@0 2074 if (rec != null) {
michael@0 2075 rec.recordSessionEnd("P", editor);
michael@0 2076 }
michael@0 2077
michael@0 2078 // If we haven't done it before, cleanup any old files in our old temp dir
michael@0 2079 if (prefs.getBoolean(GeckoApp.PREFS_CLEANUP_TEMP_FILES, true)) {
michael@0 2080 File tempDir = GeckoLoader.getGREDir(GeckoApp.this);
michael@0 2081 FileUtils.delTree(tempDir, new FileUtils.NameAndAgeFilter(null, ONE_DAY_MS), false);
michael@0 2082
michael@0 2083 editor.putBoolean(GeckoApp.PREFS_CLEANUP_TEMP_FILES, false);
michael@0 2084 }
michael@0 2085
michael@0 2086 editor.commit();
michael@0 2087
michael@0 2088 // In theory, the first browser session will not run long enough that we need to
michael@0 2089 // prune during it and we'd rather run it when the browser is inactive so we wait
michael@0 2090 // until here to register the prune service.
michael@0 2091 GeckoPreferences.broadcastHealthReportPrune(context);
michael@0 2092 }
michael@0 2093 });
michael@0 2094
michael@0 2095 if (mAppStateListeners != null) {
michael@0 2096 for(GeckoAppShell.AppStateListener listener: mAppStateListeners) {
michael@0 2097 listener.onPause();
michael@0 2098 }
michael@0 2099 }
michael@0 2100
michael@0 2101 super.onPause();
michael@0 2102 }
michael@0 2103
michael@0 2104 @Override
michael@0 2105 public void onRestart()
michael@0 2106 {
michael@0 2107 ThreadUtils.postToBackgroundThread(new Runnable() {
michael@0 2108 @Override
michael@0 2109 public void run() {
michael@0 2110 SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
michael@0 2111 SharedPreferences.Editor editor = prefs.edit();
michael@0 2112 editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
michael@0 2113 editor.commit();
michael@0 2114 }
michael@0 2115 });
michael@0 2116
michael@0 2117 super.onRestart();
michael@0 2118 }
michael@0 2119
michael@0 2120 @Override
michael@0 2121 public void onDestroy()
michael@0 2122 {
michael@0 2123 unregisterEventListener("log");
michael@0 2124 unregisterEventListener("Reader:ListStatusRequest");
michael@0 2125 unregisterEventListener("Reader:Added");
michael@0 2126 unregisterEventListener("Reader:Removed");
michael@0 2127 unregisterEventListener("Reader:Share");
michael@0 2128 unregisterEventListener("Reader:FaviconRequest");
michael@0 2129 unregisterEventListener("onCameraCapture");
michael@0 2130 unregisterEventListener("Gecko:Ready");
michael@0 2131 unregisterEventListener("Gecko:DelayedStartup");
michael@0 2132 unregisterEventListener("Toast:Show");
michael@0 2133 unregisterEventListener("DOMFullScreen:Start");
michael@0 2134 unregisterEventListener("DOMFullScreen:Stop");
michael@0 2135 unregisterEventListener("ToggleChrome:Hide");
michael@0 2136 unregisterEventListener("ToggleChrome:Show");
michael@0 2137 unregisterEventListener("ToggleChrome:Focus");
michael@0 2138 unregisterEventListener("Permissions:Data");
michael@0 2139 unregisterEventListener("Session:StatePurged");
michael@0 2140 unregisterEventListener("Bookmark:Insert");
michael@0 2141 unregisterEventListener("Accessibility:Event");
michael@0 2142 unregisterEventListener("Accessibility:Ready");
michael@0 2143 unregisterEventListener("Shortcut:Remove");
michael@0 2144 unregisterEventListener("Share:Text");
michael@0 2145 unregisterEventListener("Image:SetAs");
michael@0 2146 unregisterEventListener("Sanitize:ClearHistory");
michael@0 2147 unregisterEventListener("Update:Check");
michael@0 2148 unregisterEventListener("Update:Download");
michael@0 2149 unregisterEventListener("Update:Install");
michael@0 2150 unregisterEventListener("PrivateBrowsing:Data");
michael@0 2151 unregisterEventListener("Contact:Add");
michael@0 2152 unregisterEventListener("Intent:Open");
michael@0 2153 unregisterEventListener("Intent:GetHandlers");
michael@0 2154 unregisterEventListener("Locale:Set");
michael@0 2155 unregisterEventListener("NativeApp:IsDebuggable");
michael@0 2156 unregisterEventListener("SystemUI:Visibility");
michael@0 2157
michael@0 2158 if (mWebappEventListener != null) {
michael@0 2159 mWebappEventListener.unregisterEvents();
michael@0 2160 mWebappEventListener = null;
michael@0 2161 }
michael@0 2162
michael@0 2163 deleteTempFiles();
michael@0 2164
michael@0 2165 if (mLayerView != null)
michael@0 2166 mLayerView.destroy();
michael@0 2167 if (mDoorHangerPopup != null)
michael@0 2168 mDoorHangerPopup.destroy();
michael@0 2169 if (mFormAssistPopup != null)
michael@0 2170 mFormAssistPopup.destroy();
michael@0 2171 if (mContactService != null)
michael@0 2172 mContactService.destroy();
michael@0 2173 if (mPromptService != null)
michael@0 2174 mPromptService.destroy();
michael@0 2175 if (mTextSelection != null)
michael@0 2176 mTextSelection.destroy();
michael@0 2177 NotificationHelper.destroy();
michael@0 2178
michael@0 2179 if (SmsManager.getInstance() != null) {
michael@0 2180 SmsManager.getInstance().stop();
michael@0 2181 if (isFinishing())
michael@0 2182 SmsManager.getInstance().shutdown();
michael@0 2183 }
michael@0 2184
michael@0 2185 final HealthRecorder rec = mHealthRecorder;
michael@0 2186 mHealthRecorder = null;
michael@0 2187 if (rec != null && rec.isEnabled()) {
michael@0 2188 // Closing a BrowserHealthRecorder could incur a write.
michael@0 2189 ThreadUtils.postToBackgroundThread(new Runnable() {
michael@0 2190 @Override
michael@0 2191 public void run() {
michael@0 2192 rec.close();
michael@0 2193 }
michael@0 2194 });
michael@0 2195 }
michael@0 2196
michael@0 2197 Favicons.close();
michael@0 2198
michael@0 2199 super.onDestroy();
michael@0 2200
michael@0 2201 Tabs.unregisterOnTabsChangedListener(this);
michael@0 2202 }
michael@0 2203
michael@0 2204 protected void registerEventListener(String event) {
michael@0 2205 GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
michael@0 2206 }
michael@0 2207
michael@0 2208 protected void unregisterEventListener(String event) {
michael@0 2209 GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
michael@0 2210 }
michael@0 2211
michael@0 2212 // Get a temporary directory, may return null
michael@0 2213 public static File getTempDirectory() {
michael@0 2214 File dir = GeckoApplication.get().getExternalFilesDir("temp");
michael@0 2215 return dir;
michael@0 2216 }
michael@0 2217
michael@0 2218 // Delete any files in our temporary directory
michael@0 2219 public static void deleteTempFiles() {
michael@0 2220 File dir = getTempDirectory();
michael@0 2221 if (dir == null)
michael@0 2222 return;
michael@0 2223 File[] files = dir.listFiles();
michael@0 2224 if (files == null)
michael@0 2225 return;
michael@0 2226 for (File file : files) {
michael@0 2227 file.delete();
michael@0 2228 }
michael@0 2229 }
michael@0 2230
michael@0 2231 @Override
michael@0 2232 public void onConfigurationChanged(Configuration newConfig) {
michael@0 2233 Log.d(LOGTAG, "onConfigurationChanged: " + newConfig.locale);
michael@0 2234 BrowserLocaleManager.getInstance().correctLocale(this, getResources(), newConfig);
michael@0 2235
michael@0 2236 // onConfigurationChanged is not called for 180 degree orientation changes,
michael@0 2237 // we will miss such rotations and the screen orientation will not be
michael@0 2238 // updated.
michael@0 2239 if (GeckoScreenOrientation.getInstance().update(newConfig.orientation)) {
michael@0 2240 if (mFormAssistPopup != null)
michael@0 2241 mFormAssistPopup.hide();
michael@0 2242 refreshChrome();
michael@0 2243 }
michael@0 2244 super.onConfigurationChanged(newConfig);
michael@0 2245 }
michael@0 2246
michael@0 2247 public String getContentProcessName() {
michael@0 2248 return AppConstants.MOZ_CHILD_PROCESS_NAME;
michael@0 2249 }
michael@0 2250
michael@0 2251 public void addEnvToIntent(Intent intent) {
michael@0 2252 Map<String,String> envMap = System.getenv();
michael@0 2253 Set<Map.Entry<String,String>> envSet = envMap.entrySet();
michael@0 2254 Iterator<Map.Entry<String,String>> envIter = envSet.iterator();
michael@0 2255 int c = 0;
michael@0 2256 while (envIter.hasNext()) {
michael@0 2257 Map.Entry<String,String> entry = envIter.next();
michael@0 2258 intent.putExtra("env" + c, entry.getKey() + "="
michael@0 2259 + entry.getValue());
michael@0 2260 c++;
michael@0 2261 }
michael@0 2262 }
michael@0 2263
michael@0 2264 public void doRestart() {
michael@0 2265 doRestart(RESTARTER_ACTION, null);
michael@0 2266 }
michael@0 2267
michael@0 2268 public void doRestart(String args) {
michael@0 2269 doRestart(RESTARTER_ACTION, args);
michael@0 2270 }
michael@0 2271
michael@0 2272 public void doRestart(String action, String args) {
michael@0 2273 Log.d(LOGTAG, "doRestart(\"" + action + "\")");
michael@0 2274 try {
michael@0 2275 Intent intent = new Intent(action);
michael@0 2276 intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, RESTARTER_CLASS);
michael@0 2277 /* TODO: addEnvToIntent(intent); */
michael@0 2278 if (args != null)
michael@0 2279 intent.putExtra("args", args);
michael@0 2280 intent.putExtra("didRestart", true);
michael@0 2281 Log.d(LOGTAG, "Restart intent: " + intent.toString());
michael@0 2282 GeckoAppShell.killAnyZombies();
michael@0 2283 startActivity(intent);
michael@0 2284 } catch (Exception e) {
michael@0 2285 Log.e(LOGTAG, "Error effecting restart.", e);
michael@0 2286 }
michael@0 2287
michael@0 2288 finish();
michael@0 2289 // Give the restart process time to start before we die
michael@0 2290 GeckoAppShell.waitForAnotherGeckoProc();
michael@0 2291 }
michael@0 2292
michael@0 2293 public void handleNotification(String action, String alertName, String alertCookie) {
michael@0 2294 // If Gecko isn't running yet, we ignore the notification. Note that
michael@0 2295 // even if Gecko is running but it was restarted since the notification
michael@0 2296 // was created, the notification won't be handled (bug 849653).
michael@0 2297 if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
michael@0 2298 GeckoAppShell.handleNotification(action, alertName, alertCookie);
michael@0 2299 }
michael@0 2300 }
michael@0 2301
michael@0 2302 private void checkMigrateProfile() {
michael@0 2303 final File profileDir = getProfile().getDir();
michael@0 2304
michael@0 2305 if (profileDir != null) {
michael@0 2306 ThreadUtils.postToBackgroundThread(new Runnable() {
michael@0 2307 @Override
michael@0 2308 public void run() {
michael@0 2309 Handler handler = new Handler();
michael@0 2310 handler.postDelayed(new DeferredCleanupTask(), CLEANUP_DEFERRAL_SECONDS * 1000);
michael@0 2311 }
michael@0 2312 });
michael@0 2313 }
michael@0 2314 }
michael@0 2315
michael@0 2316 private class DeferredCleanupTask implements Runnable {
michael@0 2317 // The cleanup-version setting is recorded to avoid repeating the same
michael@0 2318 // tasks on subsequent startups; CURRENT_CLEANUP_VERSION may be updated
michael@0 2319 // if we need to do additional cleanup for future Gecko versions.
michael@0 2320
michael@0 2321 private static final String CLEANUP_VERSION = "cleanup-version";
michael@0 2322 private static final int CURRENT_CLEANUP_VERSION = 1;
michael@0 2323
michael@0 2324 @Override
michael@0 2325 public void run() {
michael@0 2326 long cleanupVersion = getSharedPreferences().getInt(CLEANUP_VERSION, 0);
michael@0 2327
michael@0 2328 if (cleanupVersion < 1) {
michael@0 2329 // Reduce device storage footprint by removing .ttf files from
michael@0 2330 // the res/fonts directory: we no longer need to copy our
michael@0 2331 // bundled fonts out of the APK in order to use them.
michael@0 2332 // See https://bugzilla.mozilla.org/show_bug.cgi?id=878674.
michael@0 2333 File dir = new File("res/fonts");
michael@0 2334 if (dir.exists() && dir.isDirectory()) {
michael@0 2335 for (File file : dir.listFiles()) {
michael@0 2336 if (file.isFile() && file.getName().endsWith(".ttf")) {
michael@0 2337 Log.i(LOGTAG, "deleting " + file.toString());
michael@0 2338 file.delete();
michael@0 2339 }
michael@0 2340 }
michael@0 2341 if (!dir.delete()) {
michael@0 2342 Log.w(LOGTAG, "unable to delete res/fonts directory (not empty?)");
michael@0 2343 } else {
michael@0 2344 Log.i(LOGTAG, "res/fonts directory deleted");
michael@0 2345 }
michael@0 2346 }
michael@0 2347 }
michael@0 2348
michael@0 2349 // Additional cleanup needed for future versions would go here
michael@0 2350
michael@0 2351 if (cleanupVersion != CURRENT_CLEANUP_VERSION) {
michael@0 2352 SharedPreferences.Editor editor = GeckoApp.this.getSharedPreferences().edit();
michael@0 2353 editor.putInt(CLEANUP_VERSION, CURRENT_CLEANUP_VERSION);
michael@0 2354 editor.commit();
michael@0 2355 }
michael@0 2356 }
michael@0 2357 }
michael@0 2358
michael@0 2359 public PromptService getPromptService() {
michael@0 2360 return mPromptService;
michael@0 2361 }
michael@0 2362
michael@0 2363 @Override
michael@0 2364 public void onBackPressed() {
michael@0 2365 if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
michael@0 2366 super.onBackPressed();
michael@0 2367 return;
michael@0 2368 }
michael@0 2369
michael@0 2370 if (autoHideTabs()) {
michael@0 2371 return;
michael@0 2372 }
michael@0 2373
michael@0 2374 if (mDoorHangerPopup != null && mDoorHangerPopup.isShowing()) {
michael@0 2375 mDoorHangerPopup.dismiss();
michael@0 2376 return;
michael@0 2377 }
michael@0 2378
michael@0 2379 if (mFullScreenPluginView != null) {
michael@0 2380 GeckoAppShell.onFullScreenPluginHidden(mFullScreenPluginView);
michael@0 2381 removeFullScreenPluginView(mFullScreenPluginView);
michael@0 2382 return;
michael@0 2383 }
michael@0 2384
michael@0 2385 if (mLayerView != null && mLayerView.isFullScreen()) {
michael@0 2386 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FullScreen:Exit", null));
michael@0 2387 return;
michael@0 2388 }
michael@0 2389
michael@0 2390 Tabs tabs = Tabs.getInstance();
michael@0 2391 Tab tab = tabs.getSelectedTab();
michael@0 2392 if (tab == null) {
michael@0 2393 moveTaskToBack(true);
michael@0 2394 return;
michael@0 2395 }
michael@0 2396
michael@0 2397 if (tab.doBack())
michael@0 2398 return;
michael@0 2399
michael@0 2400 if (tab.isExternal()) {
michael@0 2401 moveTaskToBack(true);
michael@0 2402 tabs.closeTab(tab);
michael@0 2403 return;
michael@0 2404 }
michael@0 2405
michael@0 2406 int parentId = tab.getParentId();
michael@0 2407 Tab parent = tabs.getTab(parentId);
michael@0 2408 if (parent != null) {
michael@0 2409 // The back button should always return to the parent (not a sibling).
michael@0 2410 tabs.closeTab(tab, parent);
michael@0 2411 return;
michael@0 2412 }
michael@0 2413
michael@0 2414 moveTaskToBack(true);
michael@0 2415 }
michael@0 2416
michael@0 2417 @Override
michael@0 2418 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
michael@0 2419 if (!ActivityHandlerHelper.handleActivityResult(requestCode, resultCode, data)) {
michael@0 2420 super.onActivityResult(requestCode, resultCode, data);
michael@0 2421 }
michael@0 2422 }
michael@0 2423
michael@0 2424 public AbsoluteLayout getPluginContainer() { return mPluginContainer; }
michael@0 2425
michael@0 2426 // Accelerometer.
michael@0 2427 @Override
michael@0 2428 public void onAccuracyChanged(Sensor sensor, int accuracy) {
michael@0 2429 }
michael@0 2430
michael@0 2431 @Override
michael@0 2432 public void onSensorChanged(SensorEvent event) {
michael@0 2433 GeckoAppShell.sendEventToGecko(GeckoEvent.createSensorEvent(event));
michael@0 2434 }
michael@0 2435
michael@0 2436 // Geolocation.
michael@0 2437 @Override
michael@0 2438 public void onLocationChanged(Location location) {
michael@0 2439 // No logging here: user-identifying information.
michael@0 2440 GeckoAppShell.sendEventToGecko(GeckoEvent.createLocationEvent(location));
michael@0 2441 if (mShouldReportGeoData)
michael@0 2442 collectAndReportLocInfo(location);
michael@0 2443 }
michael@0 2444
michael@0 2445 public void setCurrentSignalStrenth(SignalStrength ss) {
michael@0 2446 if (ss.isGsm())
michael@0 2447 mSignalStrenth = ss.getGsmSignalStrength();
michael@0 2448 }
michael@0 2449
michael@0 2450 private int getCellInfo(JSONArray cellInfo) {
michael@0 2451 TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
michael@0 2452 if (tm == null)
michael@0 2453 return TelephonyManager.PHONE_TYPE_NONE;
michael@0 2454 List<NeighboringCellInfo> cells = tm.getNeighboringCellInfo();
michael@0 2455 CellLocation cl = tm.getCellLocation();
michael@0 2456 String mcc = "", mnc = "";
michael@0 2457 if (cl instanceof GsmCellLocation) {
michael@0 2458 JSONObject obj = new JSONObject();
michael@0 2459 GsmCellLocation gcl = (GsmCellLocation)cl;
michael@0 2460 try {
michael@0 2461 obj.put("lac", gcl.getLac());
michael@0 2462 obj.put("cid", gcl.getCid());
michael@0 2463
michael@0 2464 int psc = (Build.VERSION.SDK_INT >= 9) ? gcl.getPsc() : -1;
michael@0 2465 obj.put("psc", psc);
michael@0 2466
michael@0 2467 switch(tm.getNetworkType()) {
michael@0 2468 case TelephonyManager.NETWORK_TYPE_GPRS:
michael@0 2469 case TelephonyManager.NETWORK_TYPE_EDGE:
michael@0 2470 obj.put("radio", "gsm");
michael@0 2471 break;
michael@0 2472 case TelephonyManager.NETWORK_TYPE_UMTS:
michael@0 2473 case TelephonyManager.NETWORK_TYPE_HSDPA:
michael@0 2474 case TelephonyManager.NETWORK_TYPE_HSUPA:
michael@0 2475 case TelephonyManager.NETWORK_TYPE_HSPA:
michael@0 2476 case TelephonyManager.NETWORK_TYPE_HSPAP:
michael@0 2477 obj.put("radio", "umts");
michael@0 2478 break;
michael@0 2479 }
michael@0 2480 String mcc_mnc = tm.getNetworkOperator();
michael@0 2481 if (mcc_mnc.length() > 3) {
michael@0 2482 mcc = mcc_mnc.substring(0, 3);
michael@0 2483 mnc = mcc_mnc.substring(3);
michael@0 2484 obj.put("mcc", mcc);
michael@0 2485 obj.put("mnc", mnc);
michael@0 2486 }
michael@0 2487 obj.put("asu", mSignalStrenth);
michael@0 2488 } catch(JSONException jsonex) {}
michael@0 2489 cellInfo.put(obj);
michael@0 2490 }
michael@0 2491 if (cells != null) {
michael@0 2492 for (NeighboringCellInfo nci : cells) {
michael@0 2493 try {
michael@0 2494 JSONObject obj = new JSONObject();
michael@0 2495 obj.put("lac", nci.getLac());
michael@0 2496 obj.put("cid", nci.getCid());
michael@0 2497 obj.put("psc", nci.getPsc());
michael@0 2498 obj.put("mcc", mcc);
michael@0 2499 obj.put("mnc", mnc);
michael@0 2500
michael@0 2501 int dbm;
michael@0 2502 switch(nci.getNetworkType()) {
michael@0 2503 case TelephonyManager.NETWORK_TYPE_GPRS:
michael@0 2504 case TelephonyManager.NETWORK_TYPE_EDGE:
michael@0 2505 obj.put("radio", "gsm");
michael@0 2506 break;
michael@0 2507 case TelephonyManager.NETWORK_TYPE_UMTS:
michael@0 2508 case TelephonyManager.NETWORK_TYPE_HSDPA:
michael@0 2509 case TelephonyManager.NETWORK_TYPE_HSUPA:
michael@0 2510 case TelephonyManager.NETWORK_TYPE_HSPA:
michael@0 2511 case TelephonyManager.NETWORK_TYPE_HSPAP:
michael@0 2512 obj.put("radio", "umts");
michael@0 2513 break;
michael@0 2514 }
michael@0 2515
michael@0 2516 obj.put("asu", nci.getRssi());
michael@0 2517 cellInfo.put(obj);
michael@0 2518 } catch(JSONException jsonex) {}
michael@0 2519 }
michael@0 2520 }
michael@0 2521 return tm.getPhoneType();
michael@0 2522 }
michael@0 2523
michael@0 2524 private static boolean shouldLog(final ScanResult sr) {
michael@0 2525 return sr.SSID == null || !sr.SSID.endsWith("_nomap");
michael@0 2526 }
michael@0 2527
michael@0 2528 private void collectAndReportLocInfo(Location location) {
michael@0 2529 final JSONObject locInfo = new JSONObject();
michael@0 2530 WifiManager wm = (WifiManager)getSystemService(Context.WIFI_SERVICE);
michael@0 2531 wm.startScan();
michael@0 2532 try {
michael@0 2533 JSONArray cellInfo = new JSONArray();
michael@0 2534
michael@0 2535 String radioType = getRadioTypeName(getCellInfo(cellInfo));
michael@0 2536 if (radioType != null) {
michael@0 2537 locInfo.put("radio", radioType);
michael@0 2538 }
michael@0 2539
michael@0 2540 locInfo.put("lon", location.getLongitude());
michael@0 2541 locInfo.put("lat", location.getLatitude());
michael@0 2542
michael@0 2543 // If we have an accuracy, round it up to the next meter.
michael@0 2544 if (location.hasAccuracy()) {
michael@0 2545 locInfo.put("accuracy", (int) Math.ceil(location.getAccuracy()));
michael@0 2546 }
michael@0 2547
michael@0 2548 // If we have an altitude, round it to the nearest meter.
michael@0 2549 if (location.hasAltitude()) {
michael@0 2550 locInfo.put("altitude", Math.round(location.getAltitude()));
michael@0 2551 }
michael@0 2552
michael@0 2553 // Reduce timestamp precision so as to expose less PII.
michael@0 2554 DateFormat df = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
michael@0 2555 locInfo.put("time", df.format(new Date(location.getTime())));
michael@0 2556 locInfo.put("cell", cellInfo);
michael@0 2557
michael@0 2558 JSONArray wifiInfo = new JSONArray();
michael@0 2559 List<ScanResult> aps = wm.getScanResults();
michael@0 2560 if (aps != null) {
michael@0 2561 for (ScanResult ap : aps) {
michael@0 2562 if (!shouldLog(ap))
michael@0 2563 continue;
michael@0 2564
michael@0 2565 JSONObject obj = new JSONObject();
michael@0 2566 obj.put("key", ap.BSSID);
michael@0 2567 obj.put("frequency", ap.frequency);
michael@0 2568 obj.put("signal", ap.level);
michael@0 2569 wifiInfo.put(obj);
michael@0 2570 }
michael@0 2571 }
michael@0 2572 locInfo.put("wifi", wifiInfo);
michael@0 2573 } catch (JSONException jsonex) {
michael@0 2574 Log.w(LOGTAG, "json exception", jsonex);
michael@0 2575 return;
michael@0 2576 }
michael@0 2577
michael@0 2578 ThreadUtils.postToBackgroundThread(new Runnable() {
michael@0 2579 public void run() {
michael@0 2580 try {
michael@0 2581 URL url = new URL(LOCATION_URL);
michael@0 2582 HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
michael@0 2583 try {
michael@0 2584 urlConnection.setDoOutput(true);
michael@0 2585
michael@0 2586 // Workaround for a bug in Android HttpURLConnection. When the library
michael@0 2587 // reuses a stale connection, the connection may fail with an EOFException.
michael@0 2588 if (Build.VERSION.SDK_INT >= 14 && Build.VERSION.SDK_INT <= 18) {
michael@0 2589 urlConnection.setRequestProperty("Connection", "Close");
michael@0 2590 }
michael@0 2591
michael@0 2592 JSONArray batch = new JSONArray();
michael@0 2593 batch.put(locInfo);
michael@0 2594 JSONObject wrapper = new JSONObject();
michael@0 2595 wrapper.put("items", batch);
michael@0 2596 byte[] bytes = wrapper.toString().getBytes();
michael@0 2597 urlConnection.setFixedLengthStreamingMode(bytes.length);
michael@0 2598 OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
michael@0 2599 out.write(bytes);
michael@0 2600 out.flush();
michael@0 2601 } catch (JSONException jsonex) {
michael@0 2602 Log.e(LOGTAG, "error wrapping data as a batch", jsonex);
michael@0 2603 } catch (IOException ioex) {
michael@0 2604 Log.e(LOGTAG, "error submitting data", ioex);
michael@0 2605 } finally {
michael@0 2606 urlConnection.disconnect();
michael@0 2607 }
michael@0 2608 } catch (IOException ioex) {
michael@0 2609 Log.e(LOGTAG, "error submitting data", ioex);
michael@0 2610 }
michael@0 2611 }
michael@0 2612 });
michael@0 2613 }
michael@0 2614
michael@0 2615 private static String getRadioTypeName(int phoneType) {
michael@0 2616 switch (phoneType) {
michael@0 2617 case TelephonyManager.PHONE_TYPE_CDMA:
michael@0 2618 return "cdma";
michael@0 2619
michael@0 2620 case TelephonyManager.PHONE_TYPE_GSM:
michael@0 2621 return "gsm";
michael@0 2622
michael@0 2623 case TelephonyManager.PHONE_TYPE_NONE:
michael@0 2624 case TelephonyManager.PHONE_TYPE_SIP:
michael@0 2625 // These devices have no radio.
michael@0 2626 return null;
michael@0 2627
michael@0 2628 default:
michael@0 2629 Log.e(LOGTAG, "", new IllegalArgumentException("Unexpected PHONE_TYPE: " + phoneType));
michael@0 2630 return null;
michael@0 2631 }
michael@0 2632 }
michael@0 2633
michael@0 2634 @Override
michael@0 2635 public void onProviderDisabled(String provider)
michael@0 2636 {
michael@0 2637 }
michael@0 2638
michael@0 2639 @Override
michael@0 2640 public void onProviderEnabled(String provider)
michael@0 2641 {
michael@0 2642 }
michael@0 2643
michael@0 2644 @Override
michael@0 2645 public void onStatusChanged(String provider, int status, Bundle extras)
michael@0 2646 {
michael@0 2647 }
michael@0 2648
michael@0 2649 // Called when a Gecko Hal WakeLock is changed
michael@0 2650 public void notifyWakeLockChanged(String topic, String state) {
michael@0 2651 PowerManager.WakeLock wl = mWakeLocks.get(topic);
michael@0 2652 if (state.equals("locked-foreground") && wl == null) {
michael@0 2653 PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
michael@0 2654 wl = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, topic);
michael@0 2655 wl.acquire();
michael@0 2656 mWakeLocks.put(topic, wl);
michael@0 2657 } else if (!state.equals("locked-foreground") && wl != null) {
michael@0 2658 wl.release();
michael@0 2659 mWakeLocks.remove(topic);
michael@0 2660 }
michael@0 2661 }
michael@0 2662
michael@0 2663 public void notifyCheckUpdateResult(String result) {
michael@0 2664 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Update:CheckResult", result));
michael@0 2665 }
michael@0 2666
michael@0 2667 protected void geckoConnected() {
michael@0 2668 mLayerView.geckoConnected();
michael@0 2669 mLayerView.setOverScrollMode(View.OVER_SCROLL_NEVER);
michael@0 2670 }
michael@0 2671
michael@0 2672 public void setAccessibilityEnabled(boolean enabled) {
michael@0 2673 }
michael@0 2674
michael@0 2675 public static class MainLayout extends RelativeLayout {
michael@0 2676 private TouchEventInterceptor mTouchEventInterceptor;
michael@0 2677 private MotionEventInterceptor mMotionEventInterceptor;
michael@0 2678
michael@0 2679 public MainLayout(Context context, AttributeSet attrs) {
michael@0 2680 super(context, attrs);
michael@0 2681 }
michael@0 2682
michael@0 2683 public void setTouchEventInterceptor(TouchEventInterceptor interceptor) {
michael@0 2684 mTouchEventInterceptor = interceptor;
michael@0 2685 }
michael@0 2686
michael@0 2687 public void setMotionEventInterceptor(MotionEventInterceptor interceptor) {
michael@0 2688 mMotionEventInterceptor = interceptor;
michael@0 2689 }
michael@0 2690
michael@0 2691 @Override
michael@0 2692 public boolean onInterceptTouchEvent(MotionEvent event) {
michael@0 2693 if (mTouchEventInterceptor != null && mTouchEventInterceptor.onInterceptTouchEvent(this, event)) {
michael@0 2694 return true;
michael@0 2695 }
michael@0 2696 return super.onInterceptTouchEvent(event);
michael@0 2697 }
michael@0 2698
michael@0 2699 @Override
michael@0 2700 public boolean onTouchEvent(MotionEvent event) {
michael@0 2701 if (mTouchEventInterceptor != null && mTouchEventInterceptor.onTouch(this, event)) {
michael@0 2702 return true;
michael@0 2703 }
michael@0 2704 return super.onTouchEvent(event);
michael@0 2705 }
michael@0 2706
michael@0 2707 @Override
michael@0 2708 public boolean onGenericMotionEvent(MotionEvent event) {
michael@0 2709 if (mMotionEventInterceptor != null && mMotionEventInterceptor.onInterceptMotionEvent(this, event)) {
michael@0 2710 return true;
michael@0 2711 }
michael@0 2712 return super.onGenericMotionEvent(event);
michael@0 2713 }
michael@0 2714
michael@0 2715 @Override
michael@0 2716 public void setDrawingCacheEnabled(boolean enabled) {
michael@0 2717 // Instead of setting drawing cache in the view itself, we simply
michael@0 2718 // enable drawing caching on its children. This is mainly used in
michael@0 2719 // animations (see PropertyAnimator)
michael@0 2720 super.setChildrenDrawnWithCacheEnabled(enabled);
michael@0 2721 }
michael@0 2722 }
michael@0 2723
michael@0 2724 private class FullScreenHolder extends FrameLayout {
michael@0 2725
michael@0 2726 public FullScreenHolder(Context ctx) {
michael@0 2727 super(ctx);
michael@0 2728 }
michael@0 2729
michael@0 2730 @Override
michael@0 2731 public void addView(View view, int index) {
michael@0 2732 /**
michael@0 2733 * This normally gets called when Flash adds a separate SurfaceView
michael@0 2734 * for the video. It is unhappy if we have the LayerView underneath
michael@0 2735 * it for some reason so we need to hide that. Hiding the LayerView causes
michael@0 2736 * its surface to be destroyed, which causes a pause composition
michael@0 2737 * event to be sent to Gecko. We synchronously wait for that to be
michael@0 2738 * processed. Simultaneously, however, Flash is waiting on a mutex so
michael@0 2739 * the post() below is an attempt to avoid a deadlock.
michael@0 2740 */
michael@0 2741 super.addView(view, index);
michael@0 2742
michael@0 2743 ThreadUtils.postToUiThread(new Runnable() {
michael@0 2744 @Override
michael@0 2745 public void run() {
michael@0 2746 mLayerView.hideSurface();
michael@0 2747 }
michael@0 2748 });
michael@0 2749 }
michael@0 2750
michael@0 2751 /**
michael@0 2752 * The methods below are simply copied from what Android WebKit does.
michael@0 2753 * It wasn't ever called in my testing, but might as well
michael@0 2754 * keep it in case it is for some reason. The methods
michael@0 2755 * all return true because we don't want any events
michael@0 2756 * leaking out from the fullscreen view.
michael@0 2757 */
michael@0 2758 @Override
michael@0 2759 public boolean onKeyDown(int keyCode, KeyEvent event) {
michael@0 2760 if (event.isSystem()) {
michael@0 2761 return super.onKeyDown(keyCode, event);
michael@0 2762 }
michael@0 2763 mFullScreenPluginView.onKeyDown(keyCode, event);
michael@0 2764 return true;
michael@0 2765 }
michael@0 2766
michael@0 2767 @Override
michael@0 2768 public boolean onKeyUp(int keyCode, KeyEvent event) {
michael@0 2769 if (event.isSystem()) {
michael@0 2770 return super.onKeyUp(keyCode, event);
michael@0 2771 }
michael@0 2772 mFullScreenPluginView.onKeyUp(keyCode, event);
michael@0 2773 return true;
michael@0 2774 }
michael@0 2775
michael@0 2776 @Override
michael@0 2777 public boolean onTouchEvent(MotionEvent event) {
michael@0 2778 return true;
michael@0 2779 }
michael@0 2780
michael@0 2781 @Override
michael@0 2782 public boolean onTrackballEvent(MotionEvent event) {
michael@0 2783 mFullScreenPluginView.onTrackballEvent(event);
michael@0 2784 return true;
michael@0 2785 }
michael@0 2786 }
michael@0 2787
michael@0 2788 protected NotificationClient makeNotificationClient() {
michael@0 2789 // Don't use a notification service; we may be killed in the background
michael@0 2790 // during downloads.
michael@0 2791 return new AppNotificationClient(getApplicationContext());
michael@0 2792 }
michael@0 2793
michael@0 2794 private int getVersionCode() {
michael@0 2795 int versionCode = 0;
michael@0 2796 try {
michael@0 2797 versionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode;
michael@0 2798 } catch (NameNotFoundException e) {
michael@0 2799 Log.wtf(LOGTAG, getPackageName() + " not found", e);
michael@0 2800 }
michael@0 2801 return versionCode;
michael@0 2802 }
michael@0 2803
michael@0 2804 protected boolean getIsDebuggable() {
michael@0 2805 // Return false so Fennec doesn't appear to be debuggable. WebappImpl
michael@0 2806 // then overrides this and returns the value of android:debuggable for
michael@0 2807 // the webapp APK, so webapps get the behavior supported by this method
michael@0 2808 // (i.e. automatic configuration and enabling of the remote debugger).
michael@0 2809 return false;
michael@0 2810
michael@0 2811 // If we ever want to expose this for Fennec, here's how we would do it:
michael@0 2812 // int flags = 0;
michael@0 2813 // try {
michael@0 2814 // flags = getPackageManager().getPackageInfo(getPackageName(), 0).applicationInfo.flags;
michael@0 2815 // } catch (NameNotFoundException e) {
michael@0 2816 // Log.wtf(LOGTAG, getPackageName() + " not found", e);
michael@0 2817 // }
michael@0 2818 // return (flags & android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE) != 0;
michael@0 2819 }
michael@0 2820
michael@0 2821 // FHR reason code for a session end prior to a restart for a
michael@0 2822 // locale change.
michael@0 2823 private static final String SESSION_END_LOCALE_CHANGED = "L";
michael@0 2824
michael@0 2825 /**
michael@0 2826 * Use BrowserLocaleManager to change our persisted and current locales,
michael@0 2827 * and poke HealthRecorder to tell it of our changed state.
michael@0 2828 */
michael@0 2829 private void setLocale(final String locale) {
michael@0 2830 if (locale == null) {
michael@0 2831 return;
michael@0 2832 }
michael@0 2833 final String resultant = BrowserLocaleManager.getInstance().setSelectedLocale(this, locale);
michael@0 2834 if (resultant == null) {
michael@0 2835 return;
michael@0 2836 }
michael@0 2837
michael@0 2838 final boolean startNewSession = true;
michael@0 2839 final boolean shouldRestart = false;
michael@0 2840
michael@0 2841 // If the HealthRecorder is not yet initialized (unlikely), the locale change won't
michael@0 2842 // trigger a session transition and subsequent events will be recorded in an environment
michael@0 2843 // with the wrong locale.
michael@0 2844 final HealthRecorder rec = mHealthRecorder;
michael@0 2845 if (rec != null) {
michael@0 2846 rec.onAppLocaleChanged(resultant);
michael@0 2847 rec.onEnvironmentChanged(startNewSession, SESSION_END_LOCALE_CHANGED);
michael@0 2848 }
michael@0 2849
michael@0 2850 if (!shouldRestart) {
michael@0 2851 ThreadUtils.postToUiThread(new Runnable() {
michael@0 2852 @Override
michael@0 2853 public void run() {
michael@0 2854 GeckoApp.this.onLocaleReady(resultant);
michael@0 2855 }
michael@0 2856 });
michael@0 2857 return;
michael@0 2858 }
michael@0 2859
michael@0 2860 // Do this in the background so that the health recorder has its
michael@0 2861 // time to finish.
michael@0 2862 ThreadUtils.postToBackgroundThread(new Runnable() {
michael@0 2863 @Override
michael@0 2864 public void run() {
michael@0 2865 GeckoApp.this.doRestart();
michael@0 2866 GeckoApp.this.finish();
michael@0 2867 }
michael@0 2868 });
michael@0 2869 }
michael@0 2870
michael@0 2871 private void setSystemUiVisible(final boolean visible) {
michael@0 2872 if (Build.VERSION.SDK_INT < 14) {
michael@0 2873 return;
michael@0 2874 }
michael@0 2875
michael@0 2876 ThreadUtils.postToUiThread(new Runnable() {
michael@0 2877 @Override
michael@0 2878 public void run() {
michael@0 2879 if (visible) {
michael@0 2880 mMainLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
michael@0 2881 } else {
michael@0 2882 mMainLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
michael@0 2883 }
michael@0 2884 }
michael@0 2885 });
michael@0 2886 }
michael@0 2887
michael@0 2888 protected HealthRecorder createHealthRecorder(final Context context,
michael@0 2889 final String profilePath,
michael@0 2890 final EventDispatcher dispatcher,
michael@0 2891 final String osLocale,
michael@0 2892 final String appLocale,
michael@0 2893 final SessionInformation previousSession) {
michael@0 2894 // GeckoApp does not need to record any health information - return a stub.
michael@0 2895 return new StubbedHealthRecorder();
michael@0 2896 }
michael@0 2897 }

mercurial