Wed, 31 Dec 2014 06:09:35 +0100
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 | } |