1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/GeckoApp.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2897 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +package org.mozilla.gecko; 1.10 + 1.11 +import java.io.BufferedOutputStream; 1.12 +import java.io.ByteArrayOutputStream; 1.13 +import java.io.File; 1.14 +import java.io.IOException; 1.15 +import java.io.InputStream; 1.16 +import java.io.OutputStream; 1.17 +import java.net.HttpURLConnection; 1.18 +import java.net.URL; 1.19 +import java.text.DateFormat; 1.20 +import java.text.SimpleDateFormat; 1.21 +import java.util.ArrayList; 1.22 +import java.util.Arrays; 1.23 +import java.util.Date; 1.24 +import java.util.HashMap; 1.25 +import java.util.Iterator; 1.26 +import java.util.LinkedList; 1.27 +import java.util.List; 1.28 +import java.util.Locale; 1.29 +import java.util.Map; 1.30 +import java.util.Set; 1.31 +import java.util.regex.Matcher; 1.32 +import java.util.regex.Pattern; 1.33 + 1.34 +import org.json.JSONArray; 1.35 +import org.json.JSONException; 1.36 +import org.json.JSONObject; 1.37 +import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException; 1.38 +import org.mozilla.gecko.background.announcements.AnnouncementsBroadcastService; 1.39 +import org.mozilla.gecko.db.BrowserDB; 1.40 +import org.mozilla.gecko.favicons.Favicons; 1.41 +import org.mozilla.gecko.gfx.BitmapUtils; 1.42 +import org.mozilla.gecko.gfx.Layer; 1.43 +import org.mozilla.gecko.gfx.LayerView; 1.44 +import org.mozilla.gecko.gfx.PluginLayer; 1.45 +import org.mozilla.gecko.health.HealthRecorder; 1.46 +import org.mozilla.gecko.health.SessionInformation; 1.47 +import org.mozilla.gecko.health.StubbedHealthRecorder; 1.48 +import org.mozilla.gecko.menu.GeckoMenu; 1.49 +import org.mozilla.gecko.menu.GeckoMenuInflater; 1.50 +import org.mozilla.gecko.menu.MenuPanel; 1.51 +import org.mozilla.gecko.mozglue.GeckoLoader; 1.52 +import org.mozilla.gecko.preferences.GeckoPreferences; 1.53 +import org.mozilla.gecko.prompts.PromptService; 1.54 +import org.mozilla.gecko.updater.UpdateService; 1.55 +import org.mozilla.gecko.updater.UpdateServiceHelper; 1.56 +import org.mozilla.gecko.util.ActivityResultHandler; 1.57 +import org.mozilla.gecko.util.FileUtils; 1.58 +import org.mozilla.gecko.util.GeckoEventListener; 1.59 +import org.mozilla.gecko.util.HardwareUtils; 1.60 +import org.mozilla.gecko.util.ThreadUtils; 1.61 +import org.mozilla.gecko.util.UiAsyncTask; 1.62 +import org.mozilla.gecko.webapp.EventListener; 1.63 +import org.mozilla.gecko.webapp.UninstallListener; 1.64 +import org.mozilla.gecko.widget.ButtonToast; 1.65 + 1.66 +import android.app.Activity; 1.67 +import android.app.AlertDialog; 1.68 +import android.app.Dialog; 1.69 +import android.content.Context; 1.70 +import android.content.DialogInterface; 1.71 +import android.content.Intent; 1.72 +import android.content.SharedPreferences; 1.73 +import android.content.pm.PackageManager.NameNotFoundException; 1.74 +import android.content.res.Configuration; 1.75 +import android.graphics.Bitmap; 1.76 +import android.graphics.BitmapFactory; 1.77 +import android.graphics.RectF; 1.78 +import android.graphics.drawable.Drawable; 1.79 +import android.hardware.Sensor; 1.80 +import android.hardware.SensorEvent; 1.81 +import android.hardware.SensorEventListener; 1.82 +import android.location.Location; 1.83 +import android.location.LocationListener; 1.84 +import android.net.Uri; 1.85 +import android.net.wifi.ScanResult; 1.86 +import android.net.wifi.WifiManager; 1.87 +import android.os.Build; 1.88 +import android.os.Bundle; 1.89 +import android.os.Handler; 1.90 +import android.os.PowerManager; 1.91 +import android.os.StrictMode; 1.92 +import android.provider.ContactsContract; 1.93 +import android.provider.MediaStore.Images.Media; 1.94 +import android.telephony.CellLocation; 1.95 +import android.telephony.NeighboringCellInfo; 1.96 +import android.telephony.PhoneStateListener; 1.97 +import android.telephony.SignalStrength; 1.98 +import android.telephony.TelephonyManager; 1.99 +import android.telephony.gsm.GsmCellLocation; 1.100 +import android.text.TextUtils; 1.101 +import android.util.AttributeSet; 1.102 +import android.util.Base64; 1.103 +import android.util.Log; 1.104 +import android.util.SparseBooleanArray; 1.105 +import android.view.Gravity; 1.106 +import android.view.KeyEvent; 1.107 +import android.view.Menu; 1.108 +import android.view.MenuInflater; 1.109 +import android.view.MenuItem; 1.110 +import android.view.MotionEvent; 1.111 +import android.view.OrientationEventListener; 1.112 +import android.view.SurfaceHolder; 1.113 +import android.view.SurfaceView; 1.114 +import android.view.TextureView; 1.115 +import android.view.View; 1.116 +import android.view.ViewGroup; 1.117 +import android.view.ViewStub; 1.118 +import android.view.Window; 1.119 +import android.view.WindowManager; 1.120 +import android.widget.AbsoluteLayout; 1.121 +import android.widget.FrameLayout; 1.122 +import android.widget.ListView; 1.123 +import android.widget.RelativeLayout; 1.124 +import android.widget.SimpleAdapter; 1.125 +import android.widget.TextView; 1.126 +import android.widget.Toast; 1.127 + 1.128 +public abstract class GeckoApp 1.129 + extends GeckoActivity 1.130 + implements 1.131 + ContextGetter, 1.132 + GeckoAppShell.GeckoInterface, 1.133 + GeckoEventListener, 1.134 + GeckoMenu.Callback, 1.135 + GeckoMenu.MenuPresenter, 1.136 + LocationListener, 1.137 + SensorEventListener, 1.138 + Tabs.OnTabsChangedListener 1.139 +{ 1.140 + private static final String LOGTAG = "GeckoApp"; 1.141 + private static final int ONE_DAY_MS = 1000*60*60*24; 1.142 + 1.143 + private static enum StartupAction { 1.144 + NORMAL, /* normal application start */ 1.145 + URL, /* launched with a passed URL */ 1.146 + PREFETCH /* launched with a passed URL that we prefetch */ 1.147 + } 1.148 + 1.149 + public static final String ACTION_ALERT_CALLBACK = "org.mozilla.gecko.ACTION_ALERT_CALLBACK"; 1.150 + public static final String ACTION_BOOKMARK = "org.mozilla.gecko.BOOKMARK"; 1.151 + public static final String ACTION_DEBUG = "org.mozilla.gecko.DEBUG"; 1.152 + public static final String ACTION_LAUNCH_SETTINGS = "org.mozilla.gecko.SETTINGS"; 1.153 + public static final String ACTION_LOAD = "org.mozilla.gecko.LOAD"; 1.154 + public static final String ACTION_INIT_PW = "org.mozilla.gecko.INIT_PW"; 1.155 + public static final String ACTION_WEBAPP_PREFIX = "org.mozilla.gecko.WEBAPP"; 1.156 + 1.157 + public static final String EXTRA_STATE_BUNDLE = "stateBundle"; 1.158 + 1.159 + public static final String PREFS_ALLOW_STATE_BUNDLE = "allowStateBundle"; 1.160 + public static final String PREFS_OOM_EXCEPTION = "OOMException"; 1.161 + public static final String PREFS_VERSION_CODE = "versionCode"; 1.162 + public static final String PREFS_WAS_STOPPED = "wasStopped"; 1.163 + public static final String PREFS_CRASHED = "crashed"; 1.164 + public static final String PREFS_CLEANUP_TEMP_FILES = "cleanupTempFiles"; 1.165 + 1.166 + public static final String SAVED_STATE_IN_BACKGROUND = "inBackground"; 1.167 + public static final String SAVED_STATE_PRIVATE_SESSION = "privateSession"; 1.168 + 1.169 + static private final String LOCATION_URL = "https://location.services.mozilla.com/v1/submit"; 1.170 + 1.171 + // Delay before running one-time "cleanup" tasks that may be needed 1.172 + // after a version upgrade. 1.173 + private static final int CLEANUP_DEFERRAL_SECONDS = 15; 1.174 + 1.175 + protected RelativeLayout mMainLayout; 1.176 + protected RelativeLayout mGeckoLayout; 1.177 + public View getView() { return mGeckoLayout; } 1.178 + private View mCameraView; 1.179 + private OrientationEventListener mCameraOrientationEventListener; 1.180 + public List<GeckoAppShell.AppStateListener> mAppStateListeners; 1.181 + protected MenuPanel mMenuPanel; 1.182 + protected Menu mMenu; 1.183 + protected GeckoProfile mProfile; 1.184 + protected boolean mIsRestoringActivity; 1.185 + 1.186 + private ContactService mContactService; 1.187 + private PromptService mPromptService; 1.188 + private TextSelection mTextSelection; 1.189 + 1.190 + protected DoorHangerPopup mDoorHangerPopup; 1.191 + protected FormAssistPopup mFormAssistPopup; 1.192 + protected ButtonToast mToast; 1.193 + 1.194 + protected LayerView mLayerView; 1.195 + private AbsoluteLayout mPluginContainer; 1.196 + 1.197 + private FullScreenHolder mFullScreenPluginContainer; 1.198 + private View mFullScreenPluginView; 1.199 + 1.200 + private HashMap<String, PowerManager.WakeLock> mWakeLocks = new HashMap<String, PowerManager.WakeLock>(); 1.201 + 1.202 + protected boolean mShouldRestore; 1.203 + protected boolean mInitialized = false; 1.204 + private Telemetry.Timer mJavaUiStartupTimer; 1.205 + private Telemetry.Timer mGeckoReadyStartupTimer; 1.206 + 1.207 + private String mPrivateBrowsingSession; 1.208 + 1.209 + private volatile HealthRecorder mHealthRecorder = null; 1.210 + 1.211 + private int mSignalStrenth; 1.212 + private PhoneStateListener mPhoneStateListener = null; 1.213 + private boolean mShouldReportGeoData; 1.214 + private EventListener mWebappEventListener; 1.215 + 1.216 + abstract public int getLayout(); 1.217 + abstract public boolean hasTabsSideBar(); 1.218 + abstract protected String getDefaultProfileName() throws NoMozillaDirectoryException; 1.219 + 1.220 + private static final String RESTARTER_ACTION = "org.mozilla.gecko.restart"; 1.221 + private static final String RESTARTER_CLASS = "org.mozilla.gecko.Restarter"; 1.222 + 1.223 + @SuppressWarnings("serial") 1.224 + class SessionRestoreException extends Exception { 1.225 + public SessionRestoreException(Exception e) { 1.226 + super(e); 1.227 + } 1.228 + 1.229 + public SessionRestoreException(String message) { 1.230 + super(message); 1.231 + } 1.232 + } 1.233 + 1.234 + void toggleChrome(final boolean aShow) { } 1.235 + 1.236 + void focusChrome() { } 1.237 + 1.238 + @Override 1.239 + public Context getContext() { 1.240 + return this; 1.241 + } 1.242 + 1.243 + @Override 1.244 + public SharedPreferences getSharedPreferences() { 1.245 + return GeckoSharedPrefs.forApp(this); 1.246 + } 1.247 + 1.248 + public Activity getActivity() { 1.249 + return this; 1.250 + } 1.251 + 1.252 + public LocationListener getLocationListener() { 1.253 + if (mShouldReportGeoData && mPhoneStateListener == null) { 1.254 + mPhoneStateListener = new PhoneStateListener() { 1.255 + public void onSignalStrengthsChanged(SignalStrength signalStrength) { 1.256 + setCurrentSignalStrenth(signalStrength); 1.257 + } 1.258 + }; 1.259 + TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE); 1.260 + tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS); 1.261 + } 1.262 + return this; 1.263 + } 1.264 + 1.265 + public SensorEventListener getSensorEventListener() { 1.266 + return this; 1.267 + } 1.268 + 1.269 + public View getCameraView() { 1.270 + return mCameraView; 1.271 + } 1.272 + 1.273 + public void addAppStateListener(GeckoAppShell.AppStateListener listener) { 1.274 + mAppStateListeners.add(listener); 1.275 + } 1.276 + 1.277 + public void removeAppStateListener(GeckoAppShell.AppStateListener listener) { 1.278 + mAppStateListeners.remove(listener); 1.279 + } 1.280 + 1.281 + public FormAssistPopup getFormAssistPopup() { 1.282 + return mFormAssistPopup; 1.283 + } 1.284 + 1.285 + @Override 1.286 + public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) { 1.287 + // When a tab is closed, it is always unselected first. 1.288 + // When a tab is unselected, another tab is always selected first. 1.289 + switch(msg) { 1.290 + case UNSELECTED: 1.291 + hidePlugins(tab); 1.292 + break; 1.293 + 1.294 + case LOCATION_CHANGE: 1.295 + // We only care about location change for the selected tab. 1.296 + if (!Tabs.getInstance().isSelectedTab(tab)) 1.297 + break; 1.298 + // Fall through... 1.299 + case SELECTED: 1.300 + invalidateOptionsMenu(); 1.301 + if (mFormAssistPopup != null) 1.302 + mFormAssistPopup.hide(); 1.303 + break; 1.304 + 1.305 + case LOADED: 1.306 + // Sync up the layer view and the tab if the tab is 1.307 + // currently displayed. 1.308 + LayerView layerView = mLayerView; 1.309 + if (layerView != null && Tabs.getInstance().isSelectedTab(tab)) 1.310 + layerView.setBackgroundColor(tab.getBackgroundColor()); 1.311 + break; 1.312 + 1.313 + case DESKTOP_MODE_CHANGE: 1.314 + if (Tabs.getInstance().isSelectedTab(tab)) 1.315 + invalidateOptionsMenu(); 1.316 + break; 1.317 + } 1.318 + } 1.319 + 1.320 + public void refreshChrome() { } 1.321 + 1.322 + @Override 1.323 + public void invalidateOptionsMenu() { 1.324 + if (mMenu == null) 1.325 + return; 1.326 + 1.327 + onPrepareOptionsMenu(mMenu); 1.328 + 1.329 + if (Build.VERSION.SDK_INT >= 11) 1.330 + super.invalidateOptionsMenu(); 1.331 + } 1.332 + 1.333 + @Override 1.334 + public boolean onCreateOptionsMenu(Menu menu) { 1.335 + mMenu = menu; 1.336 + 1.337 + MenuInflater inflater = getMenuInflater(); 1.338 + inflater.inflate(R.menu.gecko_app_menu, mMenu); 1.339 + return true; 1.340 + } 1.341 + 1.342 + @Override 1.343 + public MenuInflater getMenuInflater() { 1.344 + if (Build.VERSION.SDK_INT >= 11) 1.345 + return new GeckoMenuInflater(this); 1.346 + else 1.347 + return super.getMenuInflater(); 1.348 + } 1.349 + 1.350 + public MenuPanel getMenuPanel() { 1.351 + if (mMenuPanel == null) { 1.352 + onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, null); 1.353 + invalidateOptionsMenu(); 1.354 + } 1.355 + return mMenuPanel; 1.356 + } 1.357 + 1.358 + @Override 1.359 + public boolean onMenuItemSelected(MenuItem item) { 1.360 + return onOptionsItemSelected(item); 1.361 + } 1.362 + 1.363 + @Override 1.364 + public void openMenu() { 1.365 + openOptionsMenu(); 1.366 + } 1.367 + 1.368 + @Override 1.369 + public void showMenu(final View menu) { 1.370 + // On devices using the custom menu, focus is cleared from the menu when its tapped. 1.371 + // Close and then reshow it to avoid these issues. See bug 794581 and bug 968182. 1.372 + closeMenu(); 1.373 + 1.374 + // Post the reshow code back to the UI thread to avoid some optimizations Android 1.375 + // has put in place for menus that hide/show themselves quickly. See bug 985400. 1.376 + ThreadUtils.postToUiThread(new Runnable() { 1.377 + @Override 1.378 + public void run() { 1.379 + mMenuPanel.removeAllViews(); 1.380 + mMenuPanel.addView(menu); 1.381 + openOptionsMenu(); 1.382 + } 1.383 + }); 1.384 + } 1.385 + 1.386 + @Override 1.387 + public void closeMenu() { 1.388 + closeOptionsMenu(); 1.389 + } 1.390 + 1.391 + @Override 1.392 + public View onCreatePanelView(int featureId) { 1.393 + if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL) { 1.394 + if (mMenuPanel == null) { 1.395 + mMenuPanel = new MenuPanel(this, null); 1.396 + } else { 1.397 + // Prepare the panel everytime before showing the menu. 1.398 + onPreparePanel(featureId, mMenuPanel, mMenu); 1.399 + } 1.400 + 1.401 + return mMenuPanel; 1.402 + } 1.403 + 1.404 + return super.onCreatePanelView(featureId); 1.405 + } 1.406 + 1.407 + @Override 1.408 + public boolean onCreatePanelMenu(int featureId, Menu menu) { 1.409 + if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL) { 1.410 + if (mMenuPanel == null) { 1.411 + mMenuPanel = (MenuPanel) onCreatePanelView(featureId); 1.412 + } 1.413 + 1.414 + GeckoMenu gMenu = new GeckoMenu(this, null); 1.415 + gMenu.setCallback(this); 1.416 + gMenu.setMenuPresenter(this); 1.417 + menu = gMenu; 1.418 + mMenuPanel.addView(gMenu); 1.419 + 1.420 + return onCreateOptionsMenu(menu); 1.421 + } 1.422 + 1.423 + return super.onCreatePanelMenu(featureId, menu); 1.424 + } 1.425 + 1.426 + @Override 1.427 + public boolean onPreparePanel(int featureId, View view, Menu menu) { 1.428 + if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL) 1.429 + return onPrepareOptionsMenu(menu); 1.430 + 1.431 + return super.onPreparePanel(featureId, view, menu); 1.432 + } 1.433 + 1.434 + @Override 1.435 + public boolean onMenuOpened(int featureId, Menu menu) { 1.436 + // exit full-screen mode whenever the menu is opened 1.437 + if (mLayerView != null && mLayerView.isFullScreen()) { 1.438 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FullScreen:Exit", null)); 1.439 + } 1.440 + 1.441 + if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL) { 1.442 + if (mMenu == null) { 1.443 + // getMenuPanel() will force the creation of the menu as well 1.444 + MenuPanel panel = getMenuPanel(); 1.445 + onPreparePanel(featureId, panel, mMenu); 1.446 + } 1.447 + 1.448 + // Scroll custom menu to the top 1.449 + if (mMenuPanel != null) 1.450 + mMenuPanel.scrollTo(0, 0); 1.451 + 1.452 + return true; 1.453 + } 1.454 + 1.455 + return super.onMenuOpened(featureId, menu); 1.456 + } 1.457 + 1.458 + @Override 1.459 + public boolean onOptionsItemSelected(MenuItem item) { 1.460 + if (item.getItemId() == R.id.quit) { 1.461 + if (GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.GeckoRunning, GeckoThread.LaunchState.GeckoExiting)) { 1.462 + GeckoAppShell.notifyGeckoOfEvent(GeckoEvent.createBroadcastEvent("Browser:Quit", null)); 1.463 + } else { 1.464 + GeckoAppShell.systemExit(); 1.465 + } 1.466 + return true; 1.467 + } 1.468 + 1.469 + return super.onOptionsItemSelected(item); 1.470 + } 1.471 + 1.472 + @Override 1.473 + public void onOptionsMenuClosed(Menu menu) { 1.474 + if (Build.VERSION.SDK_INT >= 11) { 1.475 + mMenuPanel.removeAllViews(); 1.476 + mMenuPanel.addView((GeckoMenu) mMenu); 1.477 + } 1.478 + } 1.479 + 1.480 + @Override 1.481 + public boolean onKeyDown(int keyCode, KeyEvent event) { 1.482 + // Handle hardware menu key presses separately so that we can show a custom menu in some cases. 1.483 + if (keyCode == KeyEvent.KEYCODE_MENU) { 1.484 + openOptionsMenu(); 1.485 + return true; 1.486 + } 1.487 + 1.488 + return super.onKeyDown(keyCode, event); 1.489 + } 1.490 + 1.491 + @Override 1.492 + protected void onSaveInstanceState(Bundle outState) { 1.493 + super.onSaveInstanceState(outState); 1.494 + 1.495 + if (mToast != null) { 1.496 + mToast.onSaveInstanceState(outState); 1.497 + } 1.498 + 1.499 + outState.putBoolean(SAVED_STATE_IN_BACKGROUND, isApplicationInBackground()); 1.500 + outState.putString(SAVED_STATE_PRIVATE_SESSION, mPrivateBrowsingSession); 1.501 + } 1.502 + 1.503 + void handleFaviconRequest(final String url) { 1.504 + (new UiAsyncTask<Void, Void, String>(ThreadUtils.getBackgroundHandler()) { 1.505 + @Override 1.506 + public String doInBackground(Void... params) { 1.507 + return Favicons.getFaviconURLForPageURL(url); 1.508 + } 1.509 + 1.510 + @Override 1.511 + public void onPostExecute(String faviconUrl) { 1.512 + JSONObject args = new JSONObject(); 1.513 + 1.514 + if (faviconUrl != null) { 1.515 + try { 1.516 + args.put("url", url); 1.517 + args.put("faviconUrl", faviconUrl); 1.518 + } catch (JSONException e) { 1.519 + Log.w(LOGTAG, "Error building JSON favicon arguments.", e); 1.520 + } 1.521 + } 1.522 + 1.523 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:FaviconReturn", args.toString())); 1.524 + } 1.525 + }).execute(); 1.526 + } 1.527 + 1.528 + void handleClearHistory() { 1.529 + BrowserDB.clearHistory(getContentResolver()); 1.530 + } 1.531 + 1.532 + public void addTab() { } 1.533 + 1.534 + public void addPrivateTab() { } 1.535 + 1.536 + public void showNormalTabs() { } 1.537 + 1.538 + public void showPrivateTabs() { } 1.539 + 1.540 + public void hideTabs() { } 1.541 + 1.542 + /** 1.543 + * Close the tab UI indirectly (not as the result of a direct user 1.544 + * action). This does not force the UI to close; for example in Firefox 1.545 + * tablet mode it will remain open unless the user explicitly closes it. 1.546 + * 1.547 + * @return True if the tab UI was hidden. 1.548 + */ 1.549 + public boolean autoHideTabs() { return false; } 1.550 + 1.551 + public boolean areTabsShown() { return false; } 1.552 + 1.553 + @Override 1.554 + public void handleMessage(String event, JSONObject message) { 1.555 + try { 1.556 + if (event.equals("Toast:Show")) { 1.557 + final String msg = message.getString("message"); 1.558 + final JSONObject button = message.optJSONObject("button"); 1.559 + if (button != null) { 1.560 + final String label = button.optString("label"); 1.561 + final String icon = button.optString("icon"); 1.562 + final String id = button.optString("id"); 1.563 + showButtonToast(msg, label, icon, id); 1.564 + } else { 1.565 + final String duration = message.getString("duration"); 1.566 + showNormalToast(msg, duration); 1.567 + } 1.568 + } else if (event.equals("log")) { 1.569 + // generic log listener 1.570 + final String msg = message.getString("msg"); 1.571 + Log.d(LOGTAG, "Log: " + msg); 1.572 + } else if (event.equals("Reader:FaviconRequest")) { 1.573 + final String url = message.getString("url"); 1.574 + handleFaviconRequest(url); 1.575 + } else if (event.equals("Gecko:DelayedStartup")) { 1.576 + ThreadUtils.postToBackgroundThread(new UninstallListener.DelayedStartupTask(this)); 1.577 + } else if (event.equals("Gecko:Ready")) { 1.578 + mGeckoReadyStartupTimer.stop(); 1.579 + geckoConnected(); 1.580 + 1.581 + // This method is already running on the background thread, so we 1.582 + // know that mHealthRecorder will exist. That doesn't stop us being 1.583 + // paranoid. 1.584 + // This method is cheap, so don't spawn a new runnable. 1.585 + final HealthRecorder rec = mHealthRecorder; 1.586 + if (rec != null) { 1.587 + rec.recordGeckoStartupTime(mGeckoReadyStartupTimer.getElapsed()); 1.588 + } 1.589 + } else if (event.equals("ToggleChrome:Hide")) { 1.590 + toggleChrome(false); 1.591 + } else if (event.equals("ToggleChrome:Show")) { 1.592 + toggleChrome(true); 1.593 + } else if (event.equals("ToggleChrome:Focus")) { 1.594 + focusChrome(); 1.595 + } else if (event.equals("DOMFullScreen:Start")) { 1.596 + // Local ref to layerView for thread safety 1.597 + LayerView layerView = mLayerView; 1.598 + if (layerView != null) { 1.599 + layerView.setFullScreen(true); 1.600 + } 1.601 + } else if (event.equals("DOMFullScreen:Stop")) { 1.602 + // Local ref to layerView for thread safety 1.603 + LayerView layerView = mLayerView; 1.604 + if (layerView != null) { 1.605 + layerView.setFullScreen(false); 1.606 + } 1.607 + } else if (event.equals("Permissions:Data")) { 1.608 + String host = message.getString("host"); 1.609 + JSONArray permissions = message.getJSONArray("permissions"); 1.610 + showSiteSettingsDialog(host, permissions); 1.611 + } else if (event.equals("Session:StatePurged")) { 1.612 + onStatePurged(); 1.613 + } else if (event.equals("Bookmark:Insert")) { 1.614 + final String url = message.getString("url"); 1.615 + final String title = message.getString("title"); 1.616 + final Context context = this; 1.617 + ThreadUtils.postToUiThread(new Runnable() { 1.618 + @Override 1.619 + public void run() { 1.620 + Toast.makeText(context, R.string.bookmark_added, Toast.LENGTH_SHORT).show(); 1.621 + ThreadUtils.postToBackgroundThread(new Runnable() { 1.622 + @Override 1.623 + public void run() { 1.624 + BrowserDB.addBookmark(getContentResolver(), title, url); 1.625 + } 1.626 + }); 1.627 + } 1.628 + }); 1.629 + } else if (event.equals("Accessibility:Event")) { 1.630 + GeckoAccessibility.sendAccessibilityEvent(message); 1.631 + } else if (event.equals("Accessibility:Ready")) { 1.632 + GeckoAccessibility.updateAccessibilitySettings(this); 1.633 + } else if (event.equals("Shortcut:Remove")) { 1.634 + final String url = message.getString("url"); 1.635 + final String origin = message.getString("origin"); 1.636 + final String title = message.getString("title"); 1.637 + final String type = message.getString("shortcutType"); 1.638 + GeckoAppShell.removeShortcut(title, url, origin, type); 1.639 + } else if (event.equals("Share:Text")) { 1.640 + String text = message.getString("text"); 1.641 + GeckoAppShell.openUriExternal(text, "text/plain", "", "", Intent.ACTION_SEND, ""); 1.642 + 1.643 + // Context: Sharing via chrome list (no explicit session is active) 1.644 + Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST); 1.645 + } else if (event.equals("Image:SetAs")) { 1.646 + String src = message.getString("url"); 1.647 + setImageAs(src); 1.648 + } else if (event.equals("Sanitize:ClearHistory")) { 1.649 + handleClearHistory(); 1.650 + } else if (event.equals("Update:Check")) { 1.651 + startService(new Intent(UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE, null, this, UpdateService.class)); 1.652 + } else if (event.equals("Update:Download")) { 1.653 + startService(new Intent(UpdateServiceHelper.ACTION_DOWNLOAD_UPDATE, null, this, UpdateService.class)); 1.654 + } else if (event.equals("Update:Install")) { 1.655 + startService(new Intent(UpdateServiceHelper.ACTION_APPLY_UPDATE, null, this, UpdateService.class)); 1.656 + } else if (event.equals("PrivateBrowsing:Data")) { 1.657 + // null strings return "null" (http://code.google.com/p/android/issues/detail?id=13830) 1.658 + if (message.isNull("session")) { 1.659 + mPrivateBrowsingSession = null; 1.660 + } else { 1.661 + mPrivateBrowsingSession = message.getString("session"); 1.662 + } 1.663 + } else if (event.equals("Contact:Add")) { 1.664 + if (!message.isNull("email")) { 1.665 + Uri contactUri = Uri.parse(message.getString("email")); 1.666 + Intent i = new Intent(ContactsContract.Intents.SHOW_OR_CREATE_CONTACT, contactUri); 1.667 + startActivity(i); 1.668 + } else if (!message.isNull("phone")) { 1.669 + Uri contactUri = Uri.parse(message.getString("phone")); 1.670 + Intent i = new Intent(ContactsContract.Intents.SHOW_OR_CREATE_CONTACT, contactUri); 1.671 + startActivity(i); 1.672 + } else { 1.673 + // something went wrong. 1.674 + Log.e(LOGTAG, "Received Contact:Add message with no email nor phone number"); 1.675 + } 1.676 + } else if (event.equals("Intent:GetHandlers")) { 1.677 + Intent intent = GeckoAppShell.getOpenURIIntent((Context) this, message.optString("url"), 1.678 + message.optString("mime"), message.optString("action"), message.optString("title")); 1.679 + String[] handlers = GeckoAppShell.getHandlersForIntent(intent); 1.680 + List<String> appList = Arrays.asList(handlers); 1.681 + JSONObject handlersJSON = new JSONObject(); 1.682 + handlersJSON.put("apps", new JSONArray(appList)); 1.683 + EventDispatcher.sendResponse(message, handlersJSON); 1.684 + } else if (event.equals("Intent:Open")) { 1.685 + GeckoAppShell.openUriExternal(message.optString("url"), 1.686 + message.optString("mime"), message.optString("packageName"), 1.687 + message.optString("className"), message.optString("action"), message.optString("title")); 1.688 + } else if (event.equals("Intent:OpenForResult")) { 1.689 + Intent intent = GeckoAppShell.getOpenURIIntent(this, 1.690 + message.optString("url"), 1.691 + message.optString("mime"), 1.692 + message.optString("action"), 1.693 + message.optString("title")); 1.694 + intent.setClassName(message.optString("packageName"), message.optString("className")); 1.695 + 1.696 + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 1.697 + 1.698 + final JSONObject originalMessage = message; 1.699 + ActivityHandlerHelper.startIntentForActivity(this, 1.700 + intent, 1.701 + new ActivityResultHandler() { 1.702 + @Override 1.703 + public void onActivityResult (int resultCode, Intent data) { 1.704 + JSONObject response = new JSONObject(); 1.705 + 1.706 + try { 1.707 + if (data != null) { 1.708 + response.put("extras", bundleToJSON(data.getExtras())); 1.709 + } 1.710 + response.put("resultCode", resultCode); 1.711 + } catch (JSONException e) { 1.712 + Log.w(LOGTAG, "Error building JSON response.", e); 1.713 + } 1.714 + 1.715 + EventDispatcher.sendResponse(originalMessage, response); 1.716 + } 1.717 + }); 1.718 + } else if (event.equals("Locale:Set")) { 1.719 + setLocale(message.getString("locale")); 1.720 + } else if (event.equals("NativeApp:IsDebuggable")) { 1.721 + JSONObject ret = new JSONObject(); 1.722 + ret.put("isDebuggable", getIsDebuggable()); 1.723 + EventDispatcher.sendResponse(message, ret); 1.724 + } else if (event.equals("SystemUI:Visibility")) { 1.725 + setSystemUiVisible(message.getBoolean("visible")); 1.726 + } 1.727 + } catch (Exception e) { 1.728 + Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e); 1.729 + } 1.730 + } 1.731 + 1.732 + void onStatePurged() { } 1.733 + 1.734 + /** 1.735 + * @param aPermissions 1.736 + * Array of JSON objects to represent site permissions. 1.737 + * Example: { type: "offline-app", setting: "Store Offline Data", value: "Allow" } 1.738 + */ 1.739 + private void showSiteSettingsDialog(String aHost, JSONArray aPermissions) { 1.740 + final AlertDialog.Builder builder = new AlertDialog.Builder(this); 1.741 + 1.742 + View customTitleView = getLayoutInflater().inflate(R.layout.site_setting_title, null); 1.743 + ((TextView) customTitleView.findViewById(R.id.title)).setText(R.string.site_settings_title); 1.744 + ((TextView) customTitleView.findViewById(R.id.host)).setText(aHost); 1.745 + builder.setCustomTitle(customTitleView); 1.746 + 1.747 + // If there are no permissions to clear, show the user a message about that. 1.748 + // In the future, we want to disable the menu item if there are no permissions to clear. 1.749 + if (aPermissions.length() == 0) { 1.750 + builder.setMessage(R.string.site_settings_no_settings); 1.751 + } else { 1.752 + 1.753 + ArrayList <HashMap<String, String>> itemList = new ArrayList <HashMap<String, String>>(); 1.754 + for (int i = 0; i < aPermissions.length(); i++) { 1.755 + try { 1.756 + JSONObject permObj = aPermissions.getJSONObject(i); 1.757 + HashMap<String, String> map = new HashMap<String, String>(); 1.758 + map.put("setting", permObj.getString("setting")); 1.759 + map.put("value", permObj.getString("value")); 1.760 + itemList.add(map); 1.761 + } catch (JSONException e) { 1.762 + Log.w(LOGTAG, "Exception populating settings items.", e); 1.763 + } 1.764 + } 1.765 + 1.766 + // setMultiChoiceItems doesn't support using an adapter, so we're creating a hack with 1.767 + // setSingleChoiceItems and changing the choiceMode below when we create the dialog 1.768 + builder.setSingleChoiceItems(new SimpleAdapter( 1.769 + GeckoApp.this, 1.770 + itemList, 1.771 + R.layout.site_setting_item, 1.772 + new String[] { "setting", "value" }, 1.773 + new int[] { R.id.setting, R.id.value } 1.774 + ), -1, new DialogInterface.OnClickListener() { 1.775 + @Override 1.776 + public void onClick(DialogInterface dialog, int id) { } 1.777 + }); 1.778 + 1.779 + builder.setPositiveButton(R.string.site_settings_clear, new DialogInterface.OnClickListener() { 1.780 + @Override 1.781 + public void onClick(DialogInterface dialog, int id) { 1.782 + ListView listView = ((AlertDialog) dialog).getListView(); 1.783 + SparseBooleanArray checkedItemPositions = listView.getCheckedItemPositions(); 1.784 + 1.785 + // An array of the indices of the permissions we want to clear 1.786 + JSONArray permissionsToClear = new JSONArray(); 1.787 + for (int i = 0; i < checkedItemPositions.size(); i++) 1.788 + if (checkedItemPositions.get(i)) 1.789 + permissionsToClear.put(i); 1.790 + 1.791 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent( 1.792 + "Permissions:Clear", permissionsToClear.toString())); 1.793 + } 1.794 + }); 1.795 + } 1.796 + 1.797 + builder.setNegativeButton(R.string.site_settings_cancel, new DialogInterface.OnClickListener(){ 1.798 + @Override 1.799 + public void onClick(DialogInterface dialog, int id) { 1.800 + dialog.cancel(); 1.801 + } 1.802 + }); 1.803 + 1.804 + ThreadUtils.postToUiThread(new Runnable() { 1.805 + @Override 1.806 + public void run() { 1.807 + Dialog dialog = builder.create(); 1.808 + dialog.show(); 1.809 + 1.810 + ListView listView = ((AlertDialog) dialog).getListView(); 1.811 + if (listView != null) { 1.812 + listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 1.813 + int listSize = listView.getAdapter().getCount(); 1.814 + for (int i = 0; i < listSize; i++) 1.815 + listView.setItemChecked(i, true); 1.816 + } 1.817 + } 1.818 + }); 1.819 + } 1.820 + 1.821 + public void showToast(final int resId, final int duration) { 1.822 + ThreadUtils.postToUiThread(new Runnable() { 1.823 + @Override 1.824 + public void run() { 1.825 + Toast.makeText(GeckoApp.this, resId, duration).show(); 1.826 + } 1.827 + }); 1.828 + } 1.829 + 1.830 + public void showNormalToast(final String message, final String duration) { 1.831 + ThreadUtils.postToUiThread(new Runnable() { 1.832 + @Override 1.833 + public void run() { 1.834 + Toast toast; 1.835 + if (duration.equals("long")) { 1.836 + toast = Toast.makeText(GeckoApp.this, message, Toast.LENGTH_LONG); 1.837 + } else { 1.838 + toast = Toast.makeText(GeckoApp.this, message, Toast.LENGTH_SHORT); 1.839 + } 1.840 + toast.show(); 1.841 + } 1.842 + }); 1.843 + } 1.844 + 1.845 + protected ButtonToast getButtonToast() { 1.846 + if (mToast != null) { 1.847 + return mToast; 1.848 + } 1.849 + 1.850 + ViewStub toastStub = (ViewStub) findViewById(R.id.toast_stub); 1.851 + mToast = new ButtonToast(toastStub.inflate()); 1.852 + 1.853 + return mToast; 1.854 + } 1.855 + 1.856 + void showButtonToast(final String message, final String buttonText, 1.857 + final String buttonIcon, final String buttonId) { 1.858 + BitmapUtils.getDrawable(GeckoApp.this, buttonIcon, new BitmapUtils.BitmapLoader() { 1.859 + @Override 1.860 + public void onBitmapFound(final Drawable d) { 1.861 + getButtonToast().show(false, message, buttonText, d, new ButtonToast.ToastListener() { 1.862 + @Override 1.863 + public void onButtonClicked() { 1.864 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Toast:Click", buttonId)); 1.865 + } 1.866 + 1.867 + @Override 1.868 + public void onToastHidden(ButtonToast.ReasonHidden reason) { 1.869 + if (reason == ButtonToast.ReasonHidden.TIMEOUT) { 1.870 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Toast:Hidden", buttonId)); 1.871 + } 1.872 + } 1.873 + }); 1.874 + } 1.875 + }); 1.876 + } 1.877 + 1.878 + private JSONObject bundleToJSON(Bundle bundle) { 1.879 + JSONObject json = new JSONObject(); 1.880 + if (bundle == null) { 1.881 + return json; 1.882 + } 1.883 + 1.884 + for (String key : bundle.keySet()) { 1.885 + try { 1.886 + json.put(key, bundle.get(key)); 1.887 + } catch (JSONException e) { 1.888 + Log.w(LOGTAG, "Error building JSON response.", e); 1.889 + } 1.890 + } 1.891 + 1.892 + return json; 1.893 + } 1.894 + 1.895 + private void addFullScreenPluginView(View view) { 1.896 + if (mFullScreenPluginView != null) { 1.897 + Log.w(LOGTAG, "Already have a fullscreen plugin view"); 1.898 + return; 1.899 + } 1.900 + 1.901 + setFullScreen(true); 1.902 + 1.903 + view.setWillNotDraw(false); 1.904 + if (view instanceof SurfaceView) { 1.905 + ((SurfaceView) view).setZOrderOnTop(true); 1.906 + } 1.907 + 1.908 + mFullScreenPluginContainer = new FullScreenHolder(this); 1.909 + 1.910 + FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams( 1.911 + ViewGroup.LayoutParams.FILL_PARENT, 1.912 + ViewGroup.LayoutParams.FILL_PARENT, 1.913 + Gravity.CENTER); 1.914 + mFullScreenPluginContainer.addView(view, layoutParams); 1.915 + 1.916 + 1.917 + FrameLayout decor = (FrameLayout)getWindow().getDecorView(); 1.918 + decor.addView(mFullScreenPluginContainer, layoutParams); 1.919 + 1.920 + mFullScreenPluginView = view; 1.921 + } 1.922 + 1.923 + public void addPluginView(final View view, final RectF rect, final boolean isFullScreen) { 1.924 + ThreadUtils.postToUiThread(new Runnable() { 1.925 + @Override 1.926 + public void run() { 1.927 + Tabs tabs = Tabs.getInstance(); 1.928 + Tab tab = tabs.getSelectedTab(); 1.929 + 1.930 + if (isFullScreen) { 1.931 + addFullScreenPluginView(view); 1.932 + return; 1.933 + } 1.934 + 1.935 + PluginLayer layer = (PluginLayer) tab.getPluginLayer(view); 1.936 + if (layer == null) { 1.937 + layer = new PluginLayer(view, rect, mLayerView.getRenderer().getMaxTextureSize()); 1.938 + tab.addPluginLayer(view, layer); 1.939 + } else { 1.940 + layer.reset(rect); 1.941 + layer.setVisible(true); 1.942 + } 1.943 + 1.944 + mLayerView.addLayer(layer); 1.945 + } 1.946 + }); 1.947 + } 1.948 + 1.949 + private void removeFullScreenPluginView(View view) { 1.950 + if (mFullScreenPluginView == null) { 1.951 + Log.w(LOGTAG, "Don't have a fullscreen plugin view"); 1.952 + return; 1.953 + } 1.954 + 1.955 + if (mFullScreenPluginView != view) { 1.956 + Log.w(LOGTAG, "Passed view is not the current full screen view"); 1.957 + return; 1.958 + } 1.959 + 1.960 + mFullScreenPluginContainer.removeView(mFullScreenPluginView); 1.961 + 1.962 + // We need do do this on the next iteration in order to avoid 1.963 + // a deadlock, see comment below in FullScreenHolder 1.964 + ThreadUtils.postToUiThread(new Runnable() { 1.965 + @Override 1.966 + public void run() { 1.967 + mLayerView.showSurface(); 1.968 + } 1.969 + }); 1.970 + 1.971 + FrameLayout decor = (FrameLayout)getWindow().getDecorView(); 1.972 + decor.removeView(mFullScreenPluginContainer); 1.973 + 1.974 + mFullScreenPluginView = null; 1.975 + 1.976 + GeckoScreenOrientation.getInstance().unlock(); 1.977 + setFullScreen(false); 1.978 + } 1.979 + 1.980 + public void removePluginView(final View view, final boolean isFullScreen) { 1.981 + ThreadUtils.postToUiThread(new Runnable() { 1.982 + @Override 1.983 + public void run() { 1.984 + Tabs tabs = Tabs.getInstance(); 1.985 + Tab tab = tabs.getSelectedTab(); 1.986 + 1.987 + if (isFullScreen) { 1.988 + removeFullScreenPluginView(view); 1.989 + return; 1.990 + } 1.991 + 1.992 + PluginLayer layer = (PluginLayer) tab.removePluginLayer(view); 1.993 + if (layer != null) { 1.994 + layer.destroy(); 1.995 + } 1.996 + } 1.997 + }); 1.998 + } 1.999 + 1.1000 + // This method starts downloading an image synchronously and displays the Chooser activity to set the image as wallpaper. 1.1001 + private void setImageAs(final String aSrc) { 1.1002 + boolean isDataURI = aSrc.startsWith("data:"); 1.1003 + Bitmap image = null; 1.1004 + InputStream is = null; 1.1005 + ByteArrayOutputStream os = null; 1.1006 + try { 1.1007 + if (isDataURI) { 1.1008 + int dataStart = aSrc.indexOf(","); 1.1009 + byte[] buf = Base64.decode(aSrc.substring(dataStart+1), Base64.DEFAULT); 1.1010 + image = BitmapUtils.decodeByteArray(buf); 1.1011 + } else { 1.1012 + int byteRead; 1.1013 + byte[] buf = new byte[4192]; 1.1014 + os = new ByteArrayOutputStream(); 1.1015 + URL url = new URL(aSrc); 1.1016 + is = url.openStream(); 1.1017 + 1.1018 + // Cannot read from same stream twice. Also, InputStream from 1.1019 + // URL does not support reset. So converting to byte array. 1.1020 + 1.1021 + while((byteRead = is.read(buf)) != -1) { 1.1022 + os.write(buf, 0, byteRead); 1.1023 + } 1.1024 + byte[] imgBuffer = os.toByteArray(); 1.1025 + image = BitmapUtils.decodeByteArray(imgBuffer); 1.1026 + } 1.1027 + if (image != null) { 1.1028 + String path = Media.insertImage(getContentResolver(),image, null, null); 1.1029 + final Intent intent = new Intent(Intent.ACTION_ATTACH_DATA); 1.1030 + intent.addCategory(Intent.CATEGORY_DEFAULT); 1.1031 + intent.setData(Uri.parse(path)); 1.1032 + 1.1033 + // Removes the image from storage once the chooser activity ends. 1.1034 + Intent chooser = Intent.createChooser(intent, getString(R.string.set_image_chooser_title)); 1.1035 + ActivityResultHandler handler = new ActivityResultHandler() { 1.1036 + @Override 1.1037 + public void onActivityResult (int resultCode, Intent data) { 1.1038 + getContentResolver().delete(intent.getData(), null, null); 1.1039 + } 1.1040 + }; 1.1041 + ActivityHandlerHelper.startIntentForActivity(this, chooser, handler); 1.1042 + } else { 1.1043 + Toast.makeText((Context) this, R.string.set_image_fail, Toast.LENGTH_SHORT).show(); 1.1044 + } 1.1045 + } catch(OutOfMemoryError ome) { 1.1046 + Log.e(LOGTAG, "Out of Memory when converting to byte array", ome); 1.1047 + } catch(IOException ioe) { 1.1048 + Log.e(LOGTAG, "I/O Exception while setting wallpaper", ioe); 1.1049 + } finally { 1.1050 + if (is != null) { 1.1051 + try { 1.1052 + is.close(); 1.1053 + } catch(IOException ioe) { 1.1054 + Log.w(LOGTAG, "I/O Exception while closing stream", ioe); 1.1055 + } 1.1056 + } 1.1057 + if (os != null) { 1.1058 + try { 1.1059 + os.close(); 1.1060 + } catch(IOException ioe) { 1.1061 + Log.w(LOGTAG, "I/O Exception while closing stream", ioe); 1.1062 + } 1.1063 + } 1.1064 + } 1.1065 + } 1.1066 + 1.1067 + private int getBitmapSampleSize(BitmapFactory.Options options, int idealWidth, int idealHeight) { 1.1068 + int width = options.outWidth; 1.1069 + int height = options.outHeight; 1.1070 + int inSampleSize = 1; 1.1071 + if (height > idealHeight || width > idealWidth) { 1.1072 + if (width > height) { 1.1073 + inSampleSize = Math.round((float)height / (float)idealHeight); 1.1074 + } else { 1.1075 + inSampleSize = Math.round((float)width / (float)idealWidth); 1.1076 + } 1.1077 + } 1.1078 + return inSampleSize; 1.1079 + } 1.1080 + 1.1081 + private void hidePluginLayer(Layer layer) { 1.1082 + LayerView layerView = mLayerView; 1.1083 + layerView.removeLayer(layer); 1.1084 + layerView.requestRender(); 1.1085 + } 1.1086 + 1.1087 + private void showPluginLayer(Layer layer) { 1.1088 + LayerView layerView = mLayerView; 1.1089 + layerView.addLayer(layer); 1.1090 + layerView.requestRender(); 1.1091 + } 1.1092 + 1.1093 + public void requestRender() { 1.1094 + mLayerView.requestRender(); 1.1095 + } 1.1096 + 1.1097 + public void hidePlugins(Tab tab) { 1.1098 + for (Layer layer : tab.getPluginLayers()) { 1.1099 + if (layer instanceof PluginLayer) { 1.1100 + ((PluginLayer) layer).setVisible(false); 1.1101 + } 1.1102 + 1.1103 + hidePluginLayer(layer); 1.1104 + } 1.1105 + 1.1106 + requestRender(); 1.1107 + } 1.1108 + 1.1109 + public void showPlugins() { 1.1110 + Tabs tabs = Tabs.getInstance(); 1.1111 + Tab tab = tabs.getSelectedTab(); 1.1112 + 1.1113 + showPlugins(tab); 1.1114 + } 1.1115 + 1.1116 + public void showPlugins(Tab tab) { 1.1117 + for (Layer layer : tab.getPluginLayers()) { 1.1118 + showPluginLayer(layer); 1.1119 + 1.1120 + if (layer instanceof PluginLayer) { 1.1121 + ((PluginLayer) layer).setVisible(true); 1.1122 + } 1.1123 + } 1.1124 + 1.1125 + requestRender(); 1.1126 + } 1.1127 + 1.1128 + public void setFullScreen(final boolean fullscreen) { 1.1129 + ThreadUtils.postToUiThread(new Runnable() { 1.1130 + @Override 1.1131 + public void run() { 1.1132 + // Hide/show the system notification bar 1.1133 + Window window = getWindow(); 1.1134 + window.setFlags(fullscreen ? 1.1135 + WindowManager.LayoutParams.FLAG_FULLSCREEN : 0, 1.1136 + WindowManager.LayoutParams.FLAG_FULLSCREEN); 1.1137 + 1.1138 + if (Build.VERSION.SDK_INT >= 11) 1.1139 + window.getDecorView().setSystemUiVisibility(fullscreen ? 1 : 0); 1.1140 + } 1.1141 + }); 1.1142 + } 1.1143 + 1.1144 + /** 1.1145 + * Check and start the Java profiler if MOZ_PROFILER_STARTUP env var is specified 1.1146 + **/ 1.1147 + protected void earlyStartJavaSampler(Intent intent) 1.1148 + { 1.1149 + String env = intent.getStringExtra("env0"); 1.1150 + for (int i = 1; env != null; i++) { 1.1151 + if (env.startsWith("MOZ_PROFILER_STARTUP=")) { 1.1152 + if (!env.endsWith("=")) { 1.1153 + GeckoJavaSampler.start(10, 1000); 1.1154 + Log.d(LOGTAG, "Profiling Java on startup"); 1.1155 + } 1.1156 + break; 1.1157 + } 1.1158 + env = intent.getStringExtra("env" + i); 1.1159 + } 1.1160 + } 1.1161 + 1.1162 + /** 1.1163 + * Called when the activity is first created. 1.1164 + * 1.1165 + * Here we initialize all of our profile settings, Firefox Health Report, 1.1166 + * and other one-shot constructions. 1.1167 + **/ 1.1168 + @Override 1.1169 + public void onCreate(Bundle savedInstanceState) 1.1170 + { 1.1171 + GeckoAppShell.registerGlobalExceptionHandler(); 1.1172 + 1.1173 + // Enable Android Strict Mode for developers' local builds (the "default" channel). 1.1174 + if ("default".equals(AppConstants.MOZ_UPDATE_CHANNEL)) { 1.1175 + enableStrictMode(); 1.1176 + } 1.1177 + 1.1178 + // The clock starts...now. Better hurry! 1.1179 + mJavaUiStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_JAVAUI"); 1.1180 + mGeckoReadyStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_GECKOREADY"); 1.1181 + 1.1182 + final Intent intent = getIntent(); 1.1183 + final String args = intent.getStringExtra("args"); 1.1184 + 1.1185 + earlyStartJavaSampler(intent); 1.1186 + 1.1187 + // GeckoLoader wants to dig some environment variables out of the 1.1188 + // incoming intent, so pass it in here. GeckoLoader will do its 1.1189 + // business later and dispose of the reference. 1.1190 + GeckoLoader.setLastIntent(intent); 1.1191 + 1.1192 + if (mProfile == null) { 1.1193 + String profileName = null; 1.1194 + String profilePath = null; 1.1195 + if (args != null) { 1.1196 + if (args.contains("-P")) { 1.1197 + Pattern p = Pattern.compile("(?:-P\\s*)(\\w*)(\\s*)"); 1.1198 + Matcher m = p.matcher(args); 1.1199 + if (m.find()) { 1.1200 + profileName = m.group(1); 1.1201 + } 1.1202 + } 1.1203 + 1.1204 + if (args.contains("-profile")) { 1.1205 + Pattern p = Pattern.compile("(?:-profile\\s*)(\\S*)(\\s*)"); 1.1206 + Matcher m = p.matcher(args); 1.1207 + if (m.find()) { 1.1208 + profilePath = m.group(1); 1.1209 + } 1.1210 + if (profileName == null) { 1.1211 + try { 1.1212 + profileName = getDefaultProfileName(); 1.1213 + } catch (NoMozillaDirectoryException e) { 1.1214 + Log.wtf(LOGTAG, "Unable to fetch default profile name!", e); 1.1215 + // There's nothing at all we can do now. If the Mozilla directory 1.1216 + // didn't exist, then we're screwed. 1.1217 + // Crash here so we can fix the bug. 1.1218 + throw new RuntimeException(e); 1.1219 + } 1.1220 + if (profileName == null) 1.1221 + profileName = GeckoProfile.DEFAULT_PROFILE; 1.1222 + } 1.1223 + GeckoProfile.sIsUsingCustomProfile = true; 1.1224 + } 1.1225 + 1.1226 + if (profileName != null || profilePath != null) { 1.1227 + mProfile = GeckoProfile.get(this, profileName, profilePath); 1.1228 + } 1.1229 + } 1.1230 + } 1.1231 + 1.1232 + BrowserDB.initialize(getProfile().getName()); 1.1233 + 1.1234 + // Workaround for <http://code.google.com/p/android/issues/detail?id=20915>. 1.1235 + try { 1.1236 + Class.forName("android.os.AsyncTask"); 1.1237 + } catch (ClassNotFoundException e) {} 1.1238 + 1.1239 + MemoryMonitor.getInstance().init(getApplicationContext()); 1.1240 + 1.1241 + // GeckoAppShell is tightly coupled to us, rather than 1.1242 + // the app context, because various parts of Fennec (e.g., 1.1243 + // GeckoScreenOrientation) use GAS to access the Activity in 1.1244 + // the guise of fetching a Context. 1.1245 + // When that's fixed, `this` can change to 1.1246 + // `(GeckoApplication) getApplication()` here. 1.1247 + GeckoAppShell.setContextGetter(this); 1.1248 + GeckoAppShell.setGeckoInterface(this); 1.1249 + 1.1250 + ThreadUtils.setUiThread(Thread.currentThread(), new Handler()); 1.1251 + 1.1252 + Tabs.getInstance().attachToContext(this); 1.1253 + try { 1.1254 + Favicons.attachToContext(this); 1.1255 + } catch (Exception e) { 1.1256 + Log.e(LOGTAG, "Exception starting favicon cache. Corrupt resources?", e); 1.1257 + } 1.1258 + 1.1259 + // Did the OS locale change while we were backgrounded? If so, 1.1260 + // we need to die so that Gecko will re-init add-ons that touch 1.1261 + // the UI. 1.1262 + // This is using a sledgehammer to crack a nut, but it'll do for 1.1263 + // now. 1.1264 + if (BrowserLocaleManager.getInstance().systemLocaleDidChange()) { 1.1265 + Log.i(LOGTAG, "System locale changed. Restarting."); 1.1266 + doRestart(); 1.1267 + GeckoAppShell.systemExit(); 1.1268 + return; 1.1269 + } 1.1270 + 1.1271 + if (GeckoThread.isCreated()) { 1.1272 + // This happens when the GeckoApp activity is destroyed by Android 1.1273 + // without killing the entire application (see Bug 769269). 1.1274 + mIsRestoringActivity = true; 1.1275 + Telemetry.HistogramAdd("FENNEC_RESTORING_ACTIVITY", 1); 1.1276 + } 1.1277 + 1.1278 + // Fix for Bug 830557 on Tegra boards running Froyo. 1.1279 + // This fix must be done before doing layout. 1.1280 + // Assume the bug is fixed in Gingerbread and up. 1.1281 + if (Build.VERSION.SDK_INT < 9) { 1.1282 + try { 1.1283 + Class<?> inputBindResultClass = 1.1284 + Class.forName("com.android.internal.view.InputBindResult"); 1.1285 + java.lang.reflect.Field creatorField = 1.1286 + inputBindResultClass.getField("CREATOR"); 1.1287 + Log.i(LOGTAG, "froyo startup fix: " + String.valueOf(creatorField.get(null))); 1.1288 + } catch (Exception e) { 1.1289 + Log.w(LOGTAG, "froyo startup fix failed", e); 1.1290 + } 1.1291 + } 1.1292 + 1.1293 + Bundle stateBundle = getIntent().getBundleExtra(EXTRA_STATE_BUNDLE); 1.1294 + if (stateBundle != null) { 1.1295 + // Use the state bundle if it was given as an intent extra. This is 1.1296 + // only intended to be used internally via Robocop, so a boolean 1.1297 + // is read from a private shared pref to prevent other apps from 1.1298 + // injecting states. 1.1299 + final SharedPreferences prefs = getSharedPreferences(); 1.1300 + if (prefs.getBoolean(PREFS_ALLOW_STATE_BUNDLE, false)) { 1.1301 + Log.i(LOGTAG, "Restoring state from intent bundle"); 1.1302 + prefs.edit().remove(PREFS_ALLOW_STATE_BUNDLE).commit(); 1.1303 + savedInstanceState = stateBundle; 1.1304 + } 1.1305 + } else if (savedInstanceState != null) { 1.1306 + // Bug 896992 - This intent has already been handled; reset the intent. 1.1307 + setIntent(new Intent(Intent.ACTION_MAIN)); 1.1308 + } 1.1309 + 1.1310 + super.onCreate(savedInstanceState); 1.1311 + 1.1312 + GeckoScreenOrientation.getInstance().update(getResources().getConfiguration().orientation); 1.1313 + 1.1314 + setContentView(getLayout()); 1.1315 + 1.1316 + // Set up Gecko layout. 1.1317 + mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout); 1.1318 + mMainLayout = (RelativeLayout) findViewById(R.id.main_layout); 1.1319 + 1.1320 + // Determine whether we should restore tabs. 1.1321 + mShouldRestore = getSessionRestoreState(savedInstanceState); 1.1322 + if (mShouldRestore && savedInstanceState != null) { 1.1323 + boolean wasInBackground = 1.1324 + savedInstanceState.getBoolean(SAVED_STATE_IN_BACKGROUND, false); 1.1325 + 1.1326 + // Don't log OOM-kills if only one activity was destroyed. (For example 1.1327 + // from "Don't keep activities" on ICS) 1.1328 + if (!wasInBackground && !mIsRestoringActivity) { 1.1329 + Telemetry.HistogramAdd("FENNEC_WAS_KILLED", 1); 1.1330 + } 1.1331 + 1.1332 + mPrivateBrowsingSession = savedInstanceState.getString(SAVED_STATE_PRIVATE_SESSION); 1.1333 + } 1.1334 + 1.1335 + // Perform background initialization. 1.1336 + ThreadUtils.postToBackgroundThread(new Runnable() { 1.1337 + @Override 1.1338 + public void run() { 1.1339 + final SharedPreferences prefs = GeckoApp.this.getSharedPreferences(); 1.1340 + 1.1341 + // Wait until now to set this, because we'd rather throw an exception than 1.1342 + // have a caller of BrowserLocaleManager regress startup. 1.1343 + BrowserLocaleManager.getInstance().initialize(getApplicationContext()); 1.1344 + 1.1345 + SessionInformation previousSession = SessionInformation.fromSharedPrefs(prefs); 1.1346 + if (previousSession.wasKilled()) { 1.1347 + Telemetry.HistogramAdd("FENNEC_WAS_KILLED", 1); 1.1348 + } 1.1349 + 1.1350 + SharedPreferences.Editor editor = prefs.edit(); 1.1351 + editor.putBoolean(GeckoApp.PREFS_OOM_EXCEPTION, false); 1.1352 + 1.1353 + // Put a flag to check if we got a normal `onSaveInstanceState` 1.1354 + // on exit, or if we were suddenly killed (crash or native OOM). 1.1355 + editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false); 1.1356 + 1.1357 + editor.commit(); 1.1358 + 1.1359 + // The lifecycle of mHealthRecorder is "shortly after onCreate" 1.1360 + // through "onDestroy" -- essentially the same as the lifecycle 1.1361 + // of the activity itself. 1.1362 + final String profilePath = getProfile().getDir().getAbsolutePath(); 1.1363 + final EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher(); 1.1364 + Log.i(LOGTAG, "Creating HealthRecorder."); 1.1365 + 1.1366 + final String osLocale = Locale.getDefault().toString(); 1.1367 + String appLocale = BrowserLocaleManager.getInstance().getAndApplyPersistedLocale(GeckoApp.this); 1.1368 + Log.d(LOGTAG, "OS locale is " + osLocale + ", app locale is " + appLocale); 1.1369 + 1.1370 + if (appLocale == null) { 1.1371 + appLocale = osLocale; 1.1372 + } 1.1373 + 1.1374 + mHealthRecorder = GeckoApp.this.createHealthRecorder(GeckoApp.this, 1.1375 + profilePath, 1.1376 + dispatcher, 1.1377 + osLocale, 1.1378 + appLocale, 1.1379 + previousSession); 1.1380 + 1.1381 + final String uiLocale = appLocale; 1.1382 + ThreadUtils.postToUiThread(new Runnable() { 1.1383 + @Override 1.1384 + public void run() { 1.1385 + GeckoApp.this.onLocaleReady(uiLocale); 1.1386 + } 1.1387 + }); 1.1388 + } 1.1389 + }); 1.1390 + 1.1391 + GeckoAppShell.setNotificationClient(makeNotificationClient()); 1.1392 + NotificationHelper.init(getApplicationContext()); 1.1393 + } 1.1394 + 1.1395 + /** 1.1396 + * At this point, the resource system and the rest of the browser are 1.1397 + * aware of the locale. 1.1398 + * 1.1399 + * Now we can display strings! 1.1400 + * 1.1401 + * You can think of this as being something like a second phase of onCreate, 1.1402 + * where you can do string-related operations. Use this in place of embedding 1.1403 + * strings in view XML. 1.1404 + * 1.1405 + * By contrast, onConfigurationChanged does some locale operations, but is in 1.1406 + * response to device changes. 1.1407 + */ 1.1408 + @Override 1.1409 + public void onLocaleReady(final String locale) { 1.1410 + if (!ThreadUtils.isOnUiThread()) { 1.1411 + throw new RuntimeException("onLocaleReady must always be called from the UI thread."); 1.1412 + } 1.1413 + 1.1414 + // The URL bar hint needs to be populated. 1.1415 + TextView urlBar = (TextView) findViewById(R.id.url_bar_title); 1.1416 + if (urlBar != null) { 1.1417 + final String hint = getResources().getString(R.string.url_bar_default_text); 1.1418 + urlBar.setHint(hint); 1.1419 + } else { 1.1420 + Log.d(LOGTAG, "No URL bar in GeckoApp. Not loading localized hint string."); 1.1421 + } 1.1422 + 1.1423 + // Allow onConfigurationChanged to take care of the rest. 1.1424 + onConfigurationChanged(getResources().getConfiguration()); 1.1425 + } 1.1426 + 1.1427 + protected void initializeChrome() { 1.1428 + mDoorHangerPopup = new DoorHangerPopup(this); 1.1429 + mPluginContainer = (AbsoluteLayout) findViewById(R.id.plugin_container); 1.1430 + mFormAssistPopup = (FormAssistPopup) findViewById(R.id.form_assist_popup); 1.1431 + 1.1432 + if (mCameraView == null) { 1.1433 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 1.1434 + mCameraView = new SurfaceView(this); 1.1435 + ((SurfaceView)mCameraView).getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 1.1436 + } else { 1.1437 + mCameraView = new TextureView(this); 1.1438 + } 1.1439 + } 1.1440 + 1.1441 + if (mLayerView == null) { 1.1442 + LayerView layerView = (LayerView) findViewById(R.id.layer_view); 1.1443 + layerView.initializeView(GeckoAppShell.getEventDispatcher()); 1.1444 + mLayerView = layerView; 1.1445 + GeckoAppShell.setLayerView(layerView); 1.1446 + // bind the GeckoEditable instance to the new LayerView 1.1447 + GeckoAppShell.notifyIMEContext(GeckoEditableListener.IME_STATE_DISABLED, "", "", ""); 1.1448 + } 1.1449 + } 1.1450 + 1.1451 + /** 1.1452 + * Loads the initial tab at Fennec startup. 1.1453 + * 1.1454 + * If Fennec was opened with an external URL, that URL will be loaded. 1.1455 + * Otherwise, unless there was a session restore, the default URL 1.1456 + * (about:home) be loaded. 1.1457 + * 1.1458 + * @param url External URL to load, or null to load the default URL 1.1459 + */ 1.1460 + protected void loadStartupTab(String url) { 1.1461 + if (url == null) { 1.1462 + if (!mShouldRestore) { 1.1463 + // Show about:home if we aren't restoring previous session and 1.1464 + // there's no external URL. 1.1465 + Tabs.getInstance().loadUrl(AboutPages.HOME, Tabs.LOADURL_NEW_TAB); 1.1466 + } 1.1467 + } else { 1.1468 + // If given an external URL, load it 1.1469 + int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_USER_ENTERED | Tabs.LOADURL_EXTERNAL; 1.1470 + Tabs.getInstance().loadUrl(url, flags); 1.1471 + } 1.1472 + } 1.1473 + 1.1474 + private void initialize() { 1.1475 + mInitialized = true; 1.1476 + 1.1477 + Intent intent = getIntent(); 1.1478 + String action = intent.getAction(); 1.1479 + 1.1480 + String passedUri = null; 1.1481 + final String uri = getURIFromIntent(intent); 1.1482 + if (!TextUtils.isEmpty(uri)) { 1.1483 + passedUri = uri; 1.1484 + } 1.1485 + 1.1486 + final boolean isExternalURL = passedUri != null && 1.1487 + !AboutPages.isAboutHome(passedUri); 1.1488 + StartupAction startupAction; 1.1489 + if (isExternalURL) { 1.1490 + startupAction = StartupAction.URL; 1.1491 + } else { 1.1492 + startupAction = StartupAction.NORMAL; 1.1493 + } 1.1494 + 1.1495 + // Start migrating as early as possible, can do this in 1.1496 + // parallel with Gecko load. 1.1497 + checkMigrateProfile(); 1.1498 + 1.1499 + Uri data = intent.getData(); 1.1500 + if (data != null && "http".equals(data.getScheme())) { 1.1501 + startupAction = StartupAction.PREFETCH; 1.1502 + ThreadUtils.postToBackgroundThread(new PrefetchRunnable(data.toString())); 1.1503 + } 1.1504 + 1.1505 + Tabs.registerOnTabsChangedListener(this); 1.1506 + 1.1507 + initializeChrome(); 1.1508 + 1.1509 + // If we are doing a restore, read the session data and send it to Gecko 1.1510 + if (!mIsRestoringActivity) { 1.1511 + String restoreMessage = null; 1.1512 + if (mShouldRestore) { 1.1513 + try { 1.1514 + // restoreSessionTabs() will create simple tab stubs with the 1.1515 + // URL and title for each page, but we also need to restore 1.1516 + // session history. restoreSessionTabs() will inject the IDs 1.1517 + // of the tab stubs into the JSON data (which holds the session 1.1518 + // history). This JSON data is then sent to Gecko so session 1.1519 + // history can be restored for each tab. 1.1520 + restoreMessage = restoreSessionTabs(isExternalURL); 1.1521 + } catch (SessionRestoreException e) { 1.1522 + // If restore failed, do a normal startup 1.1523 + Log.e(LOGTAG, "An error occurred during restore", e); 1.1524 + mShouldRestore = false; 1.1525 + } 1.1526 + } 1.1527 + 1.1528 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Session:Restore", restoreMessage)); 1.1529 + } 1.1530 + 1.1531 + // External URLs should always be loaded regardless of whether Gecko is 1.1532 + // already running. 1.1533 + if (isExternalURL) { 1.1534 + loadStartupTab(passedUri); 1.1535 + } else if (!mIsRestoringActivity) { 1.1536 + loadStartupTab(null); 1.1537 + } 1.1538 + 1.1539 + // We now have tab stubs from the last session. Any future tabs should 1.1540 + // be animated. 1.1541 + Tabs.getInstance().notifyListeners(null, Tabs.TabEvents.RESTORED); 1.1542 + 1.1543 + // If we're not restoring, move the session file so it can be read for 1.1544 + // the last tabs section. 1.1545 + if (!mShouldRestore) { 1.1546 + getProfile().moveSessionFile(); 1.1547 + } 1.1548 + 1.1549 + Telemetry.HistogramAdd("FENNEC_STARTUP_GECKOAPP_ACTION", startupAction.ordinal()); 1.1550 + 1.1551 + if (!mIsRestoringActivity) { 1.1552 + GeckoThread.setArgs(intent.getStringExtra("args")); 1.1553 + GeckoThread.setAction(intent.getAction()); 1.1554 + GeckoThread.setUri(passedUri); 1.1555 + } 1.1556 + if (!ACTION_DEBUG.equals(action) && 1.1557 + GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching, GeckoThread.LaunchState.Launched)) { 1.1558 + GeckoThread.createAndStart(); 1.1559 + } else if (ACTION_DEBUG.equals(action) && 1.1560 + GeckoThread.checkAndSetLaunchState(GeckoThread.LaunchState.Launching, GeckoThread.LaunchState.WaitForDebugger)) { 1.1561 + ThreadUtils.getUiHandler().postDelayed(new Runnable() { 1.1562 + @Override 1.1563 + public void run() { 1.1564 + GeckoThread.setLaunchState(GeckoThread.LaunchState.Launching); 1.1565 + GeckoThread.createAndStart(); 1.1566 + } 1.1567 + }, 1000 * 5 /* 5 seconds */); 1.1568 + } 1.1569 + 1.1570 + // Check if launched from data reporting notification. 1.1571 + if (ACTION_LAUNCH_SETTINGS.equals(action)) { 1.1572 + Intent settingsIntent = new Intent(GeckoApp.this, GeckoPreferences.class); 1.1573 + // Copy extras. 1.1574 + settingsIntent.putExtras(intent); 1.1575 + startActivity(settingsIntent); 1.1576 + } 1.1577 + 1.1578 + //app state callbacks 1.1579 + mAppStateListeners = new LinkedList<GeckoAppShell.AppStateListener>(); 1.1580 + 1.1581 + //register for events 1.1582 + registerEventListener("log"); 1.1583 + registerEventListener("Reader:ListStatusRequest"); 1.1584 + registerEventListener("Reader:Added"); 1.1585 + registerEventListener("Reader:Removed"); 1.1586 + registerEventListener("Reader:Share"); 1.1587 + registerEventListener("Reader:FaviconRequest"); 1.1588 + registerEventListener("onCameraCapture"); 1.1589 + registerEventListener("Gecko:Ready"); 1.1590 + registerEventListener("Gecko:DelayedStartup"); 1.1591 + registerEventListener("Toast:Show"); 1.1592 + registerEventListener("DOMFullScreen:Start"); 1.1593 + registerEventListener("DOMFullScreen:Stop"); 1.1594 + registerEventListener("ToggleChrome:Hide"); 1.1595 + registerEventListener("ToggleChrome:Show"); 1.1596 + registerEventListener("ToggleChrome:Focus"); 1.1597 + registerEventListener("Permissions:Data"); 1.1598 + registerEventListener("Session:StatePurged"); 1.1599 + registerEventListener("Bookmark:Insert"); 1.1600 + registerEventListener("Accessibility:Event"); 1.1601 + registerEventListener("Accessibility:Ready"); 1.1602 + registerEventListener("Shortcut:Remove"); 1.1603 + registerEventListener("Share:Text"); 1.1604 + registerEventListener("Image:SetAs"); 1.1605 + registerEventListener("Sanitize:ClearHistory"); 1.1606 + registerEventListener("Update:Check"); 1.1607 + registerEventListener("Update:Download"); 1.1608 + registerEventListener("Update:Install"); 1.1609 + registerEventListener("PrivateBrowsing:Data"); 1.1610 + registerEventListener("Contact:Add"); 1.1611 + registerEventListener("Intent:Open"); 1.1612 + registerEventListener("Intent:OpenForResult"); 1.1613 + registerEventListener("Intent:GetHandlers"); 1.1614 + registerEventListener("Locale:Set"); 1.1615 + registerEventListener("NativeApp:IsDebuggable"); 1.1616 + registerEventListener("SystemUI:Visibility"); 1.1617 + 1.1618 + if (mWebappEventListener == null) { 1.1619 + mWebappEventListener = new EventListener(); 1.1620 + mWebappEventListener.registerEvents(); 1.1621 + } 1.1622 + 1.1623 + if (SmsManager.getInstance() != null) { 1.1624 + SmsManager.getInstance().start(); 1.1625 + } 1.1626 + 1.1627 + mContactService = new ContactService(GeckoAppShell.getEventDispatcher(), this); 1.1628 + 1.1629 + mPromptService = new PromptService(this); 1.1630 + 1.1631 + mTextSelection = new TextSelection((TextSelectionHandle) findViewById(R.id.start_handle), 1.1632 + (TextSelectionHandle) findViewById(R.id.middle_handle), 1.1633 + (TextSelectionHandle) findViewById(R.id.end_handle), 1.1634 + GeckoAppShell.getEventDispatcher(), 1.1635 + this); 1.1636 + 1.1637 + PrefsHelper.getPref("app.update.autodownload", new PrefsHelper.PrefHandlerBase() { 1.1638 + @Override public void prefValue(String pref, String value) { 1.1639 + UpdateServiceHelper.registerForUpdates(GeckoApp.this, value); 1.1640 + } 1.1641 + }); 1.1642 + 1.1643 + PrefsHelper.getPref("app.geo.reportdata", new PrefsHelper.PrefHandlerBase() { 1.1644 + @Override public void prefValue(String pref, int value) { 1.1645 + if (value == 1) 1.1646 + mShouldReportGeoData = true; 1.1647 + else 1.1648 + mShouldReportGeoData = false; 1.1649 + } 1.1650 + }); 1.1651 + 1.1652 + // Trigger the completion of the telemetry timer that wraps activity startup, 1.1653 + // then grab the duration to give to FHR. 1.1654 + mJavaUiStartupTimer.stop(); 1.1655 + final long javaDuration = mJavaUiStartupTimer.getElapsed(); 1.1656 + 1.1657 + ThreadUtils.getBackgroundHandler().postDelayed(new Runnable() { 1.1658 + @Override 1.1659 + public void run() { 1.1660 + final HealthRecorder rec = mHealthRecorder; 1.1661 + if (rec != null) { 1.1662 + rec.recordJavaStartupTime(javaDuration); 1.1663 + } 1.1664 + 1.1665 + // Record our launch time for the announcements service 1.1666 + // to use in assessing inactivity. 1.1667 + final Context context = GeckoApp.this; 1.1668 + AnnouncementsBroadcastService.recordLastLaunch(context); 1.1669 + 1.1670 + // Kick off our background services. We do this by invoking the broadcast 1.1671 + // receiver, which uses the system alarm infrastructure to perform tasks at 1.1672 + // intervals. 1.1673 + GeckoPreferences.broadcastAnnouncementsPref(context); 1.1674 + GeckoPreferences.broadcastHealthReportUploadPref(context); 1.1675 + if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.Launched)) { 1.1676 + return; 1.1677 + } 1.1678 + } 1.1679 + }, 50); 1.1680 + 1.1681 + if (mIsRestoringActivity) { 1.1682 + GeckoThread.setLaunchState(GeckoThread.LaunchState.GeckoRunning); 1.1683 + Tab selectedTab = Tabs.getInstance().getSelectedTab(); 1.1684 + if (selectedTab != null) 1.1685 + Tabs.getInstance().notifyListeners(selectedTab, Tabs.TabEvents.SELECTED); 1.1686 + geckoConnected(); 1.1687 + GeckoAppShell.setLayerClient(mLayerView.getLayerClientObject()); 1.1688 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Viewport:Flush", null)); 1.1689 + } 1.1690 + 1.1691 + if (ACTION_ALERT_CALLBACK.equals(action)) { 1.1692 + processAlertCallback(intent); 1.1693 + } 1.1694 + } 1.1695 + 1.1696 + private String restoreSessionTabs(final boolean isExternalURL) throws SessionRestoreException { 1.1697 + try { 1.1698 + String sessionString = getProfile().readSessionFile(false); 1.1699 + if (sessionString == null) { 1.1700 + throw new SessionRestoreException("Could not read from session file"); 1.1701 + } 1.1702 + 1.1703 + // If we are doing an OOM restore, parse the session data and 1.1704 + // stub the restored tabs immediately. This allows the UI to be 1.1705 + // updated before Gecko has restored. 1.1706 + if (mShouldRestore) { 1.1707 + final JSONArray tabs = new JSONArray(); 1.1708 + SessionParser parser = new SessionParser() { 1.1709 + @Override 1.1710 + public void onTabRead(SessionTab sessionTab) { 1.1711 + JSONObject tabObject = sessionTab.getTabObject(); 1.1712 + 1.1713 + int flags = Tabs.LOADURL_NEW_TAB; 1.1714 + flags |= ((isExternalURL || !sessionTab.isSelected()) ? Tabs.LOADURL_DELAY_LOAD : 0); 1.1715 + flags |= (tabObject.optBoolean("desktopMode") ? Tabs.LOADURL_DESKTOP : 0); 1.1716 + flags |= (tabObject.optBoolean("isPrivate") ? Tabs.LOADURL_PRIVATE : 0); 1.1717 + 1.1718 + Tab tab = Tabs.getInstance().loadUrl(sessionTab.getUrl(), flags); 1.1719 + tab.updateTitle(sessionTab.getTitle()); 1.1720 + 1.1721 + try { 1.1722 + tabObject.put("tabId", tab.getId()); 1.1723 + } catch (JSONException e) { 1.1724 + Log.e(LOGTAG, "JSON error", e); 1.1725 + } 1.1726 + tabs.put(tabObject); 1.1727 + } 1.1728 + }; 1.1729 + 1.1730 + if (mPrivateBrowsingSession == null) { 1.1731 + parser.parse(sessionString); 1.1732 + } else { 1.1733 + parser.parse(sessionString, mPrivateBrowsingSession); 1.1734 + } 1.1735 + 1.1736 + if (tabs.length() > 0) { 1.1737 + sessionString = new JSONObject().put("windows", new JSONArray().put(new JSONObject().put("tabs", tabs))).toString(); 1.1738 + } else { 1.1739 + throw new SessionRestoreException("No tabs could be read from session file"); 1.1740 + } 1.1741 + } 1.1742 + 1.1743 + JSONObject restoreData = new JSONObject(); 1.1744 + restoreData.put("sessionString", sessionString); 1.1745 + return restoreData.toString(); 1.1746 + 1.1747 + } catch (JSONException e) { 1.1748 + throw new SessionRestoreException(e); 1.1749 + } 1.1750 + } 1.1751 + 1.1752 + public GeckoProfile getProfile() { 1.1753 + // fall back to default profile if we didn't load a specific one 1.1754 + if (mProfile == null) { 1.1755 + mProfile = GeckoProfile.get(this); 1.1756 + } 1.1757 + return mProfile; 1.1758 + } 1.1759 + 1.1760 + /** 1.1761 + * Determine whether the session should be restored. 1.1762 + * 1.1763 + * @param savedInstanceState Saved instance state given to the activity 1.1764 + * @return Whether to restore 1.1765 + */ 1.1766 + protected boolean getSessionRestoreState(Bundle savedInstanceState) { 1.1767 + final SharedPreferences prefs = getSharedPreferences(); 1.1768 + boolean shouldRestore = false; 1.1769 + 1.1770 + final int versionCode = getVersionCode(); 1.1771 + if (prefs.getInt(PREFS_VERSION_CODE, 0) != versionCode) { 1.1772 + // If the version has changed, the user has done an upgrade, so restore 1.1773 + // previous tabs. 1.1774 + ThreadUtils.postToBackgroundThread(new Runnable() { 1.1775 + @Override 1.1776 + public void run() { 1.1777 + prefs.edit() 1.1778 + .putInt(PREFS_VERSION_CODE, versionCode) 1.1779 + .commit(); 1.1780 + } 1.1781 + }); 1.1782 + 1.1783 + shouldRestore = true; 1.1784 + } else if (savedInstanceState != null || 1.1785 + getSessionRestorePreference().equals("always") || 1.1786 + getRestartFromIntent()) { 1.1787 + // We're coming back from a background kill by the OS, the user 1.1788 + // has chosen to always restore, or we restarted. 1.1789 + shouldRestore = true; 1.1790 + } else if (prefs.getBoolean(GeckoApp.PREFS_CRASHED, false)) { 1.1791 + ThreadUtils.postToBackgroundThread(new Runnable() { 1.1792 + @Override 1.1793 + public void run() { 1.1794 + prefs.edit().putBoolean(PREFS_CRASHED, false).commit(); 1.1795 + } 1.1796 + }); 1.1797 + shouldRestore = true; 1.1798 + } 1.1799 + 1.1800 + return shouldRestore; 1.1801 + } 1.1802 + 1.1803 + private String getSessionRestorePreference() { 1.1804 + return getSharedPreferences().getString(GeckoPreferences.PREFS_RESTORE_SESSION, "quit"); 1.1805 + } 1.1806 + 1.1807 + private boolean getRestartFromIntent() { 1.1808 + return getIntent().getBooleanExtra("didRestart", false); 1.1809 + } 1.1810 + 1.1811 + /** 1.1812 + * Enable Android StrictMode checks (for supported OS versions). 1.1813 + * http://developer.android.com/reference/android/os/StrictMode.html 1.1814 + */ 1.1815 + private void enableStrictMode() { 1.1816 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) { 1.1817 + return; 1.1818 + } 1.1819 + 1.1820 + Log.d(LOGTAG, "Enabling Android StrictMode"); 1.1821 + 1.1822 + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() 1.1823 + .detectAll() 1.1824 + .penaltyLog() 1.1825 + .build()); 1.1826 + 1.1827 + StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() 1.1828 + .detectAll() 1.1829 + .penaltyLog() 1.1830 + .build()); 1.1831 + } 1.1832 + 1.1833 + public void enableCameraView() { 1.1834 + // Start listening for orientation events 1.1835 + mCameraOrientationEventListener = new OrientationEventListener(this) { 1.1836 + @Override 1.1837 + public void onOrientationChanged(int orientation) { 1.1838 + if (mAppStateListeners != null) { 1.1839 + for (GeckoAppShell.AppStateListener listener: mAppStateListeners) { 1.1840 + listener.onOrientationChanged(); 1.1841 + } 1.1842 + } 1.1843 + } 1.1844 + }; 1.1845 + mCameraOrientationEventListener.enable(); 1.1846 + 1.1847 + // Try to make it fully transparent. 1.1848 + if (mCameraView instanceof SurfaceView) { 1.1849 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 1.1850 + mCameraView.setAlpha(0.0f); 1.1851 + } 1.1852 + } else if (mCameraView instanceof TextureView) { 1.1853 + mCameraView.setAlpha(0.0f); 1.1854 + } 1.1855 + ViewGroup mCameraLayout = (ViewGroup) findViewById(R.id.camera_layout); 1.1856 + // Some phones (eg. nexus S) need at least a 8x16 preview size 1.1857 + mCameraLayout.addView(mCameraView, 1.1858 + new AbsoluteLayout.LayoutParams(8, 16, 0, 0)); 1.1859 + } 1.1860 + 1.1861 + public void disableCameraView() { 1.1862 + if (mCameraOrientationEventListener != null) { 1.1863 + mCameraOrientationEventListener.disable(); 1.1864 + mCameraOrientationEventListener = null; 1.1865 + } 1.1866 + ViewGroup mCameraLayout = (ViewGroup) findViewById(R.id.camera_layout); 1.1867 + mCameraLayout.removeView(mCameraView); 1.1868 + } 1.1869 + 1.1870 + public String getDefaultUAString() { 1.1871 + return HardwareUtils.isTablet() ? AppConstants.USER_AGENT_FENNEC_TABLET : 1.1872 + AppConstants.USER_AGENT_FENNEC_MOBILE; 1.1873 + } 1.1874 + 1.1875 + public String getUAStringForHost(String host) { 1.1876 + // With our standard UA String, we get a 200 response code and 1.1877 + // client-side redirect from t.co. This bot-like UA gives us a 1.1878 + // 301 response code 1.1879 + if ("t.co".equals(host)) { 1.1880 + return AppConstants.USER_AGENT_BOT_LIKE; 1.1881 + } 1.1882 + return getDefaultUAString(); 1.1883 + } 1.1884 + 1.1885 + class PrefetchRunnable implements Runnable { 1.1886 + private String mPrefetchUrl; 1.1887 + 1.1888 + PrefetchRunnable(String prefetchUrl) { 1.1889 + mPrefetchUrl = prefetchUrl; 1.1890 + } 1.1891 + 1.1892 + @Override 1.1893 + public void run() { 1.1894 + HttpURLConnection connection = null; 1.1895 + try { 1.1896 + URL url = new URL(mPrefetchUrl); 1.1897 + // data url should have an http scheme 1.1898 + connection = (HttpURLConnection) url.openConnection(); 1.1899 + connection.setRequestProperty("User-Agent", getUAStringForHost(url.getHost())); 1.1900 + connection.setInstanceFollowRedirects(false); 1.1901 + connection.setRequestMethod("GET"); 1.1902 + connection.connect(); 1.1903 + } catch (Exception e) { 1.1904 + Log.e(LOGTAG, "Exception prefetching URL", e); 1.1905 + } finally { 1.1906 + if (connection != null) 1.1907 + connection.disconnect(); 1.1908 + } 1.1909 + } 1.1910 + } 1.1911 + 1.1912 + private void processAlertCallback(Intent intent) { 1.1913 + String alertName = ""; 1.1914 + String alertCookie = ""; 1.1915 + Uri data = intent.getData(); 1.1916 + if (data != null) { 1.1917 + alertName = data.getQueryParameter("name"); 1.1918 + if (alertName == null) 1.1919 + alertName = ""; 1.1920 + alertCookie = data.getQueryParameter("cookie"); 1.1921 + if (alertCookie == null) 1.1922 + alertCookie = ""; 1.1923 + } 1.1924 + handleNotification(ACTION_ALERT_CALLBACK, alertName, alertCookie); 1.1925 + } 1.1926 + 1.1927 + @Override 1.1928 + protected void onNewIntent(Intent intent) { 1.1929 + if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoExiting)) { 1.1930 + // We're exiting and shouldn't try to do anything else. In the case 1.1931 + // where we are hung while exiting, we should force the process to exit. 1.1932 + GeckoAppShell.systemExit(); 1.1933 + return; 1.1934 + } 1.1935 + 1.1936 + // if we were previously OOM killed, we can end up here when launching 1.1937 + // from external shortcuts, so set this as the intent for initialization 1.1938 + if (!mInitialized) { 1.1939 + setIntent(intent); 1.1940 + return; 1.1941 + } 1.1942 + 1.1943 + final String action = intent.getAction(); 1.1944 + 1.1945 + if (ACTION_LOAD.equals(action)) { 1.1946 + String uri = intent.getDataString(); 1.1947 + Tabs.getInstance().loadUrl(uri); 1.1948 + } else if (Intent.ACTION_VIEW.equals(action)) { 1.1949 + String uri = intent.getDataString(); 1.1950 + Tabs.getInstance().loadUrl(uri, Tabs.LOADURL_NEW_TAB | 1.1951 + Tabs.LOADURL_USER_ENTERED | 1.1952 + Tabs.LOADURL_EXTERNAL); 1.1953 + } else if (action != null && action.startsWith(ACTION_WEBAPP_PREFIX)) { 1.1954 + // A lightweight mechanism for loading a web page as a webapp 1.1955 + // without installing the app natively nor registering it in the DOM 1.1956 + // application registry. 1.1957 + String uri = getURIFromIntent(intent); 1.1958 + GeckoAppShell.sendEventToGecko(GeckoEvent.createWebappLoadEvent(uri)); 1.1959 + } else if (ACTION_BOOKMARK.equals(action)) { 1.1960 + String uri = getURIFromIntent(intent); 1.1961 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBookmarkLoadEvent(uri)); 1.1962 + } else if (Intent.ACTION_SEARCH.equals(action)) { 1.1963 + String uri = getURIFromIntent(intent); 1.1964 + GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(uri)); 1.1965 + } else if (ACTION_ALERT_CALLBACK.equals(action)) { 1.1966 + processAlertCallback(intent); 1.1967 + } else if (ACTION_LAUNCH_SETTINGS.equals(action)) { 1.1968 + // Check if launched from data reporting notification. 1.1969 + Intent settingsIntent = new Intent(GeckoApp.this, GeckoPreferences.class); 1.1970 + // Copy extras. 1.1971 + settingsIntent.putExtras(intent); 1.1972 + startActivity(settingsIntent); 1.1973 + } 1.1974 + } 1.1975 + 1.1976 + /* 1.1977 + * Handles getting a uri from and intent in a way that is backwards 1.1978 + * compatable with our previous implementations 1.1979 + */ 1.1980 + protected String getURIFromIntent(Intent intent) { 1.1981 + final String action = intent.getAction(); 1.1982 + if (ACTION_ALERT_CALLBACK.equals(action)) 1.1983 + return null; 1.1984 + 1.1985 + String uri = intent.getDataString(); 1.1986 + if (uri != null) 1.1987 + return uri; 1.1988 + 1.1989 + if ((action != null && action.startsWith(ACTION_WEBAPP_PREFIX)) || ACTION_BOOKMARK.equals(action)) { 1.1990 + uri = intent.getStringExtra("args"); 1.1991 + if (uri != null && uri.startsWith("--url=")) { 1.1992 + uri.replace("--url=", ""); 1.1993 + } 1.1994 + } 1.1995 + return uri; 1.1996 + } 1.1997 + 1.1998 + protected int getOrientation() { 1.1999 + return GeckoScreenOrientation.getInstance().getAndroidOrientation(); 1.2000 + } 1.2001 + 1.2002 + @Override 1.2003 + public void onResume() 1.2004 + { 1.2005 + // After an onPause, the activity is back in the foreground. 1.2006 + // Undo whatever we did in onPause. 1.2007 + super.onResume(); 1.2008 + 1.2009 + int newOrientation = getResources().getConfiguration().orientation; 1.2010 + if (GeckoScreenOrientation.getInstance().update(newOrientation)) { 1.2011 + refreshChrome(); 1.2012 + } 1.2013 + 1.2014 + // User may have enabled/disabled accessibility. 1.2015 + GeckoAccessibility.updateAccessibilitySettings(this); 1.2016 + 1.2017 + if (mAppStateListeners != null) { 1.2018 + for (GeckoAppShell.AppStateListener listener: mAppStateListeners) { 1.2019 + listener.onResume(); 1.2020 + } 1.2021 + } 1.2022 + 1.2023 + // We use two times: a pseudo-unique wall-clock time to identify the 1.2024 + // current session across power cycles, and the elapsed realtime to 1.2025 + // track the duration of the session. 1.2026 + final long now = System.currentTimeMillis(); 1.2027 + final long realTime = android.os.SystemClock.elapsedRealtime(); 1.2028 + 1.2029 + ThreadUtils.postToBackgroundThread(new Runnable() { 1.2030 + @Override 1.2031 + public void run() { 1.2032 + // Now construct the new session on HealthRecorder's behalf. We do this here 1.2033 + // so it can benefit from a single near-startup prefs commit. 1.2034 + SessionInformation currentSession = new SessionInformation(now, realTime); 1.2035 + 1.2036 + SharedPreferences prefs = GeckoApp.this.getSharedPreferences(); 1.2037 + SharedPreferences.Editor editor = prefs.edit(); 1.2038 + editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false); 1.2039 + currentSession.recordBegin(editor); 1.2040 + editor.commit(); 1.2041 + 1.2042 + final HealthRecorder rec = mHealthRecorder; 1.2043 + if (rec != null) { 1.2044 + rec.setCurrentSession(currentSession); 1.2045 + } else { 1.2046 + Log.w(LOGTAG, "Can't record session: rec is null."); 1.2047 + } 1.2048 + } 1.2049 + }); 1.2050 + } 1.2051 + 1.2052 + @Override 1.2053 + public void onWindowFocusChanged(boolean hasFocus) { 1.2054 + super.onWindowFocusChanged(hasFocus); 1.2055 + 1.2056 + if (!mInitialized && hasFocus) { 1.2057 + initialize(); 1.2058 + getWindow().setBackgroundDrawable(null); 1.2059 + } 1.2060 + } 1.2061 + 1.2062 + @Override 1.2063 + public void onPause() 1.2064 + { 1.2065 + final HealthRecorder rec = mHealthRecorder; 1.2066 + final Context context = this; 1.2067 + 1.2068 + // In some way it's sad that Android will trigger StrictMode warnings 1.2069 + // here as the whole point is to save to disk while the activity is not 1.2070 + // interacting with the user. 1.2071 + ThreadUtils.postToBackgroundThread(new Runnable() { 1.2072 + @Override 1.2073 + public void run() { 1.2074 + SharedPreferences prefs = GeckoApp.this.getSharedPreferences(); 1.2075 + SharedPreferences.Editor editor = prefs.edit(); 1.2076 + editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, true); 1.2077 + if (rec != null) { 1.2078 + rec.recordSessionEnd("P", editor); 1.2079 + } 1.2080 + 1.2081 + // If we haven't done it before, cleanup any old files in our old temp dir 1.2082 + if (prefs.getBoolean(GeckoApp.PREFS_CLEANUP_TEMP_FILES, true)) { 1.2083 + File tempDir = GeckoLoader.getGREDir(GeckoApp.this); 1.2084 + FileUtils.delTree(tempDir, new FileUtils.NameAndAgeFilter(null, ONE_DAY_MS), false); 1.2085 + 1.2086 + editor.putBoolean(GeckoApp.PREFS_CLEANUP_TEMP_FILES, false); 1.2087 + } 1.2088 + 1.2089 + editor.commit(); 1.2090 + 1.2091 + // In theory, the first browser session will not run long enough that we need to 1.2092 + // prune during it and we'd rather run it when the browser is inactive so we wait 1.2093 + // until here to register the prune service. 1.2094 + GeckoPreferences.broadcastHealthReportPrune(context); 1.2095 + } 1.2096 + }); 1.2097 + 1.2098 + if (mAppStateListeners != null) { 1.2099 + for(GeckoAppShell.AppStateListener listener: mAppStateListeners) { 1.2100 + listener.onPause(); 1.2101 + } 1.2102 + } 1.2103 + 1.2104 + super.onPause(); 1.2105 + } 1.2106 + 1.2107 + @Override 1.2108 + public void onRestart() 1.2109 + { 1.2110 + ThreadUtils.postToBackgroundThread(new Runnable() { 1.2111 + @Override 1.2112 + public void run() { 1.2113 + SharedPreferences prefs = GeckoApp.this.getSharedPreferences(); 1.2114 + SharedPreferences.Editor editor = prefs.edit(); 1.2115 + editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false); 1.2116 + editor.commit(); 1.2117 + } 1.2118 + }); 1.2119 + 1.2120 + super.onRestart(); 1.2121 + } 1.2122 + 1.2123 + @Override 1.2124 + public void onDestroy() 1.2125 + { 1.2126 + unregisterEventListener("log"); 1.2127 + unregisterEventListener("Reader:ListStatusRequest"); 1.2128 + unregisterEventListener("Reader:Added"); 1.2129 + unregisterEventListener("Reader:Removed"); 1.2130 + unregisterEventListener("Reader:Share"); 1.2131 + unregisterEventListener("Reader:FaviconRequest"); 1.2132 + unregisterEventListener("onCameraCapture"); 1.2133 + unregisterEventListener("Gecko:Ready"); 1.2134 + unregisterEventListener("Gecko:DelayedStartup"); 1.2135 + unregisterEventListener("Toast:Show"); 1.2136 + unregisterEventListener("DOMFullScreen:Start"); 1.2137 + unregisterEventListener("DOMFullScreen:Stop"); 1.2138 + unregisterEventListener("ToggleChrome:Hide"); 1.2139 + unregisterEventListener("ToggleChrome:Show"); 1.2140 + unregisterEventListener("ToggleChrome:Focus"); 1.2141 + unregisterEventListener("Permissions:Data"); 1.2142 + unregisterEventListener("Session:StatePurged"); 1.2143 + unregisterEventListener("Bookmark:Insert"); 1.2144 + unregisterEventListener("Accessibility:Event"); 1.2145 + unregisterEventListener("Accessibility:Ready"); 1.2146 + unregisterEventListener("Shortcut:Remove"); 1.2147 + unregisterEventListener("Share:Text"); 1.2148 + unregisterEventListener("Image:SetAs"); 1.2149 + unregisterEventListener("Sanitize:ClearHistory"); 1.2150 + unregisterEventListener("Update:Check"); 1.2151 + unregisterEventListener("Update:Download"); 1.2152 + unregisterEventListener("Update:Install"); 1.2153 + unregisterEventListener("PrivateBrowsing:Data"); 1.2154 + unregisterEventListener("Contact:Add"); 1.2155 + unregisterEventListener("Intent:Open"); 1.2156 + unregisterEventListener("Intent:GetHandlers"); 1.2157 + unregisterEventListener("Locale:Set"); 1.2158 + unregisterEventListener("NativeApp:IsDebuggable"); 1.2159 + unregisterEventListener("SystemUI:Visibility"); 1.2160 + 1.2161 + if (mWebappEventListener != null) { 1.2162 + mWebappEventListener.unregisterEvents(); 1.2163 + mWebappEventListener = null; 1.2164 + } 1.2165 + 1.2166 + deleteTempFiles(); 1.2167 + 1.2168 + if (mLayerView != null) 1.2169 + mLayerView.destroy(); 1.2170 + if (mDoorHangerPopup != null) 1.2171 + mDoorHangerPopup.destroy(); 1.2172 + if (mFormAssistPopup != null) 1.2173 + mFormAssistPopup.destroy(); 1.2174 + if (mContactService != null) 1.2175 + mContactService.destroy(); 1.2176 + if (mPromptService != null) 1.2177 + mPromptService.destroy(); 1.2178 + if (mTextSelection != null) 1.2179 + mTextSelection.destroy(); 1.2180 + NotificationHelper.destroy(); 1.2181 + 1.2182 + if (SmsManager.getInstance() != null) { 1.2183 + SmsManager.getInstance().stop(); 1.2184 + if (isFinishing()) 1.2185 + SmsManager.getInstance().shutdown(); 1.2186 + } 1.2187 + 1.2188 + final HealthRecorder rec = mHealthRecorder; 1.2189 + mHealthRecorder = null; 1.2190 + if (rec != null && rec.isEnabled()) { 1.2191 + // Closing a BrowserHealthRecorder could incur a write. 1.2192 + ThreadUtils.postToBackgroundThread(new Runnable() { 1.2193 + @Override 1.2194 + public void run() { 1.2195 + rec.close(); 1.2196 + } 1.2197 + }); 1.2198 + } 1.2199 + 1.2200 + Favicons.close(); 1.2201 + 1.2202 + super.onDestroy(); 1.2203 + 1.2204 + Tabs.unregisterOnTabsChangedListener(this); 1.2205 + } 1.2206 + 1.2207 + protected void registerEventListener(String event) { 1.2208 + GeckoAppShell.getEventDispatcher().registerEventListener(event, this); 1.2209 + } 1.2210 + 1.2211 + protected void unregisterEventListener(String event) { 1.2212 + GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this); 1.2213 + } 1.2214 + 1.2215 + // Get a temporary directory, may return null 1.2216 + public static File getTempDirectory() { 1.2217 + File dir = GeckoApplication.get().getExternalFilesDir("temp"); 1.2218 + return dir; 1.2219 + } 1.2220 + 1.2221 + // Delete any files in our temporary directory 1.2222 + public static void deleteTempFiles() { 1.2223 + File dir = getTempDirectory(); 1.2224 + if (dir == null) 1.2225 + return; 1.2226 + File[] files = dir.listFiles(); 1.2227 + if (files == null) 1.2228 + return; 1.2229 + for (File file : files) { 1.2230 + file.delete(); 1.2231 + } 1.2232 + } 1.2233 + 1.2234 + @Override 1.2235 + public void onConfigurationChanged(Configuration newConfig) { 1.2236 + Log.d(LOGTAG, "onConfigurationChanged: " + newConfig.locale); 1.2237 + BrowserLocaleManager.getInstance().correctLocale(this, getResources(), newConfig); 1.2238 + 1.2239 + // onConfigurationChanged is not called for 180 degree orientation changes, 1.2240 + // we will miss such rotations and the screen orientation will not be 1.2241 + // updated. 1.2242 + if (GeckoScreenOrientation.getInstance().update(newConfig.orientation)) { 1.2243 + if (mFormAssistPopup != null) 1.2244 + mFormAssistPopup.hide(); 1.2245 + refreshChrome(); 1.2246 + } 1.2247 + super.onConfigurationChanged(newConfig); 1.2248 + } 1.2249 + 1.2250 + public String getContentProcessName() { 1.2251 + return AppConstants.MOZ_CHILD_PROCESS_NAME; 1.2252 + } 1.2253 + 1.2254 + public void addEnvToIntent(Intent intent) { 1.2255 + Map<String,String> envMap = System.getenv(); 1.2256 + Set<Map.Entry<String,String>> envSet = envMap.entrySet(); 1.2257 + Iterator<Map.Entry<String,String>> envIter = envSet.iterator(); 1.2258 + int c = 0; 1.2259 + while (envIter.hasNext()) { 1.2260 + Map.Entry<String,String> entry = envIter.next(); 1.2261 + intent.putExtra("env" + c, entry.getKey() + "=" 1.2262 + + entry.getValue()); 1.2263 + c++; 1.2264 + } 1.2265 + } 1.2266 + 1.2267 + public void doRestart() { 1.2268 + doRestart(RESTARTER_ACTION, null); 1.2269 + } 1.2270 + 1.2271 + public void doRestart(String args) { 1.2272 + doRestart(RESTARTER_ACTION, args); 1.2273 + } 1.2274 + 1.2275 + public void doRestart(String action, String args) { 1.2276 + Log.d(LOGTAG, "doRestart(\"" + action + "\")"); 1.2277 + try { 1.2278 + Intent intent = new Intent(action); 1.2279 + intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, RESTARTER_CLASS); 1.2280 + /* TODO: addEnvToIntent(intent); */ 1.2281 + if (args != null) 1.2282 + intent.putExtra("args", args); 1.2283 + intent.putExtra("didRestart", true); 1.2284 + Log.d(LOGTAG, "Restart intent: " + intent.toString()); 1.2285 + GeckoAppShell.killAnyZombies(); 1.2286 + startActivity(intent); 1.2287 + } catch (Exception e) { 1.2288 + Log.e(LOGTAG, "Error effecting restart.", e); 1.2289 + } 1.2290 + 1.2291 + finish(); 1.2292 + // Give the restart process time to start before we die 1.2293 + GeckoAppShell.waitForAnotherGeckoProc(); 1.2294 + } 1.2295 + 1.2296 + public void handleNotification(String action, String alertName, String alertCookie) { 1.2297 + // If Gecko isn't running yet, we ignore the notification. Note that 1.2298 + // even if Gecko is running but it was restarted since the notification 1.2299 + // was created, the notification won't be handled (bug 849653). 1.2300 + if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) { 1.2301 + GeckoAppShell.handleNotification(action, alertName, alertCookie); 1.2302 + } 1.2303 + } 1.2304 + 1.2305 + private void checkMigrateProfile() { 1.2306 + final File profileDir = getProfile().getDir(); 1.2307 + 1.2308 + if (profileDir != null) { 1.2309 + ThreadUtils.postToBackgroundThread(new Runnable() { 1.2310 + @Override 1.2311 + public void run() { 1.2312 + Handler handler = new Handler(); 1.2313 + handler.postDelayed(new DeferredCleanupTask(), CLEANUP_DEFERRAL_SECONDS * 1000); 1.2314 + } 1.2315 + }); 1.2316 + } 1.2317 + } 1.2318 + 1.2319 + private class DeferredCleanupTask implements Runnable { 1.2320 + // The cleanup-version setting is recorded to avoid repeating the same 1.2321 + // tasks on subsequent startups; CURRENT_CLEANUP_VERSION may be updated 1.2322 + // if we need to do additional cleanup for future Gecko versions. 1.2323 + 1.2324 + private static final String CLEANUP_VERSION = "cleanup-version"; 1.2325 + private static final int CURRENT_CLEANUP_VERSION = 1; 1.2326 + 1.2327 + @Override 1.2328 + public void run() { 1.2329 + long cleanupVersion = getSharedPreferences().getInt(CLEANUP_VERSION, 0); 1.2330 + 1.2331 + if (cleanupVersion < 1) { 1.2332 + // Reduce device storage footprint by removing .ttf files from 1.2333 + // the res/fonts directory: we no longer need to copy our 1.2334 + // bundled fonts out of the APK in order to use them. 1.2335 + // See https://bugzilla.mozilla.org/show_bug.cgi?id=878674. 1.2336 + File dir = new File("res/fonts"); 1.2337 + if (dir.exists() && dir.isDirectory()) { 1.2338 + for (File file : dir.listFiles()) { 1.2339 + if (file.isFile() && file.getName().endsWith(".ttf")) { 1.2340 + Log.i(LOGTAG, "deleting " + file.toString()); 1.2341 + file.delete(); 1.2342 + } 1.2343 + } 1.2344 + if (!dir.delete()) { 1.2345 + Log.w(LOGTAG, "unable to delete res/fonts directory (not empty?)"); 1.2346 + } else { 1.2347 + Log.i(LOGTAG, "res/fonts directory deleted"); 1.2348 + } 1.2349 + } 1.2350 + } 1.2351 + 1.2352 + // Additional cleanup needed for future versions would go here 1.2353 + 1.2354 + if (cleanupVersion != CURRENT_CLEANUP_VERSION) { 1.2355 + SharedPreferences.Editor editor = GeckoApp.this.getSharedPreferences().edit(); 1.2356 + editor.putInt(CLEANUP_VERSION, CURRENT_CLEANUP_VERSION); 1.2357 + editor.commit(); 1.2358 + } 1.2359 + } 1.2360 + } 1.2361 + 1.2362 + public PromptService getPromptService() { 1.2363 + return mPromptService; 1.2364 + } 1.2365 + 1.2366 + @Override 1.2367 + public void onBackPressed() { 1.2368 + if (getSupportFragmentManager().getBackStackEntryCount() > 0) { 1.2369 + super.onBackPressed(); 1.2370 + return; 1.2371 + } 1.2372 + 1.2373 + if (autoHideTabs()) { 1.2374 + return; 1.2375 + } 1.2376 + 1.2377 + if (mDoorHangerPopup != null && mDoorHangerPopup.isShowing()) { 1.2378 + mDoorHangerPopup.dismiss(); 1.2379 + return; 1.2380 + } 1.2381 + 1.2382 + if (mFullScreenPluginView != null) { 1.2383 + GeckoAppShell.onFullScreenPluginHidden(mFullScreenPluginView); 1.2384 + removeFullScreenPluginView(mFullScreenPluginView); 1.2385 + return; 1.2386 + } 1.2387 + 1.2388 + if (mLayerView != null && mLayerView.isFullScreen()) { 1.2389 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FullScreen:Exit", null)); 1.2390 + return; 1.2391 + } 1.2392 + 1.2393 + Tabs tabs = Tabs.getInstance(); 1.2394 + Tab tab = tabs.getSelectedTab(); 1.2395 + if (tab == null) { 1.2396 + moveTaskToBack(true); 1.2397 + return; 1.2398 + } 1.2399 + 1.2400 + if (tab.doBack()) 1.2401 + return; 1.2402 + 1.2403 + if (tab.isExternal()) { 1.2404 + moveTaskToBack(true); 1.2405 + tabs.closeTab(tab); 1.2406 + return; 1.2407 + } 1.2408 + 1.2409 + int parentId = tab.getParentId(); 1.2410 + Tab parent = tabs.getTab(parentId); 1.2411 + if (parent != null) { 1.2412 + // The back button should always return to the parent (not a sibling). 1.2413 + tabs.closeTab(tab, parent); 1.2414 + return; 1.2415 + } 1.2416 + 1.2417 + moveTaskToBack(true); 1.2418 + } 1.2419 + 1.2420 + @Override 1.2421 + protected void onActivityResult(int requestCode, int resultCode, Intent data) { 1.2422 + if (!ActivityHandlerHelper.handleActivityResult(requestCode, resultCode, data)) { 1.2423 + super.onActivityResult(requestCode, resultCode, data); 1.2424 + } 1.2425 + } 1.2426 + 1.2427 + public AbsoluteLayout getPluginContainer() { return mPluginContainer; } 1.2428 + 1.2429 + // Accelerometer. 1.2430 + @Override 1.2431 + public void onAccuracyChanged(Sensor sensor, int accuracy) { 1.2432 + } 1.2433 + 1.2434 + @Override 1.2435 + public void onSensorChanged(SensorEvent event) { 1.2436 + GeckoAppShell.sendEventToGecko(GeckoEvent.createSensorEvent(event)); 1.2437 + } 1.2438 + 1.2439 + // Geolocation. 1.2440 + @Override 1.2441 + public void onLocationChanged(Location location) { 1.2442 + // No logging here: user-identifying information. 1.2443 + GeckoAppShell.sendEventToGecko(GeckoEvent.createLocationEvent(location)); 1.2444 + if (mShouldReportGeoData) 1.2445 + collectAndReportLocInfo(location); 1.2446 + } 1.2447 + 1.2448 + public void setCurrentSignalStrenth(SignalStrength ss) { 1.2449 + if (ss.isGsm()) 1.2450 + mSignalStrenth = ss.getGsmSignalStrength(); 1.2451 + } 1.2452 + 1.2453 + private int getCellInfo(JSONArray cellInfo) { 1.2454 + TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE); 1.2455 + if (tm == null) 1.2456 + return TelephonyManager.PHONE_TYPE_NONE; 1.2457 + List<NeighboringCellInfo> cells = tm.getNeighboringCellInfo(); 1.2458 + CellLocation cl = tm.getCellLocation(); 1.2459 + String mcc = "", mnc = ""; 1.2460 + if (cl instanceof GsmCellLocation) { 1.2461 + JSONObject obj = new JSONObject(); 1.2462 + GsmCellLocation gcl = (GsmCellLocation)cl; 1.2463 + try { 1.2464 + obj.put("lac", gcl.getLac()); 1.2465 + obj.put("cid", gcl.getCid()); 1.2466 + 1.2467 + int psc = (Build.VERSION.SDK_INT >= 9) ? gcl.getPsc() : -1; 1.2468 + obj.put("psc", psc); 1.2469 + 1.2470 + switch(tm.getNetworkType()) { 1.2471 + case TelephonyManager.NETWORK_TYPE_GPRS: 1.2472 + case TelephonyManager.NETWORK_TYPE_EDGE: 1.2473 + obj.put("radio", "gsm"); 1.2474 + break; 1.2475 + case TelephonyManager.NETWORK_TYPE_UMTS: 1.2476 + case TelephonyManager.NETWORK_TYPE_HSDPA: 1.2477 + case TelephonyManager.NETWORK_TYPE_HSUPA: 1.2478 + case TelephonyManager.NETWORK_TYPE_HSPA: 1.2479 + case TelephonyManager.NETWORK_TYPE_HSPAP: 1.2480 + obj.put("radio", "umts"); 1.2481 + break; 1.2482 + } 1.2483 + String mcc_mnc = tm.getNetworkOperator(); 1.2484 + if (mcc_mnc.length() > 3) { 1.2485 + mcc = mcc_mnc.substring(0, 3); 1.2486 + mnc = mcc_mnc.substring(3); 1.2487 + obj.put("mcc", mcc); 1.2488 + obj.put("mnc", mnc); 1.2489 + } 1.2490 + obj.put("asu", mSignalStrenth); 1.2491 + } catch(JSONException jsonex) {} 1.2492 + cellInfo.put(obj); 1.2493 + } 1.2494 + if (cells != null) { 1.2495 + for (NeighboringCellInfo nci : cells) { 1.2496 + try { 1.2497 + JSONObject obj = new JSONObject(); 1.2498 + obj.put("lac", nci.getLac()); 1.2499 + obj.put("cid", nci.getCid()); 1.2500 + obj.put("psc", nci.getPsc()); 1.2501 + obj.put("mcc", mcc); 1.2502 + obj.put("mnc", mnc); 1.2503 + 1.2504 + int dbm; 1.2505 + switch(nci.getNetworkType()) { 1.2506 + case TelephonyManager.NETWORK_TYPE_GPRS: 1.2507 + case TelephonyManager.NETWORK_TYPE_EDGE: 1.2508 + obj.put("radio", "gsm"); 1.2509 + break; 1.2510 + case TelephonyManager.NETWORK_TYPE_UMTS: 1.2511 + case TelephonyManager.NETWORK_TYPE_HSDPA: 1.2512 + case TelephonyManager.NETWORK_TYPE_HSUPA: 1.2513 + case TelephonyManager.NETWORK_TYPE_HSPA: 1.2514 + case TelephonyManager.NETWORK_TYPE_HSPAP: 1.2515 + obj.put("radio", "umts"); 1.2516 + break; 1.2517 + } 1.2518 + 1.2519 + obj.put("asu", nci.getRssi()); 1.2520 + cellInfo.put(obj); 1.2521 + } catch(JSONException jsonex) {} 1.2522 + } 1.2523 + } 1.2524 + return tm.getPhoneType(); 1.2525 + } 1.2526 + 1.2527 + private static boolean shouldLog(final ScanResult sr) { 1.2528 + return sr.SSID == null || !sr.SSID.endsWith("_nomap"); 1.2529 + } 1.2530 + 1.2531 + private void collectAndReportLocInfo(Location location) { 1.2532 + final JSONObject locInfo = new JSONObject(); 1.2533 + WifiManager wm = (WifiManager)getSystemService(Context.WIFI_SERVICE); 1.2534 + wm.startScan(); 1.2535 + try { 1.2536 + JSONArray cellInfo = new JSONArray(); 1.2537 + 1.2538 + String radioType = getRadioTypeName(getCellInfo(cellInfo)); 1.2539 + if (radioType != null) { 1.2540 + locInfo.put("radio", radioType); 1.2541 + } 1.2542 + 1.2543 + locInfo.put("lon", location.getLongitude()); 1.2544 + locInfo.put("lat", location.getLatitude()); 1.2545 + 1.2546 + // If we have an accuracy, round it up to the next meter. 1.2547 + if (location.hasAccuracy()) { 1.2548 + locInfo.put("accuracy", (int) Math.ceil(location.getAccuracy())); 1.2549 + } 1.2550 + 1.2551 + // If we have an altitude, round it to the nearest meter. 1.2552 + if (location.hasAltitude()) { 1.2553 + locInfo.put("altitude", Math.round(location.getAltitude())); 1.2554 + } 1.2555 + 1.2556 + // Reduce timestamp precision so as to expose less PII. 1.2557 + DateFormat df = new SimpleDateFormat("yyyy-MM-dd", Locale.US); 1.2558 + locInfo.put("time", df.format(new Date(location.getTime()))); 1.2559 + locInfo.put("cell", cellInfo); 1.2560 + 1.2561 + JSONArray wifiInfo = new JSONArray(); 1.2562 + List<ScanResult> aps = wm.getScanResults(); 1.2563 + if (aps != null) { 1.2564 + for (ScanResult ap : aps) { 1.2565 + if (!shouldLog(ap)) 1.2566 + continue; 1.2567 + 1.2568 + JSONObject obj = new JSONObject(); 1.2569 + obj.put("key", ap.BSSID); 1.2570 + obj.put("frequency", ap.frequency); 1.2571 + obj.put("signal", ap.level); 1.2572 + wifiInfo.put(obj); 1.2573 + } 1.2574 + } 1.2575 + locInfo.put("wifi", wifiInfo); 1.2576 + } catch (JSONException jsonex) { 1.2577 + Log.w(LOGTAG, "json exception", jsonex); 1.2578 + return; 1.2579 + } 1.2580 + 1.2581 + ThreadUtils.postToBackgroundThread(new Runnable() { 1.2582 + public void run() { 1.2583 + try { 1.2584 + URL url = new URL(LOCATION_URL); 1.2585 + HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); 1.2586 + try { 1.2587 + urlConnection.setDoOutput(true); 1.2588 + 1.2589 + // Workaround for a bug in Android HttpURLConnection. When the library 1.2590 + // reuses a stale connection, the connection may fail with an EOFException. 1.2591 + if (Build.VERSION.SDK_INT >= 14 && Build.VERSION.SDK_INT <= 18) { 1.2592 + urlConnection.setRequestProperty("Connection", "Close"); 1.2593 + } 1.2594 + 1.2595 + JSONArray batch = new JSONArray(); 1.2596 + batch.put(locInfo); 1.2597 + JSONObject wrapper = new JSONObject(); 1.2598 + wrapper.put("items", batch); 1.2599 + byte[] bytes = wrapper.toString().getBytes(); 1.2600 + urlConnection.setFixedLengthStreamingMode(bytes.length); 1.2601 + OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream()); 1.2602 + out.write(bytes); 1.2603 + out.flush(); 1.2604 + } catch (JSONException jsonex) { 1.2605 + Log.e(LOGTAG, "error wrapping data as a batch", jsonex); 1.2606 + } catch (IOException ioex) { 1.2607 + Log.e(LOGTAG, "error submitting data", ioex); 1.2608 + } finally { 1.2609 + urlConnection.disconnect(); 1.2610 + } 1.2611 + } catch (IOException ioex) { 1.2612 + Log.e(LOGTAG, "error submitting data", ioex); 1.2613 + } 1.2614 + } 1.2615 + }); 1.2616 + } 1.2617 + 1.2618 + private static String getRadioTypeName(int phoneType) { 1.2619 + switch (phoneType) { 1.2620 + case TelephonyManager.PHONE_TYPE_CDMA: 1.2621 + return "cdma"; 1.2622 + 1.2623 + case TelephonyManager.PHONE_TYPE_GSM: 1.2624 + return "gsm"; 1.2625 + 1.2626 + case TelephonyManager.PHONE_TYPE_NONE: 1.2627 + case TelephonyManager.PHONE_TYPE_SIP: 1.2628 + // These devices have no radio. 1.2629 + return null; 1.2630 + 1.2631 + default: 1.2632 + Log.e(LOGTAG, "", new IllegalArgumentException("Unexpected PHONE_TYPE: " + phoneType)); 1.2633 + return null; 1.2634 + } 1.2635 + } 1.2636 + 1.2637 + @Override 1.2638 + public void onProviderDisabled(String provider) 1.2639 + { 1.2640 + } 1.2641 + 1.2642 + @Override 1.2643 + public void onProviderEnabled(String provider) 1.2644 + { 1.2645 + } 1.2646 + 1.2647 + @Override 1.2648 + public void onStatusChanged(String provider, int status, Bundle extras) 1.2649 + { 1.2650 + } 1.2651 + 1.2652 + // Called when a Gecko Hal WakeLock is changed 1.2653 + public void notifyWakeLockChanged(String topic, String state) { 1.2654 + PowerManager.WakeLock wl = mWakeLocks.get(topic); 1.2655 + if (state.equals("locked-foreground") && wl == null) { 1.2656 + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); 1.2657 + wl = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, topic); 1.2658 + wl.acquire(); 1.2659 + mWakeLocks.put(topic, wl); 1.2660 + } else if (!state.equals("locked-foreground") && wl != null) { 1.2661 + wl.release(); 1.2662 + mWakeLocks.remove(topic); 1.2663 + } 1.2664 + } 1.2665 + 1.2666 + public void notifyCheckUpdateResult(String result) { 1.2667 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Update:CheckResult", result)); 1.2668 + } 1.2669 + 1.2670 + protected void geckoConnected() { 1.2671 + mLayerView.geckoConnected(); 1.2672 + mLayerView.setOverScrollMode(View.OVER_SCROLL_NEVER); 1.2673 + } 1.2674 + 1.2675 + public void setAccessibilityEnabled(boolean enabled) { 1.2676 + } 1.2677 + 1.2678 + public static class MainLayout extends RelativeLayout { 1.2679 + private TouchEventInterceptor mTouchEventInterceptor; 1.2680 + private MotionEventInterceptor mMotionEventInterceptor; 1.2681 + 1.2682 + public MainLayout(Context context, AttributeSet attrs) { 1.2683 + super(context, attrs); 1.2684 + } 1.2685 + 1.2686 + public void setTouchEventInterceptor(TouchEventInterceptor interceptor) { 1.2687 + mTouchEventInterceptor = interceptor; 1.2688 + } 1.2689 + 1.2690 + public void setMotionEventInterceptor(MotionEventInterceptor interceptor) { 1.2691 + mMotionEventInterceptor = interceptor; 1.2692 + } 1.2693 + 1.2694 + @Override 1.2695 + public boolean onInterceptTouchEvent(MotionEvent event) { 1.2696 + if (mTouchEventInterceptor != null && mTouchEventInterceptor.onInterceptTouchEvent(this, event)) { 1.2697 + return true; 1.2698 + } 1.2699 + return super.onInterceptTouchEvent(event); 1.2700 + } 1.2701 + 1.2702 + @Override 1.2703 + public boolean onTouchEvent(MotionEvent event) { 1.2704 + if (mTouchEventInterceptor != null && mTouchEventInterceptor.onTouch(this, event)) { 1.2705 + return true; 1.2706 + } 1.2707 + return super.onTouchEvent(event); 1.2708 + } 1.2709 + 1.2710 + @Override 1.2711 + public boolean onGenericMotionEvent(MotionEvent event) { 1.2712 + if (mMotionEventInterceptor != null && mMotionEventInterceptor.onInterceptMotionEvent(this, event)) { 1.2713 + return true; 1.2714 + } 1.2715 + return super.onGenericMotionEvent(event); 1.2716 + } 1.2717 + 1.2718 + @Override 1.2719 + public void setDrawingCacheEnabled(boolean enabled) { 1.2720 + // Instead of setting drawing cache in the view itself, we simply 1.2721 + // enable drawing caching on its children. This is mainly used in 1.2722 + // animations (see PropertyAnimator) 1.2723 + super.setChildrenDrawnWithCacheEnabled(enabled); 1.2724 + } 1.2725 + } 1.2726 + 1.2727 + private class FullScreenHolder extends FrameLayout { 1.2728 + 1.2729 + public FullScreenHolder(Context ctx) { 1.2730 + super(ctx); 1.2731 + } 1.2732 + 1.2733 + @Override 1.2734 + public void addView(View view, int index) { 1.2735 + /** 1.2736 + * This normally gets called when Flash adds a separate SurfaceView 1.2737 + * for the video. It is unhappy if we have the LayerView underneath 1.2738 + * it for some reason so we need to hide that. Hiding the LayerView causes 1.2739 + * its surface to be destroyed, which causes a pause composition 1.2740 + * event to be sent to Gecko. We synchronously wait for that to be 1.2741 + * processed. Simultaneously, however, Flash is waiting on a mutex so 1.2742 + * the post() below is an attempt to avoid a deadlock. 1.2743 + */ 1.2744 + super.addView(view, index); 1.2745 + 1.2746 + ThreadUtils.postToUiThread(new Runnable() { 1.2747 + @Override 1.2748 + public void run() { 1.2749 + mLayerView.hideSurface(); 1.2750 + } 1.2751 + }); 1.2752 + } 1.2753 + 1.2754 + /** 1.2755 + * The methods below are simply copied from what Android WebKit does. 1.2756 + * It wasn't ever called in my testing, but might as well 1.2757 + * keep it in case it is for some reason. The methods 1.2758 + * all return true because we don't want any events 1.2759 + * leaking out from the fullscreen view. 1.2760 + */ 1.2761 + @Override 1.2762 + public boolean onKeyDown(int keyCode, KeyEvent event) { 1.2763 + if (event.isSystem()) { 1.2764 + return super.onKeyDown(keyCode, event); 1.2765 + } 1.2766 + mFullScreenPluginView.onKeyDown(keyCode, event); 1.2767 + return true; 1.2768 + } 1.2769 + 1.2770 + @Override 1.2771 + public boolean onKeyUp(int keyCode, KeyEvent event) { 1.2772 + if (event.isSystem()) { 1.2773 + return super.onKeyUp(keyCode, event); 1.2774 + } 1.2775 + mFullScreenPluginView.onKeyUp(keyCode, event); 1.2776 + return true; 1.2777 + } 1.2778 + 1.2779 + @Override 1.2780 + public boolean onTouchEvent(MotionEvent event) { 1.2781 + return true; 1.2782 + } 1.2783 + 1.2784 + @Override 1.2785 + public boolean onTrackballEvent(MotionEvent event) { 1.2786 + mFullScreenPluginView.onTrackballEvent(event); 1.2787 + return true; 1.2788 + } 1.2789 + } 1.2790 + 1.2791 + protected NotificationClient makeNotificationClient() { 1.2792 + // Don't use a notification service; we may be killed in the background 1.2793 + // during downloads. 1.2794 + return new AppNotificationClient(getApplicationContext()); 1.2795 + } 1.2796 + 1.2797 + private int getVersionCode() { 1.2798 + int versionCode = 0; 1.2799 + try { 1.2800 + versionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode; 1.2801 + } catch (NameNotFoundException e) { 1.2802 + Log.wtf(LOGTAG, getPackageName() + " not found", e); 1.2803 + } 1.2804 + return versionCode; 1.2805 + } 1.2806 + 1.2807 + protected boolean getIsDebuggable() { 1.2808 + // Return false so Fennec doesn't appear to be debuggable. WebappImpl 1.2809 + // then overrides this and returns the value of android:debuggable for 1.2810 + // the webapp APK, so webapps get the behavior supported by this method 1.2811 + // (i.e. automatic configuration and enabling of the remote debugger). 1.2812 + return false; 1.2813 + 1.2814 + // If we ever want to expose this for Fennec, here's how we would do it: 1.2815 + // int flags = 0; 1.2816 + // try { 1.2817 + // flags = getPackageManager().getPackageInfo(getPackageName(), 0).applicationInfo.flags; 1.2818 + // } catch (NameNotFoundException e) { 1.2819 + // Log.wtf(LOGTAG, getPackageName() + " not found", e); 1.2820 + // } 1.2821 + // return (flags & android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE) != 0; 1.2822 + } 1.2823 + 1.2824 + // FHR reason code for a session end prior to a restart for a 1.2825 + // locale change. 1.2826 + private static final String SESSION_END_LOCALE_CHANGED = "L"; 1.2827 + 1.2828 + /** 1.2829 + * Use BrowserLocaleManager to change our persisted and current locales, 1.2830 + * and poke HealthRecorder to tell it of our changed state. 1.2831 + */ 1.2832 + private void setLocale(final String locale) { 1.2833 + if (locale == null) { 1.2834 + return; 1.2835 + } 1.2836 + final String resultant = BrowserLocaleManager.getInstance().setSelectedLocale(this, locale); 1.2837 + if (resultant == null) { 1.2838 + return; 1.2839 + } 1.2840 + 1.2841 + final boolean startNewSession = true; 1.2842 + final boolean shouldRestart = false; 1.2843 + 1.2844 + // If the HealthRecorder is not yet initialized (unlikely), the locale change won't 1.2845 + // trigger a session transition and subsequent events will be recorded in an environment 1.2846 + // with the wrong locale. 1.2847 + final HealthRecorder rec = mHealthRecorder; 1.2848 + if (rec != null) { 1.2849 + rec.onAppLocaleChanged(resultant); 1.2850 + rec.onEnvironmentChanged(startNewSession, SESSION_END_LOCALE_CHANGED); 1.2851 + } 1.2852 + 1.2853 + if (!shouldRestart) { 1.2854 + ThreadUtils.postToUiThread(new Runnable() { 1.2855 + @Override 1.2856 + public void run() { 1.2857 + GeckoApp.this.onLocaleReady(resultant); 1.2858 + } 1.2859 + }); 1.2860 + return; 1.2861 + } 1.2862 + 1.2863 + // Do this in the background so that the health recorder has its 1.2864 + // time to finish. 1.2865 + ThreadUtils.postToBackgroundThread(new Runnable() { 1.2866 + @Override 1.2867 + public void run() { 1.2868 + GeckoApp.this.doRestart(); 1.2869 + GeckoApp.this.finish(); 1.2870 + } 1.2871 + }); 1.2872 + } 1.2873 + 1.2874 + private void setSystemUiVisible(final boolean visible) { 1.2875 + if (Build.VERSION.SDK_INT < 14) { 1.2876 + return; 1.2877 + } 1.2878 + 1.2879 + ThreadUtils.postToUiThread(new Runnable() { 1.2880 + @Override 1.2881 + public void run() { 1.2882 + if (visible) { 1.2883 + mMainLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); 1.2884 + } else { 1.2885 + mMainLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); 1.2886 + } 1.2887 + } 1.2888 + }); 1.2889 + } 1.2890 + 1.2891 + protected HealthRecorder createHealthRecorder(final Context context, 1.2892 + final String profilePath, 1.2893 + final EventDispatcher dispatcher, 1.2894 + final String osLocale, 1.2895 + final String appLocale, 1.2896 + final SessionInformation previousSession) { 1.2897 + // GeckoApp does not need to record any health information - return a stub. 1.2898 + return new StubbedHealthRecorder(); 1.2899 + } 1.2900 +}