mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-31 14:15:30 +00:00
2786 lines
105 KiB
Java
2786 lines
105 KiB
Java
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
package org.mozilla.gecko;
|
|
|
|
import org.mozilla.gecko.background.announcements.AnnouncementsBroadcastService;
|
|
import org.mozilla.gecko.db.BrowserDB;
|
|
import org.mozilla.gecko.db.BrowserContract;
|
|
import org.mozilla.gecko.gfx.Layer;
|
|
import org.mozilla.gecko.gfx.LayerView;
|
|
import org.mozilla.gecko.gfx.PluginLayer;
|
|
import org.mozilla.gecko.gfx.PointUtils;
|
|
import org.mozilla.gecko.ui.PanZoomController;
|
|
import org.mozilla.gecko.util.GeckoAsyncTask;
|
|
import org.mozilla.gecko.util.GeckoBackgroundThread;
|
|
import org.mozilla.gecko.util.GeckoEventListener;
|
|
import org.mozilla.gecko.util.GeckoEventResponder;
|
|
import org.mozilla.gecko.GeckoAccessibility;
|
|
import org.mozilla.gecko.updater.UpdateServiceHelper;
|
|
import org.mozilla.gecko.updater.UpdateService;
|
|
|
|
import org.json.JSONArray;
|
|
import org.json.JSONException;
|
|
import org.json.JSONObject;
|
|
|
|
import android.app.Activity;
|
|
import android.app.AlertDialog;
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.content.SharedPreferences;
|
|
import android.content.pm.ActivityInfo;
|
|
import android.content.pm.ApplicationInfo;
|
|
import android.content.pm.PackageInfo;
|
|
import android.content.pm.PackageManager;
|
|
import android.content.pm.PackageManager.NameNotFoundException;
|
|
import android.content.pm.ResolveInfo;
|
|
import android.content.pm.ServiceInfo;
|
|
import android.content.pm.Signature;
|
|
import android.content.res.Configuration;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.BitmapFactory;
|
|
import android.graphics.Color;
|
|
import android.graphics.PointF;
|
|
import android.graphics.Rect;
|
|
import android.graphics.drawable.BitmapDrawable;
|
|
import android.hardware.Sensor;
|
|
import android.hardware.SensorEvent;
|
|
import android.hardware.SensorEventListener;
|
|
import android.location.Location;
|
|
import android.location.LocationListener;
|
|
import android.net.Uri;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.os.Environment;
|
|
import android.os.Handler;
|
|
import android.os.PowerManager;
|
|
import android.os.StrictMode;
|
|
import android.os.SystemClock;
|
|
import android.text.TextUtils;
|
|
import android.util.AttributeSet;
|
|
import android.util.DisplayMetrics;
|
|
import android.util.Log;
|
|
import android.util.SparseBooleanArray;
|
|
import android.view.Gravity;
|
|
import android.view.KeyEvent;
|
|
import android.view.LayoutInflater;
|
|
import android.view.Menu;
|
|
import android.view.MenuInflater;
|
|
import android.view.MenuItem;
|
|
import android.view.MotionEvent;
|
|
import android.view.SurfaceHolder;
|
|
import android.view.SurfaceView;
|
|
import android.view.View;
|
|
import android.view.ViewConfiguration;
|
|
import android.view.ViewGroup;
|
|
import android.view.Window;
|
|
import android.view.WindowManager;
|
|
|
|
import android.view.accessibility.AccessibilityEvent;
|
|
import android.view.accessibility.AccessibilityManager;
|
|
import android.widget.AbsoluteLayout;
|
|
import android.widget.FrameLayout;
|
|
import android.widget.LinearLayout;
|
|
import android.widget.ListView;
|
|
import android.widget.RelativeLayout;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.File;
|
|
import java.io.FileOutputStream;
|
|
import java.io.FileReader;
|
|
import java.io.IOException;
|
|
import java.io.OutputStream;
|
|
import java.lang.reflect.Method;
|
|
import java.net.HttpURLConnection;
|
|
import java.net.URL;
|
|
import java.nio.ByteBuffer;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.Comparator;
|
|
import java.util.HashMap;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.concurrent.CopyOnWriteArrayList;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
abstract public class GeckoApp
|
|
extends GeckoActivity
|
|
implements GeckoEventListener, SensorEventListener, LocationListener,
|
|
Tabs.OnTabsChangedListener, GeckoEventResponder
|
|
{
|
|
private static final String LOGTAG = "GeckoApp";
|
|
|
|
public static enum StartupMode {
|
|
NORMAL,
|
|
NEW_VERSION,
|
|
NEW_PROFILE
|
|
}
|
|
|
|
private static enum StartupAction {
|
|
NORMAL, /* normal application start */
|
|
URL, /* launched with a passed URL */
|
|
PREFETCH, /* launched with a passed URL that we prefetch */
|
|
REDIRECTOR /* launched with a passed URL in our redirect list */
|
|
}
|
|
|
|
public static final String ACTION_ALERT_CLICK = "org.mozilla.gecko.ACTION_ALERT_CLICK";
|
|
public static final String ACTION_ALERT_CLEAR = "org.mozilla.gecko.ACTION_ALERT_CLEAR";
|
|
public static final String ACTION_ALERT_CALLBACK = "org.mozilla.gecko.ACTION_ALERT_CALLBACK";
|
|
public static final String ACTION_ANNOUNCEMENTS_PREF = "org.mozilla.gecko.ANNOUNCEMENTS_PREF";
|
|
public static final String ACTION_WEBAPP_PREFIX = "org.mozilla.gecko.WEBAPP";
|
|
public static final String ACTION_DEBUG = "org.mozilla.gecko.DEBUG";
|
|
public static final String ACTION_BOOKMARK = "org.mozilla.gecko.BOOKMARK";
|
|
public static final String ACTION_LOAD = "org.mozilla.gecko.LOAD";
|
|
public static final String ACTION_INIT_PW = "org.mozilla.gecko.INIT_PW";
|
|
public static final String ACTION_WIDGET = "org.mozilla.gecko.WIDGET";
|
|
public static final String SAVED_STATE_IN_BACKGROUND = "inBackground";
|
|
public static final String SAVED_STATE_PRIVATE_SESSION = "privateSession";
|
|
|
|
public static final String PREFS_NAME = "GeckoApp";
|
|
public static final String PREFS_OOM_EXCEPTION = "OOMException";
|
|
public static final String PREFS_WAS_STOPPED = "wasStopped";
|
|
|
|
static public final int RESTORE_NONE = 0;
|
|
static public final int RESTORE_OOM = 1;
|
|
static public final int RESTORE_CRASH = 2;
|
|
|
|
StartupMode mStartupMode = null;
|
|
protected LinearLayout mMainLayout;
|
|
protected RelativeLayout mGeckoLayout;
|
|
public View getView() { return mGeckoLayout; }
|
|
public SurfaceView cameraView;
|
|
public static GeckoApp mAppContext;
|
|
public boolean mDOMFullScreen = false;
|
|
protected MenuPresenter mMenuPresenter;
|
|
protected MenuPanel mMenuPanel;
|
|
protected Menu mMenu;
|
|
private static GeckoThread sGeckoThread;
|
|
public Handler mMainHandler;
|
|
private GeckoProfile mProfile;
|
|
public static int mOrientation;
|
|
private boolean mIsRestoringActivity;
|
|
private String mCurrentResponse = "";
|
|
public static boolean sIsUsingCustomProfile = false;
|
|
|
|
private PromptService mPromptService;
|
|
private Favicons mFavicons;
|
|
private TextSelection mTextSelection;
|
|
|
|
protected DoorHangerPopup mDoorHangerPopup;
|
|
protected FormAssistPopup mFormAssistPopup;
|
|
protected TabsPanel mTabsPanel;
|
|
|
|
protected LayerView mLayerView;
|
|
private AbsoluteLayout mPluginContainer;
|
|
|
|
private FullScreenHolder mFullScreenPluginContainer;
|
|
private View mFullScreenPluginView;
|
|
|
|
private HashMap<String, PowerManager.WakeLock> mWakeLocks = new HashMap<String, PowerManager.WakeLock>();
|
|
|
|
protected int mRestoreMode = RESTORE_NONE;
|
|
protected boolean mInitialized = false;
|
|
protected Telemetry.Timer mAboutHomeStartupTimer;
|
|
private Telemetry.Timer mJavaUiStartupTimer;
|
|
private Telemetry.Timer mGeckoReadyStartupTimer;
|
|
|
|
private String mPrivateBrowsingSession;
|
|
|
|
public enum LaunchState {Launching, WaitForDebugger,
|
|
Launched, GeckoRunning, GeckoExiting};
|
|
private static LaunchState sLaunchState = LaunchState.Launching;
|
|
|
|
abstract public int getLayout();
|
|
abstract public boolean hasTabsSideBar();
|
|
abstract protected String getDefaultProfileName();
|
|
|
|
public static boolean checkLaunchState(LaunchState checkState) {
|
|
synchronized(sLaunchState) {
|
|
return sLaunchState == checkState;
|
|
}
|
|
}
|
|
|
|
static void setLaunchState(LaunchState setState) {
|
|
synchronized(sLaunchState) {
|
|
sLaunchState = setState;
|
|
}
|
|
}
|
|
|
|
// if mLaunchState is equal to checkState this sets mLaunchState to setState
|
|
// and return true. Otherwise we return false.
|
|
static boolean checkAndSetLaunchState(LaunchState checkState, LaunchState setState) {
|
|
synchronized(sLaunchState) {
|
|
if (sLaunchState != checkState)
|
|
return false;
|
|
sLaunchState = setState;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void toggleChrome(final Boolean aShow) { }
|
|
|
|
void focusChrome() { }
|
|
|
|
public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
|
|
// When a tab is closed, it is always unselected first.
|
|
// When a tab is unselected, another tab is always selected first.
|
|
switch(msg) {
|
|
case UNSELECTED:
|
|
hidePlugins(tab);
|
|
break;
|
|
|
|
case LOCATION_CHANGE:
|
|
// We only care about location change for the selected tab.
|
|
if (!Tabs.getInstance().isSelectedTab(tab))
|
|
break;
|
|
// Fall through...
|
|
case SELECTED:
|
|
invalidateOptionsMenu();
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void refreshChrome() { }
|
|
|
|
public static final String PLUGIN_ACTION = "android.webkit.PLUGIN";
|
|
|
|
/**
|
|
* A plugin that wish to be loaded in the WebView must provide this permission
|
|
* in their AndroidManifest.xml.
|
|
*/
|
|
public static final String PLUGIN_PERMISSION = "android.webkit.permission.PLUGIN";
|
|
|
|
private static final String PLUGIN_SYSTEM_LIB = "/system/lib/plugins/";
|
|
|
|
private static final String PLUGIN_TYPE = "type";
|
|
private static final String TYPE_NATIVE = "native";
|
|
public ArrayList<PackageInfo> mPackageInfoCache = new ArrayList<PackageInfo>();
|
|
|
|
// Returns null if plugins are blocked on the device.
|
|
String[] getPluginDirectories() {
|
|
|
|
// An awful hack to detect Tegra devices. Easiest way to do it without spinning up a EGL context.
|
|
boolean isTegra = (new File("/system/lib/hw/gralloc.tegra.so")).exists();
|
|
if (isTegra) {
|
|
// disable Flash on Tegra ICS with CM9 and other custom firmware (bug 736421)
|
|
File vfile = new File("/proc/version");
|
|
FileReader vreader = null;
|
|
try {
|
|
if (vfile.canRead()) {
|
|
vreader = new FileReader(vfile);
|
|
String version = new BufferedReader(vreader).readLine();
|
|
if (version.indexOf("CM9") != -1 ||
|
|
version.indexOf("cyanogen") != -1 ||
|
|
version.indexOf("Nova") != -1)
|
|
{
|
|
Log.w(LOGTAG, "Blocking plugins because of Tegra 2 + unofficial ICS bug (bug 736421)");
|
|
return null;
|
|
}
|
|
}
|
|
} catch (IOException ex) {
|
|
// nothing
|
|
} finally {
|
|
try {
|
|
if (vreader != null) {
|
|
vreader.close();
|
|
}
|
|
} catch (IOException ex) {
|
|
// nothing
|
|
}
|
|
}
|
|
}
|
|
|
|
ArrayList<String> directories = new ArrayList<String>();
|
|
PackageManager pm = mAppContext.getPackageManager();
|
|
List<ResolveInfo> plugins = pm.queryIntentServices(new Intent(PLUGIN_ACTION),
|
|
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
|
|
|
|
synchronized(mPackageInfoCache) {
|
|
|
|
// clear the list of existing packageInfo objects
|
|
mPackageInfoCache.clear();
|
|
|
|
|
|
for (ResolveInfo info : plugins) {
|
|
|
|
// retrieve the plugin's service information
|
|
ServiceInfo serviceInfo = info.serviceInfo;
|
|
if (serviceInfo == null) {
|
|
Log.w(LOGTAG, "Ignoring bad plugin.");
|
|
continue;
|
|
}
|
|
|
|
// Blacklist HTC's flash lite.
|
|
// See bug #704516 - We're not quite sure what Flash Lite does,
|
|
// but loading it causes Flash to give errors and fail to draw.
|
|
if (serviceInfo.packageName.equals("com.htc.flashliteplugin")) {
|
|
Log.w(LOGTAG, "Skipping HTC's flash lite plugin");
|
|
continue;
|
|
}
|
|
|
|
|
|
// Retrieve information from the plugin's manifest.
|
|
PackageInfo pkgInfo;
|
|
try {
|
|
pkgInfo = pm.getPackageInfo(serviceInfo.packageName,
|
|
PackageManager.GET_PERMISSIONS
|
|
| PackageManager.GET_SIGNATURES);
|
|
} catch (Exception e) {
|
|
Log.w(LOGTAG, "Can't find plugin: " + serviceInfo.packageName);
|
|
continue;
|
|
}
|
|
|
|
if (pkgInfo == null) {
|
|
Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Could not load package information.");
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* find the location of the plugin's shared library. The default
|
|
* is to assume the app is either a user installed app or an
|
|
* updated system app. In both of these cases the library is
|
|
* stored in the app's data directory.
|
|
*/
|
|
String directory = pkgInfo.applicationInfo.dataDir + "/lib";
|
|
final int appFlags = pkgInfo.applicationInfo.flags;
|
|
final int updatedSystemFlags = ApplicationInfo.FLAG_SYSTEM |
|
|
ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
|
|
|
|
// preloaded system app with no user updates
|
|
if ((appFlags & updatedSystemFlags) == ApplicationInfo.FLAG_SYSTEM) {
|
|
directory = PLUGIN_SYSTEM_LIB + pkgInfo.packageName;
|
|
}
|
|
|
|
// check if the plugin has the required permissions
|
|
String permissions[] = pkgInfo.requestedPermissions;
|
|
if (permissions == null) {
|
|
Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Does not have required permission.");
|
|
continue;
|
|
}
|
|
boolean permissionOk = false;
|
|
for (String permit : permissions) {
|
|
if (PLUGIN_PERMISSION.equals(permit)) {
|
|
permissionOk = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!permissionOk) {
|
|
Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Does not have required permission (2).");
|
|
continue;
|
|
}
|
|
|
|
// check to ensure the plugin is properly signed
|
|
Signature signatures[] = pkgInfo.signatures;
|
|
if (signatures == null) {
|
|
Log.w(LOGTAG, "Not loading plugin: " + serviceInfo.packageName + ". Not signed.");
|
|
continue;
|
|
}
|
|
|
|
// determine the type of plugin from the manifest
|
|
if (serviceInfo.metaData == null) {
|
|
Log.e(LOGTAG, "The plugin '" + serviceInfo.name + "' has no defined type.");
|
|
continue;
|
|
}
|
|
|
|
String pluginType = serviceInfo.metaData.getString(PLUGIN_TYPE);
|
|
if (!TYPE_NATIVE.equals(pluginType)) {
|
|
Log.e(LOGTAG, "Unrecognized plugin type: " + pluginType);
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
Class<?> cls = getPluginClass(serviceInfo.packageName, serviceInfo.name);
|
|
|
|
//TODO implement any requirements of the plugin class here!
|
|
boolean classFound = true;
|
|
|
|
if (!classFound) {
|
|
Log.e(LOGTAG, "The plugin's class' " + serviceInfo.name + "' does not extend the appropriate class.");
|
|
continue;
|
|
}
|
|
|
|
} catch (NameNotFoundException e) {
|
|
Log.e(LOGTAG, "Can't find plugin: " + serviceInfo.packageName);
|
|
continue;
|
|
} catch (ClassNotFoundException e) {
|
|
Log.e(LOGTAG, "Can't find plugin's class: " + serviceInfo.name);
|
|
continue;
|
|
}
|
|
|
|
// if all checks have passed then make the plugin available
|
|
mPackageInfoCache.add(pkgInfo);
|
|
directories.add(directory);
|
|
}
|
|
}
|
|
|
|
return directories.toArray(new String[directories.size()]);
|
|
}
|
|
|
|
String getPluginPackage(String pluginLib) {
|
|
|
|
if (pluginLib == null || pluginLib.length() == 0) {
|
|
return null;
|
|
}
|
|
|
|
synchronized(mPackageInfoCache) {
|
|
for (PackageInfo pkgInfo : mPackageInfoCache) {
|
|
if (pluginLib.contains(pkgInfo.packageName)) {
|
|
return pkgInfo.packageName;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
synchronized Favicons getFavicons() {
|
|
if (mFavicons == null)
|
|
mFavicons = new Favicons(this);
|
|
|
|
return mFavicons;
|
|
}
|
|
|
|
Class<?> getPluginClass(String packageName, String className)
|
|
throws NameNotFoundException, ClassNotFoundException {
|
|
Context pluginContext = mAppContext.createPackageContext(packageName,
|
|
Context.CONTEXT_INCLUDE_CODE |
|
|
Context.CONTEXT_IGNORE_SECURITY);
|
|
ClassLoader pluginCL = pluginContext.getClassLoader();
|
|
return pluginCL.loadClass(className);
|
|
}
|
|
|
|
@Override
|
|
public void invalidateOptionsMenu() {
|
|
if (mMenu == null)
|
|
return;
|
|
|
|
onPrepareOptionsMenu(mMenu);
|
|
|
|
if (Build.VERSION.SDK_INT >= 11)
|
|
super.invalidateOptionsMenu();
|
|
}
|
|
|
|
@Override
|
|
public boolean onCreateOptionsMenu(Menu menu) {
|
|
mMenu = menu;
|
|
|
|
MenuInflater inflater = getMenuInflater();
|
|
inflater.inflate(R.menu.gecko_app_menu, mMenu);
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public MenuInflater getMenuInflater() {
|
|
if (Build.VERSION.SDK_INT >= 11)
|
|
return new GeckoMenuInflater(mAppContext);
|
|
else
|
|
return super.getMenuInflater();
|
|
}
|
|
|
|
public MenuPanel getMenuPanel() {
|
|
return mMenuPanel;
|
|
}
|
|
|
|
public MenuPresenter getMenuPresenter() {
|
|
return mMenuPresenter;
|
|
}
|
|
|
|
// MenuPanel holds the scrollable Menu
|
|
public static class MenuPanel extends LinearLayout {
|
|
public MenuPanel(Context context, AttributeSet attrs) {
|
|
super(context, attrs);
|
|
setLayoutParams(new ViewGroup.LayoutParams((int) context.getResources().getDimension(R.dimen.menu_item_row_width),
|
|
ViewGroup.LayoutParams.WRAP_CONTENT));
|
|
}
|
|
|
|
@Override
|
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
|
|
// heightPixels changes during rotation.
|
|
DisplayMetrics metrics = GeckoApp.mAppContext.getResources().getDisplayMetrics();
|
|
int restrictedHeightSpec = MeasureSpec.makeMeasureSpec((int) (0.75 * metrics.heightPixels), MeasureSpec.AT_MOST);
|
|
|
|
super.onMeasure(widthMeasureSpec, restrictedHeightSpec);
|
|
}
|
|
|
|
@Override
|
|
public boolean dispatchPopulateAccessibilityEvent (AccessibilityEvent event) {
|
|
if (Build.VERSION.SDK_INT >= 14) // Build.VERSION_CODES.ICE_CREAM_SANDWICH
|
|
onPopulateAccessibilityEvent(event);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// MenuPresenter takes care of proper animation and inflation.
|
|
public class MenuPresenter {
|
|
GeckoApp mActivity;
|
|
|
|
public MenuPresenter(GeckoApp activity) {
|
|
mActivity = activity;
|
|
}
|
|
|
|
public void show(GeckoMenu menu) {
|
|
MenuPanel panel = mActivity.getMenuPanel();
|
|
panel.removeAllViews();
|
|
panel.addView(menu);
|
|
|
|
mActivity.openOptionsMenu();
|
|
}
|
|
|
|
public void onOptionsMenuClosed() {
|
|
MenuPanel panel = mActivity.getMenuPanel();
|
|
panel.removeAllViews();
|
|
panel.addView((GeckoMenu) mMenu);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public View onCreatePanelView(int featureId) {
|
|
if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL) {
|
|
if (mMenuPanel == null) {
|
|
mMenuPanel = new MenuPanel(mAppContext, null);
|
|
mMenuPresenter = new MenuPresenter(this);
|
|
} else {
|
|
// Prepare the panel everytime before showing the menu.
|
|
onPreparePanel(featureId, mMenuPanel, mMenu);
|
|
}
|
|
|
|
return mMenuPanel;
|
|
}
|
|
|
|
return super.onCreatePanelView(featureId);
|
|
}
|
|
|
|
@Override
|
|
public boolean onCreatePanelMenu(int featureId, Menu menu) {
|
|
if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL) {
|
|
if (mMenuPanel == null) {
|
|
mMenuPanel = (MenuPanel) onCreatePanelView(featureId);
|
|
}
|
|
|
|
GeckoMenu gMenu = new GeckoMenu(mAppContext, null);
|
|
menu = gMenu;
|
|
mMenuPanel.addView(gMenu);
|
|
|
|
return onCreateOptionsMenu(menu);
|
|
}
|
|
|
|
return super.onCreatePanelMenu(featureId, menu);
|
|
}
|
|
|
|
@Override
|
|
public boolean onPreparePanel(int featureId, View view, Menu menu) {
|
|
if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL)
|
|
return onPrepareOptionsMenu(menu);
|
|
|
|
return super.onPreparePanel(featureId, view, menu);
|
|
}
|
|
|
|
@Override
|
|
public boolean onMenuOpened(int featureId, Menu menu) {
|
|
if (Build.VERSION.SDK_INT >= 11 && featureId == Window.FEATURE_OPTIONS_PANEL) {
|
|
if (mMenu == null) {
|
|
onCreatePanelMenu(featureId, menu);
|
|
onPreparePanel(featureId, mMenuPanel, mMenu);
|
|
}
|
|
|
|
// Scroll custom menu to the top
|
|
if (mMenuPanel != null)
|
|
mMenuPanel.scrollTo(0, 0);
|
|
|
|
return true;
|
|
}
|
|
|
|
return super.onMenuOpened(featureId, menu);
|
|
}
|
|
|
|
@Override
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
switch (item.getItemId()) {
|
|
case R.id.quit:
|
|
synchronized(sLaunchState) {
|
|
if (sLaunchState == LaunchState.GeckoRunning)
|
|
GeckoAppShell.notifyGeckoOfEvent(
|
|
GeckoEvent.createBroadcastEvent("Browser:Quit", null));
|
|
else
|
|
System.exit(0);
|
|
sLaunchState = LaunchState.GeckoExiting;
|
|
}
|
|
return true;
|
|
default:
|
|
return super.onOptionsItemSelected(item);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onOptionsMenuClosed(Menu menu) {
|
|
if (Build.VERSION.SDK_INT >= 11)
|
|
mMenuPresenter.onOptionsMenuClosed();
|
|
}
|
|
|
|
@Override
|
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
|
// Custom Menu should be opened when hardware menu key is pressed.
|
|
if (Build.VERSION.SDK_INT >= 11 && keyCode == KeyEvent.KEYCODE_MENU) {
|
|
openOptionsMenu();
|
|
return true;
|
|
}
|
|
|
|
return super.onKeyDown(keyCode, event);
|
|
}
|
|
|
|
protected void shareCurrentUrl() {
|
|
Tab tab = Tabs.getInstance().getSelectedTab();
|
|
if (tab == null)
|
|
return;
|
|
|
|
String url = tab.getURL();
|
|
if (url == null)
|
|
return;
|
|
|
|
if (ReaderModeUtils.isAboutReader(url))
|
|
url = ReaderModeUtils.getUrlFromAboutReader(url);
|
|
|
|
if (Build.VERSION.SDK_INT >= 11) {
|
|
final Intent shareIntent = new Intent(Intent.ACTION_SEND);
|
|
shareIntent.putExtra(Intent.EXTRA_TEXT, url);
|
|
shareIntent.putExtra(Intent.EXTRA_SUBJECT, tab.getDisplayTitle());
|
|
shareIntent.setType("text/plain");
|
|
|
|
PackageManager pm = getPackageManager();
|
|
List<ResolveInfo> activities = pm.queryIntentActivities(shareIntent, 0);
|
|
Collections.sort(activities, new Comparator<ResolveInfo>() {
|
|
@Override
|
|
public int compare(ResolveInfo one, ResolveInfo two) {
|
|
return one.preferredOrder - two.preferredOrder;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object info) {
|
|
return this.equals(info);
|
|
}
|
|
});
|
|
|
|
GeckoSubMenu menu = new GeckoSubMenu(mAppContext, null);
|
|
for (ResolveInfo activity : activities) {
|
|
final ActivityInfo activityInfo = activity.activityInfo;
|
|
|
|
MenuItem item = menu.add(activity.loadLabel(pm));
|
|
item.setIcon(activity.loadIcon(pm));
|
|
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
|
|
@Override
|
|
public boolean onMenuItemClick(MenuItem item) {
|
|
shareIntent.setComponent(new ComponentName(activityInfo.packageName, activityInfo.name));
|
|
startActivity(shareIntent);
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
|
|
mMenuPresenter.show(menu);
|
|
} else {
|
|
GeckoAppShell.openUriExternal(url, "text/plain", "", "",
|
|
Intent.ACTION_SEND, tab.getDisplayTitle());
|
|
}
|
|
}
|
|
|
|
protected void onSaveInstanceState(Bundle outState) {
|
|
super.onSaveInstanceState(outState);
|
|
|
|
if (outState == null)
|
|
outState = new Bundle();
|
|
|
|
boolean inBackground =
|
|
((GeckoApplication)getApplication()).isApplicationInBackground();
|
|
|
|
outState.putBoolean(SAVED_STATE_IN_BACKGROUND, inBackground);
|
|
outState.putString(SAVED_STATE_PRIVATE_SESSION, mPrivateBrowsingSession);
|
|
}
|
|
|
|
void getAndProcessThumbnailForTab(final Tab tab) {
|
|
if ("about:home".equals(tab.getURL())) {
|
|
tab.updateThumbnail(null);
|
|
return;
|
|
}
|
|
|
|
if (tab.getState() == Tab.STATE_DELAYED) {
|
|
if (tab.getURL() != null) {
|
|
byte[] thumbnail = BrowserDB.getThumbnailForUrl(getContentResolver(), tab.getURL());
|
|
if (thumbnail != null)
|
|
processThumbnail(tab, null, thumbnail);
|
|
}
|
|
return;
|
|
}
|
|
|
|
int dw = Tabs.getThumbnailWidth();
|
|
int dh = Tabs.getThumbnailHeight();
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createScreenshotEvent(tab.getId(), 0, 0, 0, 0, 0, 0, dw, dh, dw, dh, ScreenshotHandler.SCREENSHOT_THUMBNAIL, tab.getThumbnailBuffer()));
|
|
}
|
|
|
|
void handleThumbnailData(Tab tab, ByteBuffer data) {
|
|
if (shouldUpdateThumbnail(tab)) {
|
|
Bitmap b = tab.getThumbnailBitmap();
|
|
b.copyPixelsFromBuffer(data);
|
|
processThumbnail(tab, b, null);
|
|
}
|
|
}
|
|
|
|
void processThumbnail(Tab thumbnailTab, Bitmap bitmap, byte[] compressed) {
|
|
try {
|
|
if (bitmap == null) {
|
|
if (compressed == null) {
|
|
Log.w(LOGTAG, "processThumbnail: one of bitmap or compressed must be non-null!");
|
|
return;
|
|
}
|
|
bitmap = BitmapFactory.decodeByteArray(compressed, 0, compressed.length);
|
|
}
|
|
thumbnailTab.updateThumbnail(bitmap);
|
|
} catch (OutOfMemoryError ome) {
|
|
Log.w(LOGTAG, "decoding byte array ran out of memory", ome);
|
|
}
|
|
}
|
|
|
|
private boolean shouldUpdateThumbnail(Tab tab) {
|
|
return (Tabs.getInstance().isSelectedTab(tab) || areTabsShown());
|
|
}
|
|
|
|
public void hideFormAssistPopup() {
|
|
if (mFormAssistPopup != null)
|
|
mFormAssistPopup.hide();
|
|
}
|
|
|
|
void handleSecurityChange(final int tabId, final JSONObject identityData) {
|
|
final Tab tab = Tabs.getInstance().getTab(tabId);
|
|
if (tab == null)
|
|
return;
|
|
|
|
tab.updateIdentityData(identityData);
|
|
}
|
|
|
|
void handleReaderEnabled(final int tabId) {
|
|
final Tab tab = Tabs.getInstance().getTab(tabId);
|
|
if (tab == null)
|
|
return;
|
|
|
|
tab.setReaderEnabled(true);
|
|
}
|
|
|
|
void handleFaviconRequest(final String url) {
|
|
(new GeckoAsyncTask<Void, Void, String>(mAppContext, GeckoAppShell.getHandler()) {
|
|
@Override
|
|
public String doInBackground(Void... params) {
|
|
return getFavicons().getFaviconUrlForPageUrl(url);
|
|
}
|
|
|
|
@Override
|
|
public void onPostExecute(String faviconUrl) {
|
|
JSONObject args = new JSONObject();
|
|
|
|
if (faviconUrl != null) {
|
|
try {
|
|
args.put("url", url);
|
|
args.put("faviconUrl", faviconUrl);
|
|
} catch (JSONException e) {
|
|
Log.w(LOGTAG, "Error building JSON favicon arguments.", e);
|
|
}
|
|
}
|
|
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Reader:FaviconReturn", args.toString()));
|
|
}
|
|
}).execute();
|
|
}
|
|
|
|
void handleLoadError(final int tabId, final String uri, final String title) {
|
|
final Tab tab = Tabs.getInstance().getTab(tabId);
|
|
if (tab == null)
|
|
return;
|
|
|
|
// When a load error occurs, the URLBar can get corrupt so we reset it
|
|
Tabs.getInstance().notifyListeners(tab, Tabs.TabEvents.LOAD_ERROR);
|
|
}
|
|
|
|
void handlePageShow(final int tabId) { }
|
|
|
|
void handleClearHistory() {
|
|
BrowserDB.clearHistory(getContentResolver());
|
|
getFavicons().clearFavicons();
|
|
}
|
|
|
|
/**
|
|
* This function might perform synchronous IO. Don't call it
|
|
* from the main thread.
|
|
*/
|
|
public StartupMode getStartupMode() {
|
|
|
|
synchronized(this) {
|
|
if (mStartupMode != null)
|
|
return mStartupMode;
|
|
|
|
String packageName = getPackageName();
|
|
SharedPreferences settings = getPreferences(Activity.MODE_PRIVATE);
|
|
|
|
// This key should be profile-dependent. For now, we're simply hardcoding
|
|
// the "default" profile here.
|
|
String profileName = getDefaultProfileName();
|
|
if (profileName == null)
|
|
profileName = "default";
|
|
String keyName = packageName + "." + profileName + ".startup_version";
|
|
String appVersion = null;
|
|
|
|
try {
|
|
PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName, 0);
|
|
appVersion = pkgInfo.versionName;
|
|
} catch(NameNotFoundException nnfe) {
|
|
// If, for some reason, we can't fetch the app version
|
|
// we fallback to NORMAL startup mode.
|
|
mStartupMode = StartupMode.NORMAL;
|
|
return mStartupMode;
|
|
}
|
|
|
|
String startupVersion = settings.getString(keyName, null);
|
|
if (startupVersion == null) {
|
|
mStartupMode = StartupMode.NEW_PROFILE;
|
|
} else {
|
|
if (startupVersion.equals(appVersion))
|
|
mStartupMode = StartupMode.NORMAL;
|
|
else
|
|
mStartupMode = StartupMode.NEW_VERSION;
|
|
}
|
|
|
|
if (mStartupMode != StartupMode.NORMAL)
|
|
settings.edit().putString(keyName, appVersion).commit();
|
|
|
|
Log.i(LOGTAG, "Startup mode: " + mStartupMode);
|
|
|
|
return mStartupMode;
|
|
}
|
|
}
|
|
|
|
void addTab() { }
|
|
|
|
public void showLocalTabs() { }
|
|
|
|
public void showRemoteTabs() { }
|
|
|
|
private void showTabs(TabsPanel.Panel panel) { }
|
|
|
|
public void hideTabs() { }
|
|
|
|
/**
|
|
* Close the tab UI indirectly (not as the result of a direct user
|
|
* action). This does not force the UI to close; for example in Firefox
|
|
* tablet mode it will remain open unless the user explicitly closes it.
|
|
*
|
|
* @return True if the tab UI was hidden.
|
|
*/
|
|
public boolean autoHideTabs() { return false; }
|
|
|
|
public boolean areTabsShown() { return false; }
|
|
|
|
public boolean hasPermanentMenuKey() {
|
|
boolean hasMenu = true;
|
|
|
|
if (Build.VERSION.SDK_INT >= 11)
|
|
hasMenu = false;
|
|
|
|
if (Build.VERSION.SDK_INT >= 14)
|
|
hasMenu = ViewConfiguration.get(GeckoApp.mAppContext).hasPermanentMenuKey();
|
|
|
|
return hasMenu;
|
|
}
|
|
|
|
public void handleMessage(String event, JSONObject message) {
|
|
try {
|
|
if (event.equals("Toast:Show")) {
|
|
final String msg = message.getString("message");
|
|
final String duration = message.getString("duration");
|
|
handleShowToast(msg, duration);
|
|
} else if (event.equals("DOMContentLoaded")) {
|
|
final int tabId = message.getInt("tabID");
|
|
final String backgroundColor = message.getString("bgColor");
|
|
handleContentLoaded(tabId);
|
|
Tab tab = Tabs.getInstance().getTab(tabId);
|
|
if (backgroundColor != null) {
|
|
tab.setCheckerboardColor(backgroundColor);
|
|
} else {
|
|
// Default to white if no color is given
|
|
tab.setCheckerboardColor(Color.WHITE);
|
|
}
|
|
|
|
// Sync up the layer view and the tab if the tab is
|
|
// currently displayed.
|
|
LayerView layerView = mLayerView;
|
|
if (layerView != null && Tabs.getInstance().isSelectedTab(tab)) {
|
|
layerView.setCheckerboardColor(tab.getCheckerboardColor());
|
|
}
|
|
} else if (event.equals("DOMTitleChanged")) {
|
|
final int tabId = message.getInt("tabID");
|
|
final String title = message.getString("title");
|
|
handleTitleChanged(tabId, title);
|
|
} else if (event.equals("DOMLinkAdded")) {
|
|
final int tabId = message.getInt("tabID");
|
|
final String rel = message.getString("rel");
|
|
final String href = message.getString("href");
|
|
final int size = message.getInt("size");
|
|
handleLinkAdded(tabId, rel, href, size);
|
|
} else if (event.equals("DOMWindowClose")) {
|
|
final int tabId = message.getInt("tabID");
|
|
handleWindowClose(tabId);
|
|
} else if (event.equals("log")) {
|
|
// generic log listener
|
|
final String msg = message.getString("msg");
|
|
Log.d(LOGTAG, "Log: " + msg);
|
|
} else if (event.equals("Content:SecurityChange")) {
|
|
final int tabId = message.getInt("tabID");
|
|
final JSONObject identity = message.getJSONObject("identity");
|
|
Log.i(LOGTAG, "Security Mode - " + identity.getString("mode"));
|
|
handleSecurityChange(tabId, identity);
|
|
} else if (event.equals("Content:ReaderEnabled")) {
|
|
final int tabId = message.getInt("tabID");
|
|
handleReaderEnabled(tabId);
|
|
} else if (event.equals("Reader:FaviconRequest")) {
|
|
final String url = message.getString("url");
|
|
handleFaviconRequest(url);
|
|
} else if (event.equals("Reader:GoToReadingList")) {
|
|
showReadingList();
|
|
} else if (event.equals("Content:StateChange")) {
|
|
final int tabId = message.getInt("tabID");
|
|
final String uri = message.getString("uri");
|
|
final boolean success = message.getBoolean("success");
|
|
int state = message.getInt("state");
|
|
Log.d(LOGTAG, "State - " + state);
|
|
if ((state & GeckoAppShell.WPL_STATE_IS_NETWORK) != 0) {
|
|
if ((state & GeckoAppShell.WPL_STATE_START) != 0) {
|
|
Log.d(LOGTAG, "Got a document start event.");
|
|
final boolean showProgress = message.getBoolean("showProgress");
|
|
handleDocumentStart(tabId, showProgress, uri);
|
|
} else if ((state & GeckoAppShell.WPL_STATE_STOP) != 0) {
|
|
Log.d(LOGTAG, "Got a document stop event.");
|
|
handleDocumentStop(tabId, success);
|
|
}
|
|
}
|
|
} else if (event.equals("Content:LoadError")) {
|
|
final int tabId = message.getInt("tabID");
|
|
final String uri = message.getString("uri");
|
|
final String title = message.getString("title");
|
|
handleLoadError(tabId, uri, title);
|
|
} else if (event.equals("Content:PageShow")) {
|
|
final int tabId = message.getInt("tabID");
|
|
handlePageShow(tabId);
|
|
} else if (event.equals("Gecko:Ready")) {
|
|
mGeckoReadyStartupTimer.stop();
|
|
setLaunchState(GeckoApp.LaunchState.GeckoRunning);
|
|
GeckoAppShell.sendPendingEventsToGecko();
|
|
connectGeckoLayerClient();
|
|
} else if (event.equals("ToggleChrome:Hide")) {
|
|
toggleChrome(false);
|
|
} else if (event.equals("ToggleChrome:Show")) {
|
|
toggleChrome(true);
|
|
} else if (event.equals("ToggleChrome:Focus")) {
|
|
focusChrome();
|
|
} else if (event.equals("DOMFullScreen:Start")) {
|
|
mDOMFullScreen = true;
|
|
} else if (event.equals("DOMFullScreen:Stop")) {
|
|
mDOMFullScreen = false;
|
|
} else if (event.equals("Permissions:Data")) {
|
|
String host = message.getString("host");
|
|
JSONArray permissions = message.getJSONArray("permissions");
|
|
showSiteSettingsDialog(host, permissions);
|
|
} else if (event.equals("Tab:ViewportMetadata")) {
|
|
int tabId = message.getInt("tabID");
|
|
Tab tab = Tabs.getInstance().getTab(tabId);
|
|
if (tab == null)
|
|
return;
|
|
tab.setZoomConstraints(new ZoomConstraints(message));
|
|
// Sync up the layer view and the tab if the tab is currently displayed.
|
|
LayerView layerView = mLayerView;
|
|
if (layerView != null && Tabs.getInstance().isSelectedTab(tab)) {
|
|
layerView.setZoomConstraints(tab.getZoomConstraints());
|
|
}
|
|
} else if (event.equals("Tab:HasTouchListener")) {
|
|
int tabId = message.getInt("tabID");
|
|
final Tab tab = Tabs.getInstance().getTab(tabId);
|
|
tab.setHasTouchListeners(true);
|
|
mMainHandler.post(new Runnable() {
|
|
public void run() {
|
|
if (Tabs.getInstance().isSelectedTab(tab))
|
|
mLayerView.getTouchEventHandler().setWaitForTouchListeners(true);
|
|
}
|
|
});
|
|
} else if (event.equals("Session:StatePurged")) {
|
|
onStatePurged();
|
|
} else if (event.equals("Bookmark:Insert")) {
|
|
final String url = message.getString("url");
|
|
final String title = message.getString("title");
|
|
mMainHandler.post(new Runnable() {
|
|
public void run() {
|
|
Toast.makeText(GeckoApp.mAppContext, R.string.bookmark_added, Toast.LENGTH_SHORT).show();
|
|
GeckoAppShell.getHandler().post(new Runnable() {
|
|
public void run() {
|
|
BrowserDB.addBookmark(GeckoApp.mAppContext.getContentResolver(), title, url);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
} else if (event.equals("Accessibility:Event")) {
|
|
GeckoAccessibility.sendAccessibilityEvent(message);
|
|
} else if (event.equals("Accessibility:Ready")) {
|
|
GeckoAccessibility.updateAccessibilitySettings();
|
|
} else if (event.equals("Shortcut:Remove")) {
|
|
final String url = message.getString("url");
|
|
final String origin = message.getString("origin");
|
|
final String title = message.getString("title");
|
|
final String type = message.getString("shortcutType");
|
|
GeckoAppShell.removeShortcut(title, url, origin, type);
|
|
} else if (event.equals("WebApps:Open")) {
|
|
String url = message.getString("uri");
|
|
String origin = message.getString("origin");
|
|
Intent intent = GeckoAppShell.getWebAppIntent(url, origin, "", null);
|
|
if (intent == null)
|
|
return;
|
|
startActivity(intent);
|
|
} else if (event.equals("WebApps:Install")) {
|
|
String name = message.getString("name");
|
|
String launchPath = message.getString("launchPath");
|
|
String iconURL = message.getString("iconURL");
|
|
String uniqueURI = message.getString("uniqueURI");
|
|
|
|
// installWebapp will return a File object pointing to the profile directory of the webapp
|
|
mCurrentResponse = GeckoAppShell.installWebApp(name, launchPath, uniqueURI, iconURL).toString();
|
|
} else if (event.equals("WebApps:Uninstall")) {
|
|
String uniqueURI = message.getString("uniqueURI");
|
|
GeckoAppShell.uninstallWebApp(uniqueURI);
|
|
} else if (event.equals("DesktopMode:Changed")) {
|
|
int tabId = message.getInt("tabId");
|
|
boolean desktopMode = message.getBoolean("desktopMode");
|
|
final Tab tab = Tabs.getInstance().getTab(tabId);
|
|
if (tab == null)
|
|
return;
|
|
|
|
tab.setDesktopMode(desktopMode);
|
|
mMainHandler.post(new Runnable() {
|
|
public void run() {
|
|
if (tab == Tabs.getInstance().getSelectedTab())
|
|
invalidateOptionsMenu();
|
|
}
|
|
});
|
|
} else if (event.equals("Share:Text")) {
|
|
String text = message.getString("text");
|
|
GeckoAppShell.openUriExternal(text, "text/plain", "", "", Intent.ACTION_SEND, "");
|
|
} else if (event.equals("Share:Image")) {
|
|
String src = message.getString("url");
|
|
String type = message.getString("mime");
|
|
GeckoAppShell.shareImage(src, type);
|
|
} else if (event.equals("Sanitize:ClearHistory")) {
|
|
handleClearHistory();
|
|
} else if (event.equals("Update:Check")) {
|
|
startService(new Intent(UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE, null, this, UpdateService.class));
|
|
} else if (event.equals("PrivateBrowsing:Data")) {
|
|
// null strings return "null" (http://code.google.com/p/android/issues/detail?id=13830)
|
|
if (message.isNull("session")) {
|
|
mPrivateBrowsingSession = null;
|
|
} else {
|
|
mPrivateBrowsingSession = message.getString("session");
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
|
|
}
|
|
}
|
|
|
|
public String getResponse() {
|
|
String res = mCurrentResponse;
|
|
mCurrentResponse = "";
|
|
return res;
|
|
}
|
|
|
|
void onStatePurged() { }
|
|
|
|
/**
|
|
* @param aPermissions
|
|
* Array of JSON objects to represent site permissions.
|
|
* Example: { type: "offline-app", setting: "Store Offline Data: Allow" }
|
|
*/
|
|
private void showSiteSettingsDialog(String aHost, JSONArray aPermissions) {
|
|
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
|
|
|
View customTitleView = getLayoutInflater().inflate(R.layout.site_setting_title, null);
|
|
((TextView) customTitleView.findViewById(R.id.title)).setText(R.string.site_settings_title);
|
|
((TextView) customTitleView.findViewById(R.id.host)).setText(aHost);
|
|
builder.setCustomTitle(customTitleView);
|
|
|
|
// If there are no permissions to clear, show the user a message about that.
|
|
// In the future, we want to disable the menu item if there are no permissions to clear.
|
|
if (aPermissions.length() == 0) {
|
|
builder.setMessage(R.string.site_settings_no_settings);
|
|
} else {
|
|
// Eventually we should use a list adapter and custom checkable list items
|
|
// to make a two-line UI to match the mock-ups
|
|
CharSequence[] items = new CharSequence[aPermissions.length()];
|
|
boolean[] states = new boolean[aPermissions.length()];
|
|
for (int i = 0; i < aPermissions.length(); i++) {
|
|
try {
|
|
items[i] = aPermissions.getJSONObject(i).getString("setting");
|
|
// Make all the items checked by default.
|
|
states[i] = true;
|
|
} catch (JSONException e) {
|
|
Log.w(LOGTAG, "Exception populating settings items.", e);
|
|
}
|
|
}
|
|
builder.setMultiChoiceItems(items, states, new DialogInterface.OnMultiChoiceClickListener(){
|
|
public void onClick(DialogInterface dialog, int item, boolean state) {
|
|
// Do nothing
|
|
}
|
|
});
|
|
builder.setPositiveButton(R.string.site_settings_clear, new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int id) {
|
|
ListView listView = ((AlertDialog) dialog).getListView();
|
|
SparseBooleanArray checkedItemPositions = listView.getCheckedItemPositions();
|
|
|
|
// An array of the indices of the permissions we want to clear
|
|
JSONArray permissionsToClear = new JSONArray();
|
|
for (int i = 0; i < checkedItemPositions.size(); i++) {
|
|
boolean checked = checkedItemPositions.get(i);
|
|
if (checked)
|
|
permissionsToClear.put(i);
|
|
}
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Permissions:Clear", permissionsToClear.toString()));
|
|
}
|
|
});
|
|
}
|
|
|
|
builder.setNegativeButton(R.string.site_settings_cancel, new DialogInterface.OnClickListener(){
|
|
public void onClick(DialogInterface dialog, int id) {
|
|
dialog.cancel();
|
|
}
|
|
});
|
|
|
|
mMainHandler.post(new Runnable() {
|
|
public void run() {
|
|
builder.create().show();
|
|
}
|
|
});
|
|
}
|
|
|
|
void handleDocumentStart(int tabId, final boolean showProgress, String uri) {
|
|
final Tab tab = Tabs.getInstance().getTab(tabId);
|
|
if (tab == null)
|
|
return;
|
|
|
|
tab.setState(shouldShowProgress(uri) ? Tab.STATE_SUCCESS : Tab.STATE_LOADING);
|
|
tab.updateIdentityData(null);
|
|
tab.setReaderEnabled(false);
|
|
if (Tabs.getInstance().isSelectedTab(tab))
|
|
mLayerView.getRenderer().resetCheckerboard();
|
|
Tabs.getInstance().notifyListeners(tab, Tabs.TabEvents.START, showProgress);
|
|
}
|
|
|
|
void handleDocumentStop(int tabId, boolean success) {
|
|
final Tab tab = Tabs.getInstance().getTab(tabId);
|
|
if (tab == null)
|
|
return;
|
|
|
|
tab.setState(success ? Tab.STATE_SUCCESS : Tab.STATE_ERROR);
|
|
|
|
Tabs.getInstance().notifyListeners(tab, Tabs.TabEvents.STOP);
|
|
|
|
final String oldURL = tab.getURL();
|
|
GeckoAppShell.getHandler().postDelayed(new Runnable() {
|
|
public void run() {
|
|
// tab.getURL() may return null
|
|
if (!TextUtils.equals(oldURL, tab.getURL()))
|
|
return;
|
|
|
|
getAndProcessThumbnailForTab(tab);
|
|
if (Tabs.getInstance().isSelectedTab(tab)) {
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createStartPaintListentingEvent(tab.getId()));
|
|
ScreenshotHandler.screenshotWholePage(tab);
|
|
}
|
|
|
|
}
|
|
}, 500);
|
|
}
|
|
|
|
public void showToast(final int resId, final int duration) {
|
|
mMainHandler.post(new Runnable() {
|
|
public void run() {
|
|
Toast.makeText(mAppContext, resId, duration).show();
|
|
}
|
|
});
|
|
}
|
|
|
|
void handleShowToast(final String message, final String duration) {
|
|
mMainHandler.post(new Runnable() {
|
|
public void run() {
|
|
Toast toast;
|
|
if (duration.equals("long"))
|
|
toast = Toast.makeText(mAppContext, message, Toast.LENGTH_LONG);
|
|
else
|
|
toast = Toast.makeText(mAppContext, message, Toast.LENGTH_SHORT);
|
|
toast.show();
|
|
}
|
|
});
|
|
}
|
|
|
|
void handleContentLoaded(int tabId) {
|
|
final Tab tab = Tabs.getInstance().getTab(tabId);
|
|
if (tab == null)
|
|
return;
|
|
|
|
Tabs.getInstance().notifyListeners(tab, Tabs.TabEvents.LOADED);
|
|
}
|
|
|
|
void handleTitleChanged(int tabId, String title) {
|
|
final Tab tab = Tabs.getInstance().getTab(tabId);
|
|
if (tab == null)
|
|
return;
|
|
|
|
tab.updateTitle(title);
|
|
}
|
|
|
|
void handleLinkAdded(final int tabId, String rel, final String href, int size) {
|
|
if (rel.indexOf("[icon]") == -1)
|
|
return;
|
|
|
|
final Tab tab = Tabs.getInstance().getTab(tabId);
|
|
if (tab == null)
|
|
return;
|
|
|
|
tab.updateFaviconURL(href, size);
|
|
}
|
|
|
|
void handleWindowClose(final int tabId) {
|
|
Tabs tabs = Tabs.getInstance();
|
|
Tab tab = tabs.getTab(tabId);
|
|
tabs.closeTab(tab);
|
|
}
|
|
|
|
private void addFullScreenPluginView(View view) {
|
|
if (mFullScreenPluginView != null) {
|
|
Log.w(LOGTAG, "Already have a fullscreen plugin view");
|
|
return;
|
|
}
|
|
|
|
setFullScreen(true);
|
|
|
|
view.setWillNotDraw(false);
|
|
if (view instanceof SurfaceView) {
|
|
((SurfaceView) view).setZOrderOnTop(true);
|
|
}
|
|
|
|
mFullScreenPluginContainer = new FullScreenHolder(this);
|
|
|
|
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
|
|
ViewGroup.LayoutParams.FILL_PARENT,
|
|
ViewGroup.LayoutParams.FILL_PARENT,
|
|
Gravity.CENTER);
|
|
mFullScreenPluginContainer.addView(view, layoutParams);
|
|
|
|
|
|
FrameLayout decor = (FrameLayout)getWindow().getDecorView();
|
|
decor.addView(mFullScreenPluginContainer, layoutParams);
|
|
|
|
mFullScreenPluginView = view;
|
|
}
|
|
|
|
void addPluginView(final View view, final Rect rect, final boolean isFullScreen) {
|
|
mMainHandler.post(new Runnable() {
|
|
public void run() {
|
|
Tabs tabs = Tabs.getInstance();
|
|
Tab tab = tabs.getSelectedTab();
|
|
|
|
if (isFullScreen) {
|
|
addFullScreenPluginView(view);
|
|
return;
|
|
}
|
|
|
|
PluginLayer layer = (PluginLayer) tab.getPluginLayer(view);
|
|
if (layer == null) {
|
|
layer = new PluginLayer(view, rect, mLayerView.getRenderer().getMaxTextureSize());
|
|
tab.addPluginLayer(view, layer);
|
|
} else {
|
|
layer.reset(rect);
|
|
layer.setVisible(true);
|
|
}
|
|
|
|
mLayerView.addLayer(layer);
|
|
}
|
|
});
|
|
}
|
|
|
|
private void removeFullScreenPluginView(View view) {
|
|
if (mFullScreenPluginView == null) {
|
|
Log.w(LOGTAG, "Don't have a fullscreen plugin view");
|
|
return;
|
|
}
|
|
|
|
if (mFullScreenPluginView != view) {
|
|
Log.w(LOGTAG, "Passed view is not the current full screen view");
|
|
return;
|
|
}
|
|
|
|
mFullScreenPluginContainer.removeView(mFullScreenPluginView);
|
|
|
|
// We need do do this on the next iteration in order to avoid
|
|
// a deadlock, see comment below in FullScreenHolder
|
|
mMainHandler.post(new Runnable() {
|
|
public void run() {
|
|
mLayerView.show();
|
|
}
|
|
});
|
|
|
|
FrameLayout decor = (FrameLayout)getWindow().getDecorView();
|
|
decor.removeView(mFullScreenPluginContainer);
|
|
|
|
mFullScreenPluginView = null;
|
|
|
|
GeckoScreenOrientationListener.getInstance().unlockScreenOrientation();
|
|
setFullScreen(false);
|
|
}
|
|
|
|
void removePluginView(final View view, final boolean isFullScreen) {
|
|
mMainHandler.post(new Runnable() {
|
|
public void run() {
|
|
Tabs tabs = Tabs.getInstance();
|
|
Tab tab = tabs.getSelectedTab();
|
|
|
|
if (isFullScreen) {
|
|
removeFullScreenPluginView(view);
|
|
return;
|
|
}
|
|
|
|
PluginLayer layer = (PluginLayer) tab.removePluginLayer(view);
|
|
if (layer != null) {
|
|
layer.destroy();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private void hidePluginLayer(Layer layer) {
|
|
LayerView layerView = mLayerView;
|
|
layerView.removeLayer(layer);
|
|
layerView.requestRender();
|
|
}
|
|
|
|
private void showPluginLayer(Layer layer) {
|
|
LayerView layerView = mLayerView;
|
|
layerView.addLayer(layer);
|
|
layerView.requestRender();
|
|
}
|
|
|
|
public void requestRender() {
|
|
mLayerView.requestRender();
|
|
}
|
|
|
|
public void hidePlugins(Tab tab) {
|
|
for (Layer layer : tab.getPluginLayers()) {
|
|
if (layer instanceof PluginLayer) {
|
|
((PluginLayer) layer).setVisible(false);
|
|
}
|
|
|
|
hidePluginLayer(layer);
|
|
}
|
|
|
|
requestRender();
|
|
}
|
|
|
|
public void showPlugins() {
|
|
Tabs tabs = Tabs.getInstance();
|
|
Tab tab = tabs.getSelectedTab();
|
|
|
|
showPlugins(tab);
|
|
}
|
|
|
|
public void showPlugins(Tab tab) {
|
|
for (Layer layer : tab.getPluginLayers()) {
|
|
showPluginLayer(layer);
|
|
|
|
if (layer instanceof PluginLayer) {
|
|
((PluginLayer) layer).setVisible(true);
|
|
}
|
|
}
|
|
|
|
requestRender();
|
|
}
|
|
|
|
public void setFullScreen(final boolean fullscreen) {
|
|
mMainHandler.post(new Runnable() {
|
|
public void run() {
|
|
// Hide/show the system notification bar
|
|
Window window = getWindow();
|
|
window.setFlags(fullscreen ?
|
|
WindowManager.LayoutParams.FLAG_FULLSCREEN : 0,
|
|
WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
|
|
|
if (Build.VERSION.SDK_INT >= 11)
|
|
window.getDecorView().setSystemUiVisibility(fullscreen ? 1 : 0);
|
|
}
|
|
});
|
|
}
|
|
|
|
public boolean isTablet() {
|
|
int screenLayout = getResources().getConfiguration().screenLayout;
|
|
return (Build.VERSION.SDK_INT >= 11 &&
|
|
(((screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_LARGE) ||
|
|
((screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE)));
|
|
}
|
|
|
|
/** Called when the activity is first created. */
|
|
@Override
|
|
public void onCreate(Bundle savedInstanceState)
|
|
{
|
|
GeckoAppShell.registerGlobalExceptionHandler();
|
|
|
|
// The clock starts...now. Better hurry!
|
|
mJavaUiStartupTimer = new Telemetry.Timer("FENNEC_STARTUP_TIME_JAVAUI");
|
|
mAboutHomeStartupTimer = new Telemetry.Timer("FENNEC_STARTUP_TIME_ABOUTHOME");
|
|
mGeckoReadyStartupTimer = new Telemetry.Timer("FENNEC_STARTUP_TIME_GECKOREADY");
|
|
|
|
((GeckoApplication)getApplication()).initialize();
|
|
|
|
mAppContext = this;
|
|
Tabs.getInstance().attachToActivity(this);
|
|
|
|
// Check to see if the activity is restarted after configuration change.
|
|
if (getLastNonConfigurationInstance() != null) {
|
|
// Restart the application as a safe way to handle the configuration change.
|
|
doRestart();
|
|
System.exit(0);
|
|
return;
|
|
}
|
|
|
|
// StrictMode is set by defaults resource flag |enableStrictMode|.
|
|
if (getResources().getBoolean(R.bool.enableStrictMode)) {
|
|
enableStrictMode();
|
|
}
|
|
|
|
GeckoAppShell.loadMozGlue();
|
|
if (sGeckoThread != null) {
|
|
// this happens when the GeckoApp activity is destroyed by android
|
|
// without killing the entire application (see bug 769269)
|
|
mIsRestoringActivity = true;
|
|
Telemetry.HistogramAdd("FENNEC_RESTORING_ACTIVITY", 1);
|
|
}
|
|
|
|
mMainHandler = new Handler();
|
|
|
|
LayoutInflater.from(this).setFactory(GeckoViewsFactory.getInstance());
|
|
|
|
super.onCreate(savedInstanceState);
|
|
|
|
mOrientation = getResources().getConfiguration().orientation;
|
|
|
|
setContentView(getLayout());
|
|
|
|
// setup gecko layout
|
|
mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout);
|
|
mMainLayout = (LinearLayout) findViewById(R.id.main_layout);
|
|
|
|
// setup tabs panel
|
|
mTabsPanel = (TabsPanel) findViewById(R.id.tabs_panel);
|
|
|
|
// check if the last run was exited due to a normal kill while
|
|
// we were in the background, or a more harsh kill while we were
|
|
// active
|
|
if (savedInstanceState != null) {
|
|
mRestoreMode = RESTORE_OOM;
|
|
|
|
boolean wasInBackground =
|
|
savedInstanceState.getBoolean(SAVED_STATE_IN_BACKGROUND, false);
|
|
|
|
// Don't log OOM-kills if only one activity was destroyed. (For example
|
|
// from "Don't keep activities" on ICS)
|
|
if (!wasInBackground && !mIsRestoringActivity) {
|
|
Telemetry.HistogramAdd("FENNEC_WAS_KILLED", 1);
|
|
}
|
|
|
|
mPrivateBrowsingSession = savedInstanceState.getString(SAVED_STATE_PRIVATE_SESSION);
|
|
}
|
|
|
|
GeckoBackgroundThread.getHandler().post(new Runnable() {
|
|
public void run() {
|
|
SharedPreferences prefs =
|
|
GeckoApp.mAppContext.getSharedPreferences(PREFS_NAME, 0);
|
|
|
|
boolean wasOOM = prefs.getBoolean(PREFS_OOM_EXCEPTION, false);
|
|
boolean wasStopped = prefs.getBoolean(PREFS_WAS_STOPPED, true);
|
|
if (wasOOM || !wasStopped) {
|
|
Telemetry.HistogramAdd("FENNEC_WAS_KILLED", 1);
|
|
}
|
|
SharedPreferences.Editor editor = prefs.edit();
|
|
editor.putBoolean(GeckoApp.PREFS_OOM_EXCEPTION, false);
|
|
|
|
// put a flag to check if we got a normal onSaveInstaceState
|
|
// on exit, or if we were suddenly killed (crash or native OOM)
|
|
editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
|
|
editor.commit();
|
|
}
|
|
});
|
|
}
|
|
|
|
protected void initializeChrome(String uri, Boolean isExternalURL) {
|
|
mDoorHangerPopup = new DoorHangerPopup(this, null);
|
|
mPluginContainer = (AbsoluteLayout) findViewById(R.id.plugin_container);
|
|
mFormAssistPopup = (FormAssistPopup) findViewById(R.id.form_assist_popup);
|
|
|
|
if (cameraView == null) {
|
|
cameraView = new SurfaceView(this);
|
|
cameraView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
|
|
}
|
|
|
|
if (mLayerView == null) {
|
|
LayerView layerView = (LayerView) findViewById(R.id.layer_view);
|
|
layerView.initializeView(GeckoAppShell.getEventDispatcher());
|
|
mLayerView = layerView;
|
|
}
|
|
}
|
|
|
|
private void initialize() {
|
|
mInitialized = true;
|
|
|
|
invalidateOptionsMenu();
|
|
|
|
Intent intent = getIntent();
|
|
String action = intent.getAction();
|
|
String args = intent.getStringExtra("args");
|
|
|
|
String profileName = null;
|
|
String profilePath = null;
|
|
if (args != null) {
|
|
if (args.contains("-P")) {
|
|
Pattern p = Pattern.compile("(?:-P\\s*)(\\w*)(\\s*)");
|
|
Matcher m = p.matcher(args);
|
|
if (m.find()) {
|
|
profileName = m.group(1);
|
|
}
|
|
}
|
|
if (args.contains("-profile")) {
|
|
Pattern p = Pattern.compile("(?:-profile\\s*)(\\S*)(\\s*)");
|
|
Matcher m = p.matcher(args);
|
|
if (m.find()) {
|
|
profilePath = m.group(1);
|
|
}
|
|
if (profileName == null) {
|
|
profileName = getDefaultProfileName();
|
|
if (profileName == null)
|
|
profileName = "default";
|
|
}
|
|
GeckoApp.sIsUsingCustomProfile = true;
|
|
}
|
|
if (profileName != null || profilePath != null) {
|
|
mProfile = GeckoProfile.get(this, profileName, profilePath);
|
|
}
|
|
}
|
|
|
|
BrowserDB.initialize(getProfile().getName());
|
|
|
|
String passedUri = null;
|
|
String uri = getURIFromIntent(intent);
|
|
if (uri != null && uri.length() > 0) {
|
|
passedUri = uri;
|
|
}
|
|
|
|
if (mRestoreMode == RESTORE_NONE && getProfile().shouldRestoreSession()) {
|
|
mRestoreMode = RESTORE_CRASH;
|
|
}
|
|
|
|
final boolean isExternalURL = passedUri != null && !passedUri.equals("about:home");
|
|
StartupAction startupAction;
|
|
if (isExternalURL) {
|
|
startupAction = StartupAction.URL;
|
|
} else {
|
|
startupAction = StartupAction.NORMAL;
|
|
}
|
|
|
|
// Start migrating as early as possible, can do this in
|
|
// parallel with Gecko load.
|
|
checkMigrateProfile();
|
|
|
|
Uri data = intent.getData();
|
|
if (data != null && "http".equals(data.getScheme())) {
|
|
Intent copy = new Intent(intent);
|
|
copy.setAction(ACTION_LOAD);
|
|
if (isHostOnRedirectWhitelist(data.getHost())) {
|
|
startupAction = StartupAction.REDIRECTOR;
|
|
GeckoAppShell.getHandler().post(new RedirectorRunnable(copy));
|
|
// We're going to handle this uri with the redirector, so setting
|
|
// the action to MAIN and clearing the uri data prevents us from
|
|
// loading it twice
|
|
intent.setAction(Intent.ACTION_MAIN);
|
|
intent.setData(null);
|
|
passedUri = null;
|
|
} else {
|
|
startupAction = StartupAction.PREFETCH;
|
|
GeckoAppShell.getHandler().post(new PrefetchRunnable(copy));
|
|
}
|
|
}
|
|
|
|
Tabs.registerOnTabsChangedListener(this);
|
|
|
|
// If we are doing a restore, read the session data and send it to Gecko
|
|
if (mRestoreMode != RESTORE_NONE) {
|
|
try {
|
|
String sessionString = getProfile().readSessionFile(false);
|
|
if (sessionString == null) {
|
|
throw new IOException("could not read from session file");
|
|
}
|
|
|
|
// If we are doing an OOM restore, parse the session data and
|
|
// stub the restored tabs immediately. This allows the UI to be
|
|
// updated before Gecko has restored.
|
|
if (mRestoreMode == RESTORE_OOM) {
|
|
final JSONArray tabs = new JSONArray();
|
|
SessionParser parser = new SessionParser() {
|
|
@Override
|
|
public void onTabRead(SessionTab sessionTab) {
|
|
JSONObject tabObject = sessionTab.getTabObject();
|
|
|
|
int flags = Tabs.LOADURL_NEW_TAB;
|
|
flags |= ((isExternalURL || !sessionTab.isSelected()) ? Tabs.LOADURL_DELAY_LOAD : 0);
|
|
flags |= (tabObject.optBoolean("desktopMode") ? Tabs.LOADURL_DESKTOP : 0);
|
|
flags |= (tabObject.optBoolean("isPrivate") ? Tabs.LOADURL_PRIVATE : 0);
|
|
|
|
Tab tab = Tabs.getInstance().loadUrl(sessionTab.getSelectedUrl(), flags);
|
|
tab.updateTitle(sessionTab.getSelectedTitle());
|
|
|
|
try {
|
|
tabObject.put("tabId", tab.getId());
|
|
} catch (JSONException e) {
|
|
Log.e(LOGTAG, "JSON error", e);
|
|
}
|
|
tabs.put(tabObject);
|
|
}
|
|
};
|
|
|
|
parser.parse(sessionString);
|
|
if (mPrivateBrowsingSession != null) {
|
|
parser.parse(mPrivateBrowsingSession);
|
|
}
|
|
|
|
if (tabs.length() > 0) {
|
|
sessionString = new JSONObject().put("windows", new JSONArray().put(new JSONObject().put("tabs", tabs))).toString();
|
|
} else {
|
|
throw new Exception("No tabs could be read from session file");
|
|
}
|
|
}
|
|
|
|
JSONObject restoreData = new JSONObject();
|
|
restoreData.put("restoringOOM", mRestoreMode == RESTORE_OOM);
|
|
restoreData.put("sessionString", sessionString);
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Session:Restore", restoreData.toString()));
|
|
} catch (Exception e) {
|
|
// If restore failed, do a normal startup
|
|
Log.e(LOGTAG, "An error occurred during restore", e);
|
|
mRestoreMode = RESTORE_NONE;
|
|
}
|
|
}
|
|
|
|
// Move the session file if it exists
|
|
if (mRestoreMode != RESTORE_OOM) {
|
|
getProfile().moveSessionFile();
|
|
}
|
|
|
|
initializeChrome(passedUri, isExternalURL);
|
|
|
|
// Show telemetry door hanger if we aren't restoring a session
|
|
if (mRestoreMode == RESTORE_NONE) {
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Telemetry:Prompt", null));
|
|
}
|
|
|
|
Telemetry.HistogramAdd("FENNEC_STARTUP_GECKOAPP_ACTION", startupAction.ordinal());
|
|
|
|
if (!mIsRestoringActivity) {
|
|
sGeckoThread = new GeckoThread(intent, passedUri);
|
|
}
|
|
if (!ACTION_DEBUG.equals(action) &&
|
|
checkAndSetLaunchState(LaunchState.Launching, LaunchState.Launched)) {
|
|
sGeckoThread.start();
|
|
} else if (ACTION_DEBUG.equals(action) &&
|
|
checkAndSetLaunchState(LaunchState.Launching, LaunchState.WaitForDebugger)) {
|
|
mMainHandler.postDelayed(new Runnable() {
|
|
public void run() {
|
|
setLaunchState(LaunchState.Launching);
|
|
sGeckoThread.start();
|
|
}
|
|
}, 1000 * 5 /* 5 seconds */);
|
|
}
|
|
|
|
//register for events
|
|
registerEventListener("DOMContentLoaded");
|
|
registerEventListener("DOMTitleChanged");
|
|
registerEventListener("DOMLinkAdded");
|
|
registerEventListener("DOMWindowClose");
|
|
registerEventListener("log");
|
|
registerEventListener("Content:SecurityChange");
|
|
registerEventListener("Content:ReaderEnabled");
|
|
registerEventListener("Content:StateChange");
|
|
registerEventListener("Content:LoadError");
|
|
registerEventListener("Content:PageShow");
|
|
registerEventListener("Reader:FaviconRequest");
|
|
registerEventListener("Reader:GoToReadingList");
|
|
registerEventListener("onCameraCapture");
|
|
registerEventListener("Menu:Add");
|
|
registerEventListener("Menu:Remove");
|
|
registerEventListener("Gecko:Ready");
|
|
registerEventListener("Toast:Show");
|
|
registerEventListener("DOMFullScreen:Start");
|
|
registerEventListener("DOMFullScreen:Stop");
|
|
registerEventListener("ToggleChrome:Hide");
|
|
registerEventListener("ToggleChrome:Show");
|
|
registerEventListener("ToggleChrome:Focus");
|
|
registerEventListener("Permissions:Data");
|
|
registerEventListener("Tab:HasTouchListener");
|
|
registerEventListener("Tab:ViewportMetadata");
|
|
registerEventListener("Session:StatePurged");
|
|
registerEventListener("Bookmark:Insert");
|
|
registerEventListener("Accessibility:Event");
|
|
registerEventListener("Accessibility:Ready");
|
|
registerEventListener("Shortcut:Remove");
|
|
registerEventListener("WebApps:Open");
|
|
registerEventListener("WebApps:Install");
|
|
registerEventListener("WebApps:Uninstall");
|
|
registerEventListener("DesktopMode:Changed");
|
|
registerEventListener("Share:Text");
|
|
registerEventListener("Share:Image");
|
|
registerEventListener("Sanitize:ClearHistory");
|
|
registerEventListener("Update:Check");
|
|
registerEventListener("PrivateBrowsing:Data");
|
|
|
|
if (SmsManager.getInstance() != null) {
|
|
SmsManager.getInstance().start();
|
|
}
|
|
|
|
mPromptService = new PromptService();
|
|
|
|
mTextSelection = new TextSelection((TextSelectionHandle) findViewById(R.id.start_handle),
|
|
(TextSelectionHandle) findViewById(R.id.middle_handle),
|
|
(TextSelectionHandle) findViewById(R.id.end_handle),
|
|
GeckoAppShell.getEventDispatcher());
|
|
|
|
PrefsHelper.getPref("app.update.autodownload", new PrefsHelper.PrefHandlerBase() {
|
|
@Override public void prefValue(String pref, String value) {
|
|
UpdateServiceHelper.registerForUpdates(GeckoApp.this, value);
|
|
}
|
|
});
|
|
|
|
final GeckoApp self = this;
|
|
|
|
// End of the startup of our Java App
|
|
mJavaUiStartupTimer.stop();
|
|
|
|
GeckoAppShell.getHandler().postDelayed(new Runnable() {
|
|
public void run() {
|
|
// Sync settings need Gecko to be loaded, so
|
|
// no hurry in starting this.
|
|
checkMigrateSync();
|
|
|
|
// Record our launch time for the announcements service
|
|
// to use in assessing inactivity.
|
|
final Context context = GeckoApp.mAppContext;
|
|
AnnouncementsBroadcastService.recordLastLaunch(context);
|
|
|
|
// Kick off our background service to fetch product announcements.
|
|
// We do this by invoking the broadcast receiver, which uses the
|
|
// system alarm infrastructure to perform tasks at intervals.
|
|
GeckoPreferences.broadcastAnnouncementsPref(context);
|
|
|
|
/*
|
|
XXXX see bug 635342
|
|
We want to disable this code if possible. It is about 145ms in runtime
|
|
SharedPreferences settings = getPreferences(Activity.MODE_PRIVATE);
|
|
String localeCode = settings.getString(getPackageName() + ".locale", "");
|
|
if (localeCode != null && localeCode.length() > 0)
|
|
GeckoAppShell.setSelectedLocale(localeCode);
|
|
*/
|
|
|
|
if (!checkLaunchState(LaunchState.Launched)) {
|
|
return;
|
|
}
|
|
}
|
|
}, 50);
|
|
|
|
if (mIsRestoringActivity) {
|
|
setLaunchState(GeckoApp.LaunchState.GeckoRunning);
|
|
Tab selectedTab = Tabs.getInstance().getSelectedTab();
|
|
if (selectedTab != null)
|
|
Tabs.getInstance().selectTab(selectedTab.getId());
|
|
connectGeckoLayerClient();
|
|
GeckoAppShell.setLayerClient(mLayerView.getLayerClient());
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Viewport:Flush", null));
|
|
}
|
|
}
|
|
|
|
public GeckoProfile getProfile() {
|
|
// fall back to default profile if we didn't load a specific one
|
|
if (mProfile == null) {
|
|
mProfile = GeckoProfile.get(this);
|
|
}
|
|
return mProfile;
|
|
}
|
|
|
|
/**
|
|
* Enable Android StrictMode checks (for supported OS versions).
|
|
* http://developer.android.com/reference/android/os/StrictMode.html
|
|
*/
|
|
private void enableStrictMode()
|
|
{
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) {
|
|
return;
|
|
}
|
|
|
|
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
|
|
.detectAll()
|
|
.penaltyLog()
|
|
.build());
|
|
|
|
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
|
|
.detectAll()
|
|
.penaltyLog()
|
|
.build());
|
|
}
|
|
|
|
public void enableCameraView() {
|
|
// Some phones (eg. nexus S) need at least a 8x16 preview size
|
|
mMainLayout.addView(cameraView, new AbsoluteLayout.LayoutParams(8, 16, 0, 0));
|
|
}
|
|
|
|
public void disableCameraView() {
|
|
mMainLayout.removeView(cameraView);
|
|
}
|
|
|
|
abstract public String getDefaultUAString();
|
|
abstract public String getUAStringForHost(String host);
|
|
|
|
class PrefetchRunnable implements Runnable {
|
|
Intent mIntent;
|
|
protected HttpURLConnection mConnection = null;
|
|
PrefetchRunnable(Intent intent) {
|
|
mIntent = intent;
|
|
}
|
|
|
|
private void afterLoad() { }
|
|
|
|
public void run() {
|
|
try {
|
|
// this class should only be initialized with an intent with non-null data
|
|
URL url = new URL(mIntent.getData().toString());
|
|
// data url should have an http scheme
|
|
mConnection = (HttpURLConnection) url.openConnection();
|
|
mConnection.setRequestProperty("User-Agent", getUAStringForHost(url.getHost()));
|
|
mConnection.setInstanceFollowRedirects(false);
|
|
mConnection.setRequestMethod("GET");
|
|
mConnection.connect();
|
|
afterLoad();
|
|
} catch (Exception e) {
|
|
Log.w(LOGTAG, "unexpected exception, passing url directly to Gecko but we should explicitly catch this", e);
|
|
mIntent.putExtra("prefetched", 1);
|
|
} finally {
|
|
if (mConnection != null)
|
|
mConnection.disconnect();
|
|
}
|
|
}
|
|
}
|
|
|
|
class RedirectorRunnable extends PrefetchRunnable {
|
|
RedirectorRunnable(Intent intent) {
|
|
super(intent);
|
|
}
|
|
private void afterLoad() {
|
|
try {
|
|
int code = mConnection.getResponseCode();
|
|
if (code >= 300 && code < 400) {
|
|
String location = mConnection.getHeaderField("Location");
|
|
Uri data;
|
|
if (location != null &&
|
|
(data = Uri.parse(location)) != null &&
|
|
!"about".equals(data.getScheme()) &&
|
|
!"chrome".equals(data.getScheme())) {
|
|
mIntent.setData(data);
|
|
} else {
|
|
mIntent.putExtra("prefetched", 1);
|
|
}
|
|
} else {
|
|
mIntent.putExtra("prefetched", 1);
|
|
}
|
|
} catch (IOException ioe) {
|
|
Log.w(LOGTAG, "Exception trying to pre-fetch redirected URL.", ioe);
|
|
mIntent.putExtra("prefetched", 1);
|
|
}
|
|
}
|
|
public void run() {
|
|
super.run();
|
|
|
|
mMainHandler.postAtFrontOfQueue(new Runnable() {
|
|
public void run() {
|
|
onNewIntent(mIntent);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
private final String kRedirectWhiteListArray[] = new String[] {
|
|
"t.co",
|
|
"bit.ly",
|
|
"moz.la",
|
|
"aje.me",
|
|
"facebook.com",
|
|
"goo.gl",
|
|
"tinyurl.com"
|
|
};
|
|
|
|
private final CopyOnWriteArrayList<String> kRedirectWhiteList =
|
|
new CopyOnWriteArrayList<String>(kRedirectWhiteListArray);
|
|
|
|
private boolean isHostOnRedirectWhitelist(String host) {
|
|
return kRedirectWhiteList.contains(host);
|
|
}
|
|
|
|
@Override
|
|
protected void onNewIntent(Intent intent) {
|
|
if (checkLaunchState(LaunchState.GeckoExiting)) {
|
|
// We're exiting and shouldn't try to do anything else just incase
|
|
// we're hung for some reason we'll force the process to exit
|
|
System.exit(0);
|
|
return;
|
|
}
|
|
|
|
// if we were previously OOM killed, we can end up here when launching
|
|
// from external shortcuts, so set this as the intent for initialization
|
|
if (!mInitialized) {
|
|
setIntent(intent);
|
|
return;
|
|
}
|
|
|
|
// don't perform any actions if launching from recent apps
|
|
if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0)
|
|
return;
|
|
|
|
if (checkLaunchState(LaunchState.Launched)) {
|
|
Uri data = intent.getData();
|
|
Bundle bundle = intent.getExtras();
|
|
// if the intent has data (i.e. a URI to be opened) and the scheme
|
|
// is either http, we'll prefetch it, which means warming
|
|
// up the radio and DNS cache by connecting and parsing the redirect
|
|
// if the return code is between 300 and 400
|
|
if (data != null &&
|
|
"http".equals(data.getScheme()) &&
|
|
(bundle == null || bundle.getInt("prefetched", 0) != 1)) {
|
|
if (isHostOnRedirectWhitelist(data.getHost())) {
|
|
GeckoAppShell.getHandler().post(new RedirectorRunnable(intent));
|
|
return;
|
|
} else {
|
|
GeckoAppShell.getHandler().post(new PrefetchRunnable(intent));
|
|
}
|
|
}
|
|
}
|
|
final String action = intent.getAction();
|
|
|
|
if (Intent.ACTION_MAIN.equals(action)) {
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(""));
|
|
}
|
|
else if (ACTION_LOAD.equals(action)) {
|
|
String uri = intent.getDataString();
|
|
Tabs.getInstance().loadUrl(uri);
|
|
}
|
|
else if (Intent.ACTION_VIEW.equals(action)) {
|
|
String uri = intent.getDataString();
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(uri));
|
|
}
|
|
else if (action != null && action.startsWith(ACTION_WEBAPP_PREFIX)) {
|
|
String uri = getURIFromIntent(intent);
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createWebappLoadEvent(uri));
|
|
}
|
|
else if (ACTION_BOOKMARK.equals(action)) {
|
|
String uri = getURIFromIntent(intent);
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createBookmarkLoadEvent(uri));
|
|
}
|
|
else if (Intent.ACTION_SEARCH.equals(action)) {
|
|
String uri = getURIFromIntent(intent);
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(uri));
|
|
}
|
|
else if (ACTION_ALERT_CALLBACK.equals(action)) {
|
|
String alertName = "";
|
|
String alertCookie = "";
|
|
Uri data = intent.getData();
|
|
if (data != null) {
|
|
alertName = data.getQueryParameter("name");
|
|
if (alertName == null)
|
|
alertName = "";
|
|
alertCookie = data.getQueryParameter("cookie");
|
|
if (alertCookie == null)
|
|
alertCookie = "";
|
|
}
|
|
handleNotification(ACTION_ALERT_CALLBACK, alertName, alertCookie);
|
|
}
|
|
else if (ACTION_WIDGET.equals(action)) {
|
|
addTab();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Handles getting a uri from and intent in a way that is backwards
|
|
* compatable with our previous implementations
|
|
*/
|
|
protected String getURIFromIntent(Intent intent) {
|
|
final String action = intent.getAction();
|
|
if (ACTION_ALERT_CALLBACK.equals(action))
|
|
return null;
|
|
|
|
String uri = intent.getDataString();
|
|
if (uri != null)
|
|
return uri;
|
|
|
|
if ((action != null && action.startsWith(ACTION_WEBAPP_PREFIX)) || ACTION_BOOKMARK.equals(action)) {
|
|
uri = intent.getStringExtra("args");
|
|
if (uri != null && uri.startsWith("--url=")) {
|
|
uri.replace("--url=", "");
|
|
}
|
|
}
|
|
return uri;
|
|
}
|
|
|
|
@Override
|
|
public void onResume()
|
|
{
|
|
// After an onPause, the activity is back in the foreground.
|
|
// Undo whatever we did in onPause.
|
|
super.onResume();
|
|
|
|
SiteIdentityPopup.getInstance().dismiss();
|
|
|
|
int newOrientation = getResources().getConfiguration().orientation;
|
|
|
|
if (mOrientation != newOrientation) {
|
|
mOrientation = newOrientation;
|
|
refreshChrome();
|
|
}
|
|
|
|
GeckoScreenOrientationListener.getInstance().start();
|
|
|
|
// User may have enabled/disabled accessibility.
|
|
GeckoAccessibility.updateAccessibilitySettings();
|
|
|
|
GeckoBackgroundThread.getHandler().post(new Runnable() {
|
|
public void run() {
|
|
SharedPreferences prefs =
|
|
GeckoApp.mAppContext.getSharedPreferences(GeckoApp.PREFS_NAME, 0);
|
|
SharedPreferences.Editor editor = prefs.edit();
|
|
editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
|
|
editor.commit();
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onWindowFocusChanged(boolean hasFocus) {
|
|
super.onWindowFocusChanged(hasFocus);
|
|
|
|
if (!mInitialized && hasFocus)
|
|
initialize();
|
|
}
|
|
|
|
@Override
|
|
public void onStop()
|
|
{
|
|
// We're about to be stopped, potentially in preparation for
|
|
// being destroyed. We're killable after this point -- as I
|
|
// understand it, in extreme cases the process can be terminated
|
|
// without going through onDestroy.
|
|
//
|
|
// We might also get an onRestart after this; not sure what
|
|
// that would mean for Gecko if we were to kill it here.
|
|
// Instead, what we should do here is save prefs, session,
|
|
// etc., and generally mark the profile as 'clean', and then
|
|
// dirty it again if we get an onResume.
|
|
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createStoppingEvent(isApplicationInBackground()));
|
|
super.onStop();
|
|
}
|
|
|
|
@Override
|
|
public void onPause()
|
|
{
|
|
// In some way it's sad that Android will trigger StrictMode warnings
|
|
// here as the whole point is to save to disk while the activity is not
|
|
// interacting with the user.
|
|
GeckoBackgroundThread.getHandler().post(new Runnable() {
|
|
public void run() {
|
|
SharedPreferences prefs =
|
|
GeckoApp.mAppContext.getSharedPreferences(GeckoApp.PREFS_NAME, 0);
|
|
SharedPreferences.Editor editor = prefs.edit();
|
|
editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, true);
|
|
editor.commit();
|
|
|
|
BrowserDB.expireHistory(getContentResolver(),
|
|
BrowserContract.ExpirePriority.NORMAL);
|
|
}
|
|
});
|
|
|
|
GeckoScreenOrientationListener.getInstance().stop();
|
|
|
|
super.onPause();
|
|
}
|
|
|
|
@Override
|
|
public void onRestart()
|
|
{
|
|
GeckoBackgroundThread.getHandler().post(new Runnable() {
|
|
public void run() {
|
|
SharedPreferences prefs =
|
|
GeckoApp.mAppContext.getSharedPreferences(GeckoApp.PREFS_NAME, 0);
|
|
SharedPreferences.Editor editor = prefs.edit();
|
|
editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
|
|
editor.commit();
|
|
}
|
|
});
|
|
|
|
super.onRestart();
|
|
}
|
|
|
|
@Override
|
|
public void onStart()
|
|
{
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createStartEvent(isApplicationInBackground()));
|
|
super.onStart();
|
|
}
|
|
|
|
@Override
|
|
public void onDestroy()
|
|
{
|
|
// Tell Gecko to shutting down; we'll end up calling System.exit()
|
|
// in onXreExit.
|
|
if (isFinishing())
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createShutdownEvent());
|
|
|
|
unregisterEventListener("DOMContentLoaded");
|
|
unregisterEventListener("DOMTitleChanged");
|
|
unregisterEventListener("DOMLinkAdded");
|
|
unregisterEventListener("DOMWindowClose");
|
|
unregisterEventListener("log");
|
|
unregisterEventListener("Content:SecurityChange");
|
|
unregisterEventListener("Content:ReaderEnabled");
|
|
unregisterEventListener("Content:StateChange");
|
|
unregisterEventListener("Content:LoadError");
|
|
unregisterEventListener("Content:PageShow");
|
|
unregisterEventListener("Reader:FaviconRequest");
|
|
unregisterEventListener("Reader:GoToReadingList");
|
|
unregisterEventListener("onCameraCapture");
|
|
unregisterEventListener("Menu:Add");
|
|
unregisterEventListener("Menu:Remove");
|
|
unregisterEventListener("Gecko:Ready");
|
|
unregisterEventListener("Toast:Show");
|
|
unregisterEventListener("DOMFullScreen:Start");
|
|
unregisterEventListener("DOMFullScreen:Stop");
|
|
unregisterEventListener("ToggleChrome:Hide");
|
|
unregisterEventListener("ToggleChrome:Show");
|
|
unregisterEventListener("ToggleChrome:Focus");
|
|
unregisterEventListener("Permissions:Data");
|
|
unregisterEventListener("Tab:HasTouchListener");
|
|
unregisterEventListener("Tab:ViewportMetadata");
|
|
unregisterEventListener("Session:StatePurged");
|
|
unregisterEventListener("Bookmark:Insert");
|
|
unregisterEventListener("Accessibility:Event");
|
|
unregisterEventListener("Accessibility:Ready");
|
|
unregisterEventListener("Shortcut:Remove");
|
|
unregisterEventListener("WebApps:Open");
|
|
unregisterEventListener("WebApps:Install");
|
|
unregisterEventListener("WebApps:Uninstall");
|
|
unregisterEventListener("DesktopMode:Changed");
|
|
unregisterEventListener("Share:Text");
|
|
unregisterEventListener("Share:Image");
|
|
unregisterEventListener("Sanitize:ClearHistory");
|
|
unregisterEventListener("Update:Check");
|
|
unregisterEventListener("PrivateBrowsing:Data");
|
|
|
|
deleteTempFiles();
|
|
|
|
if (mLayerView != null)
|
|
mLayerView.destroy();
|
|
if (mDoorHangerPopup != null)
|
|
mDoorHangerPopup.destroy();
|
|
if (mFormAssistPopup != null)
|
|
mFormAssistPopup.destroy();
|
|
if (mPromptService != null)
|
|
mPromptService.destroy();
|
|
if (mTextSelection != null)
|
|
mTextSelection.destroy();
|
|
|
|
GeckoAppShell.getHandler().post(new Runnable() {
|
|
public void run() {
|
|
if (mFavicons != null)
|
|
mFavicons.close();
|
|
}
|
|
});
|
|
|
|
if (SmsManager.getInstance() != null) {
|
|
SmsManager.getInstance().stop();
|
|
if (isFinishing())
|
|
SmsManager.getInstance().shutdown();
|
|
}
|
|
|
|
super.onDestroy();
|
|
|
|
Tabs.unregisterOnTabsChangedListener(this);
|
|
}
|
|
|
|
protected void registerEventListener(String event) {
|
|
GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
|
|
}
|
|
|
|
protected void unregisterEventListener(String event) {
|
|
GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
|
|
}
|
|
|
|
// Get a temporary directory, may return null
|
|
public static File getTempDirectory() {
|
|
File dir = mAppContext.getExternalFilesDir("temp");
|
|
return dir;
|
|
}
|
|
|
|
// Delete any files in our temporary directory
|
|
public static void deleteTempFiles() {
|
|
File dir = getTempDirectory();
|
|
if (dir == null)
|
|
return;
|
|
File[] files = dir.listFiles();
|
|
if (files == null)
|
|
return;
|
|
for (File file : files) {
|
|
file.delete();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onContentChanged() {
|
|
super.onContentChanged();
|
|
}
|
|
|
|
|
|
@Override
|
|
public void onConfigurationChanged(Configuration newConfig) {
|
|
super.onConfigurationChanged(newConfig);
|
|
|
|
if (mOrientation != newConfig.orientation) {
|
|
mOrientation = newConfig.orientation;
|
|
if (mFormAssistPopup != null)
|
|
mFormAssistPopup.hide();
|
|
SiteIdentityPopup.getInstance().dismiss();
|
|
refreshChrome();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Object onRetainNonConfigurationInstance() {
|
|
// Send a non-null value so that we can restart the application,
|
|
// when activity restarts due to configuration change.
|
|
return new Boolean(true);
|
|
}
|
|
|
|
abstract public String getPackageName();
|
|
abstract public String getContentProcessName();
|
|
|
|
public void addEnvToIntent(Intent intent) {
|
|
Map<String,String> envMap = System.getenv();
|
|
Set<Map.Entry<String,String>> envSet = envMap.entrySet();
|
|
Iterator<Map.Entry<String,String>> envIter = envSet.iterator();
|
|
int c = 0;
|
|
while (envIter.hasNext()) {
|
|
Map.Entry<String,String> entry = envIter.next();
|
|
intent.putExtra("env" + c, entry.getKey() + "="
|
|
+ entry.getValue());
|
|
c++;
|
|
}
|
|
}
|
|
|
|
public void doRestart() {
|
|
doRestart("org.mozilla.gecko.restart");
|
|
}
|
|
|
|
public void doRestart(String action) {
|
|
Log.d(LOGTAG, "doRestart(\"" + action + "\")");
|
|
try {
|
|
Intent intent = new Intent(action);
|
|
intent.setClassName(getPackageName(),
|
|
getPackageName() + ".Restarter");
|
|
/* TODO: addEnvToIntent(intent); */
|
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
|
|
Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
|
|
Log.d(LOGTAG, "Restart intent: " + intent.toString());
|
|
GeckoAppShell.killAnyZombies();
|
|
startActivity(intent);
|
|
} catch (Exception e) {
|
|
Log.e(LOGTAG, "Error effecting restart.", e);
|
|
}
|
|
finish();
|
|
// Give the restart process time to start before we die
|
|
GeckoAppShell.waitForAnotherGeckoProc();
|
|
}
|
|
|
|
public void handleNotification(String action, String alertName, String alertCookie) {
|
|
GeckoAppShell.handleNotification(action, alertName, alertCookie);
|
|
}
|
|
|
|
private void checkMigrateProfile() {
|
|
final File profileDir = getProfile().getDir();
|
|
|
|
if (profileDir != null) {
|
|
final GeckoApp app = GeckoApp.mAppContext;
|
|
|
|
GeckoAppShell.getHandler().post(new Runnable() {
|
|
public void run() {
|
|
ProfileMigrator profileMigrator = new ProfileMigrator(app);
|
|
|
|
// Do a migration run on the first start after an upgrade.
|
|
if (!GeckoApp.sIsUsingCustomProfile &&
|
|
!profileMigrator.hasMigrationRun()) {
|
|
// Show the "Setting up Fennec" screen if this takes
|
|
// a while.
|
|
|
|
// Create a "final" holder for the setup screen so that we can
|
|
// create it in startCallback and still find a reference to it
|
|
// in stopCallback. (We must create it on the UI thread to fix
|
|
// bug 788216). Note that synchronization is not a problem here
|
|
// since it is only ever touched on the UI thread.
|
|
final SetupScreen[] setupScreenHolder = new SetupScreen[1];
|
|
|
|
final Runnable startCallback = new Runnable() {
|
|
public void run() {
|
|
GeckoApp.mAppContext.runOnUiThread(new Runnable() {
|
|
public void run() {
|
|
setupScreenHolder[0] = new SetupScreen(app);
|
|
setupScreenHolder[0].show();
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
final Runnable stopCallback = new Runnable() {
|
|
public void run() {
|
|
GeckoApp.mAppContext.runOnUiThread(new Runnable() {
|
|
public void run() {
|
|
SetupScreen screen = setupScreenHolder[0];
|
|
// screen will never be null if this code runs, but
|
|
// stranger things have happened...
|
|
if (screen != null) {
|
|
screen.dismiss();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
profileMigrator.setLongOperationCallbacks(startCallback,
|
|
stopCallback);
|
|
profileMigrator.launchPlaces(profileDir);
|
|
finishProfileMigration();
|
|
}
|
|
}}
|
|
);
|
|
}
|
|
}
|
|
|
|
protected void finishProfileMigration() {
|
|
}
|
|
|
|
private void checkMigrateSync() {
|
|
final File profileDir = getProfile().getDir();
|
|
if (!GeckoApp.sIsUsingCustomProfile && profileDir != null) {
|
|
final GeckoApp app = GeckoApp.mAppContext;
|
|
ProfileMigrator profileMigrator = new ProfileMigrator(app);
|
|
if (!profileMigrator.hasSyncMigrated()) {
|
|
profileMigrator.launchSyncPrefs();
|
|
}
|
|
}
|
|
}
|
|
|
|
PromptService getPromptService() {
|
|
return mPromptService;
|
|
}
|
|
|
|
@Override
|
|
public boolean onSearchRequested() {
|
|
return showAwesomebar(AwesomeBar.Target.CURRENT_TAB);
|
|
}
|
|
|
|
public boolean showAwesomebar(AwesomeBar.Target aTarget) {
|
|
return showAwesomebar(aTarget, null);
|
|
}
|
|
|
|
public boolean showAwesomebar(AwesomeBar.Target aTarget, String aUrl) {
|
|
Intent intent = new Intent(getBaseContext(), AwesomeBar.class);
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION | Intent.FLAG_ACTIVITY_NO_HISTORY);
|
|
intent.putExtra(AwesomeBar.TARGET_KEY, aTarget.name());
|
|
|
|
// if we were passed in a url, show it
|
|
if (aUrl != null && !TextUtils.isEmpty(aUrl)) {
|
|
intent.putExtra(AwesomeBar.CURRENT_URL_KEY, aUrl);
|
|
} else if (aTarget == AwesomeBar.Target.CURRENT_TAB) {
|
|
// otherwise, if we're editing the current tab, show its url
|
|
Tab tab = Tabs.getInstance().getSelectedTab();
|
|
if (tab != null) {
|
|
|
|
aUrl = tab.getURL();
|
|
if (aUrl != null) {
|
|
intent.putExtra(AwesomeBar.CURRENT_URL_KEY, aUrl);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
int requestCode = GeckoAppShell.sActivityHelper.makeRequestCodeForAwesomebar();
|
|
startActivityForResult(intent, requestCode);
|
|
return true;
|
|
}
|
|
|
|
public void showReadingList() {
|
|
Intent intent = new Intent(getBaseContext(), AwesomeBar.class);
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION | Intent.FLAG_ACTIVITY_NO_HISTORY);
|
|
intent.putExtra(AwesomeBar.TARGET_KEY, AwesomeBar.Target.CURRENT_TAB.toString());
|
|
intent.putExtra(AwesomeBar.READING_LIST_KEY, true);
|
|
|
|
int requestCode = GeckoAppShell.sActivityHelper.makeRequestCodeForAwesomebar();
|
|
startActivityForResult(intent, requestCode);
|
|
}
|
|
|
|
@Override
|
|
public void onBackPressed() {
|
|
if (autoHideTabs()) {
|
|
return;
|
|
}
|
|
|
|
if (mDoorHangerPopup != null && mDoorHangerPopup.isShowing()) {
|
|
mDoorHangerPopup.dismiss();
|
|
return;
|
|
}
|
|
|
|
if (mFullScreenPluginView != null) {
|
|
GeckoAppShell.onFullScreenPluginHidden(mFullScreenPluginView);
|
|
removeFullScreenPluginView(mFullScreenPluginView);
|
|
return;
|
|
}
|
|
|
|
SiteIdentityPopup identityPopup = SiteIdentityPopup.getInstance();
|
|
if (identityPopup.isShowing()) {
|
|
identityPopup.dismiss();
|
|
return;
|
|
}
|
|
|
|
if (mDOMFullScreen) {
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FullScreen:Exit", null));
|
|
return;
|
|
}
|
|
|
|
Tabs tabs = Tabs.getInstance();
|
|
Tab tab = tabs.getSelectedTab();
|
|
if (tab == null) {
|
|
moveTaskToBack(true);
|
|
return;
|
|
}
|
|
|
|
if (tab.doBack())
|
|
return;
|
|
|
|
if (tab.isExternal()) {
|
|
moveTaskToBack(true);
|
|
tabs.closeTab(tab);
|
|
return;
|
|
}
|
|
|
|
int parentId = tab.getParentId();
|
|
Tab parent = tabs.getTab(parentId);
|
|
if (parent != null) {
|
|
// The back button should always return to the parent (not a sibling).
|
|
tabs.closeTab(tab, parent);
|
|
return;
|
|
}
|
|
|
|
moveTaskToBack(true);
|
|
}
|
|
|
|
@Override
|
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
if (!GeckoAppShell.sActivityHelper.handleActivityResult(requestCode, resultCode, data)) {
|
|
super.onActivityResult(requestCode, resultCode, data);
|
|
}
|
|
}
|
|
|
|
public LayerView getLayerView() {
|
|
return mLayerView;
|
|
}
|
|
|
|
public AbsoluteLayout getPluginContainer() { return mPluginContainer; }
|
|
|
|
// Accelerometer.
|
|
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
|
}
|
|
|
|
public void onSensorChanged(SensorEvent event) {
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createSensorEvent(event));
|
|
}
|
|
|
|
// Geolocation.
|
|
public void onLocationChanged(Location location) {
|
|
// No logging here: user-identifying information.
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createLocationEvent(location));
|
|
}
|
|
|
|
public void onProviderDisabled(String provider)
|
|
{
|
|
}
|
|
|
|
public void onProviderEnabled(String provider)
|
|
{
|
|
}
|
|
|
|
public void onStatusChanged(String provider, int status, Bundle extras)
|
|
{
|
|
}
|
|
|
|
// Called when a Gecko Hal WakeLock is changed
|
|
public void notifyWakeLockChanged(String topic, String state) {
|
|
PowerManager.WakeLock wl = mWakeLocks.get(topic);
|
|
if (state.equals("locked-foreground") && wl == null) {
|
|
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
|
wl = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, topic);
|
|
wl.acquire();
|
|
mWakeLocks.put(topic, wl);
|
|
} else if (!state.equals("locked-foreground") && wl != null) {
|
|
wl.release();
|
|
mWakeLocks.remove(topic);
|
|
}
|
|
}
|
|
|
|
public void notifyCheckUpdateResult(boolean result) {
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Update:CheckResult", result ? "true" : "false"));
|
|
}
|
|
|
|
protected void connectGeckoLayerClient() {
|
|
mLayerView.getLayerClient().notifyGeckoReady();
|
|
|
|
mLayerView.getTouchEventHandler().setOnTouchListener(new OnInterceptTouchListener() {
|
|
private PointF initialPoint = null;
|
|
|
|
@Override
|
|
public boolean onInterceptTouchEvent(View view, MotionEvent event) {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean onTouch(View view, MotionEvent event) {
|
|
if (event == null)
|
|
return true;
|
|
|
|
int action = event.getAction();
|
|
PointF point = new PointF(event.getX(), event.getY());
|
|
if ((action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
|
|
initialPoint = point;
|
|
}
|
|
|
|
if (initialPoint != null && (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_MOVE) {
|
|
if (PointUtils.subtract(point, initialPoint).length() < PanZoomController.PAN_THRESHOLD) {
|
|
// Don't send the touchmove event if if the users finger hasn't move far
|
|
// Necessary for Google Maps to work correctlly. See bug 771099.
|
|
return true;
|
|
} else {
|
|
initialPoint = null;
|
|
}
|
|
}
|
|
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createMotionEvent(event));
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
|
|
public static class MainLayout extends LinearLayout {
|
|
private OnInterceptTouchListener mOnInterceptTouchListener;
|
|
|
|
public MainLayout(Context context, AttributeSet attrs) {
|
|
super(context, attrs);
|
|
mOnInterceptTouchListener = null;
|
|
}
|
|
|
|
public void setOnInterceptTouchListener(OnInterceptTouchListener listener) {
|
|
mOnInterceptTouchListener = listener;
|
|
}
|
|
|
|
@Override
|
|
public boolean onInterceptTouchEvent(MotionEvent event) {
|
|
if (mOnInterceptTouchListener != null && mOnInterceptTouchListener.onInterceptTouchEvent(this, event))
|
|
return true;
|
|
return super.onInterceptTouchEvent(event);
|
|
}
|
|
|
|
@Override
|
|
public boolean onTouchEvent(MotionEvent event) {
|
|
if (mOnInterceptTouchListener != null && mOnInterceptTouchListener.onTouch(this, event))
|
|
return true;
|
|
return super.onTouchEvent(event);
|
|
}
|
|
|
|
@Override
|
|
public void setDrawingCacheEnabled(boolean enabled) {
|
|
// Instead of setting drawing cache in the view itself, we simply
|
|
// enable drawing caching on its children. This is mainly used in
|
|
// animations (see PropertyAnimator)
|
|
super.setChildrenDrawnWithCacheEnabled(enabled);
|
|
}
|
|
}
|
|
|
|
public boolean linkerExtract() {
|
|
return false;
|
|
}
|
|
|
|
private class FullScreenHolder extends FrameLayout {
|
|
|
|
public FullScreenHolder(Context ctx) {
|
|
super(ctx);
|
|
}
|
|
|
|
@Override
|
|
public void addView(View view, int index) {
|
|
/**
|
|
* This normally gets called when Flash adds a separate SurfaceView
|
|
* for the video. It is unhappy if we have the LayerView underneath
|
|
* it for some reason so we need to hide that. Hiding the LayerView causes
|
|
* its surface to be destroyed, which causes a pause composition
|
|
* event to be sent to Gecko. We synchronously wait for that to be
|
|
* processed. Simultaneously, however, Flash is waiting on a mutex so
|
|
* the post() below is an attempt to avoid a deadlock.
|
|
*/
|
|
super.addView(view, index);
|
|
|
|
mMainHandler.post(new Runnable() {
|
|
public void run() {
|
|
mLayerView.hide();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* The methods below are simply copied from what Android WebKit does.
|
|
* It wasn't ever called in my testing, but might as well
|
|
* keep it in case it is for some reason. The methods
|
|
* all return true because we don't want any events
|
|
* leaking out from the fullscreen view.
|
|
*/
|
|
@Override
|
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
|
if (event.isSystem()) {
|
|
return super.onKeyDown(keyCode, event);
|
|
}
|
|
mFullScreenPluginView.onKeyDown(keyCode, event);
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
|
if (event.isSystem()) {
|
|
return super.onKeyUp(keyCode, event);
|
|
}
|
|
mFullScreenPluginView.onKeyUp(keyCode, event);
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean onTouchEvent(MotionEvent event) {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean onTrackballEvent(MotionEvent event) {
|
|
mFullScreenPluginView.onTrackballEvent(event);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onContextItemSelected(MenuItem item) {
|
|
switch(item.getItemId()) {
|
|
case R.id.pasteandgo: {
|
|
String text = GeckoAppShell.getClipboardText();
|
|
if (text != null && !TextUtils.isEmpty(text)) {
|
|
Tabs.getInstance().loadUrl(text);
|
|
}
|
|
return true;
|
|
}
|
|
case R.id.site_settings: {
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Permissions:Get", null));
|
|
return true;
|
|
}
|
|
case R.id.paste: {
|
|
String text = GeckoAppShell.getClipboardText();
|
|
if (text != null && !TextUtils.isEmpty(text)) {
|
|
showAwesomebar(AwesomeBar.Target.CURRENT_TAB, text);
|
|
}
|
|
return true;
|
|
}
|
|
case R.id.share: {
|
|
shareCurrentUrl();
|
|
return true;
|
|
}
|
|
case R.id.copyurl: {
|
|
Tab tab = Tabs.getInstance().getSelectedTab();
|
|
if (tab != null) {
|
|
String url = tab.getURL();
|
|
if (url != null) {
|
|
GeckoAppShell.setClipboardText(url);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
case R.id.add_to_launcher: {
|
|
Tab tab = Tabs.getInstance().getSelectedTab();
|
|
if (tab != null) {
|
|
String url = tab.getURL();
|
|
String title = tab.getDisplayTitle();
|
|
BitmapDrawable favicon = (BitmapDrawable)(tab.getFavicon());
|
|
if (url != null && title != null) {
|
|
GeckoAppShell.createShortcut(title, url, url, favicon == null ? null : favicon.getBitmap(), "");
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static boolean shouldShowProgress(String url) {
|
|
return "about:home".equals(url) || ReaderModeUtils.isAboutReader(url);
|
|
}
|
|
|
|
public static void assertOnUiThread() {
|
|
Thread uiThread = mAppContext.getMainLooper().getThread();
|
|
assertOnThread(uiThread);
|
|
}
|
|
|
|
public static void assertOnGeckoThread() {
|
|
assertOnThread(sGeckoThread);
|
|
}
|
|
|
|
private static void assertOnThread(Thread expectedThread) {
|
|
Thread currentThread = Thread.currentThread();
|
|
long currentThreadId = currentThread.getId();
|
|
long expectedThreadId = expectedThread.getId();
|
|
|
|
if (currentThreadId != expectedThreadId) {
|
|
throw new IllegalThreadStateException("Expected thread " + expectedThreadId + " (\""
|
|
+ expectedThread.getName()
|
|
+ "\"), but running on thread " + currentThreadId
|
|
+ " (\"" + currentThread.getName() + ")");
|
|
}
|
|
}
|
|
}
|