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