gecko-dev/embedding/android/GeckoAppShell.java
Mike Hommey 7de58a1cfa Bug 620931 part 3 - Allow GRE and XUL application to use omni.jar independently. r=bsmedberg,r=mwu
We now store two independent locations for an omni.jar, allowing GRE/XRE and
XUL application to each have their own omni.jar. And since xulrunner setups
are very independent from the XUL applications, we implement support for both
omni.jar and non omni.jar cases in the same runtime, with the side effect of
allowing to switch from one to the other manually without rebuilding the
binaries.

We let the mozilla::Omnijar API handle both cases, so that callers don't need
too much work to support them.

We also make the preferences service load the same set of preferences in all
the various cases (unified vs. separate, omni.jar vs. no omni.jar).

The child process launcher for IPC is modified to pass the base directories
needed for the mozilla::Omnijar API initialization in the child process.

Finally, the startupcache file name canonicalization is modified to separate
APP and GRE resources.
2011-02-25 12:53:36 +01:00

1143 lines
43 KiB
Java

/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Vladimir Vukicevic <vladimir@pobox.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko;
import java.io.*;
import java.lang.reflect.*;
import java.nio.*;
import java.nio.channels.*;
import java.text.*;
import java.util.*;
import java.util.zip.*;
import java.util.concurrent.*;
import android.os.*;
import android.app.*;
import android.text.*;
import android.view.*;
import android.view.inputmethod.*;
import android.content.*;
import android.content.res.*;
import android.content.pm.*;
import android.graphics.*;
import android.widget.*;
import android.hardware.*;
import android.location.*;
import android.webkit.MimeTypeMap;
import android.media.MediaScannerConnection;
import android.media.MediaScannerConnection.MediaScannerConnectionClient;
import android.util.*;
import android.net.Uri;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
public class GeckoAppShell
{
// static members only
private GeckoAppShell() { }
static private LinkedList<GeckoEvent> gPendingEvents =
new LinkedList<GeckoEvent>();
static private boolean gRestartScheduled = false;
static private final Timer mIMETimer = new Timer();
static private final HashMap<Integer, AlertNotification>
mAlertNotifications = new HashMap<Integer, AlertNotification>();
static private final int NOTIFY_IME_RESETINPUTSTATE = 0;
static private final int NOTIFY_IME_SETOPENSTATE = 1;
static private final int NOTIFY_IME_CANCELCOMPOSITION = 2;
static private final int NOTIFY_IME_FOCUSCHANGE = 3;
static public final long kFreeSpaceThreshold = 157286400L; // 150MB
static private final long kLibFreeSpaceBuffer = 20971520L; // 29MB
static private File sCacheFile = null;
static private int sFreeSpace = -1;
/* The Android-side API: API methods that Android calls */
// Initialization methods
public static native void nativeInit();
public static native void nativeRun(String args);
// helper methods
public static native void setSurfaceView(GeckoSurfaceView sv);
public static native void putenv(String map);
public static native void onResume();
public static native void onLowMemory();
public static native void callObserver(String observerKey, String topic, String data);
public static native void removeObserver(String observerKey);
public static native void loadLibs(String apkName, boolean shouldExtract);
public static native void onChangeNetworkLinkStatus(String status);
// A looper thread, accessed by GeckoAppShell.getHandler
private static class LooperThread extends Thread {
public SynchronousQueue<Handler> mHandlerQueue =
new SynchronousQueue<Handler>();
public void run() {
Looper.prepare();
try {
mHandlerQueue.put(new Handler());
} catch (InterruptedException ie) {}
Looper.loop();
}
}
private static class GeckoMediaScannerClient implements MediaScannerConnectionClient {
private String mFile = "";
private String mMimeType = "";
private MediaScannerConnection mScanner = null;
public GeckoMediaScannerClient(Context aContext, String aFile, String aMimeType) {
mFile = aFile;
mMimeType = aMimeType;
mScanner = new MediaScannerConnection(aContext, this);
if (mScanner != null)
mScanner.connect();
}
public void onMediaScannerConnected() {
mScanner.scanFile(mFile, mMimeType);
}
public void onScanCompleted(String path, Uri uri) {
if(path.equals(mFile)) {
mScanner.disconnect();
mScanner = null;
}
}
}
// Get a Handler for the main java thread
public static Handler getMainHandler() {
return GeckoApp.mAppContext.mMainHandler;
}
private static Handler sHandler = null;
// Get a Handler for a looper thread, or create one if it doesn't exist yet
public static Handler getHandler() {
if (sHandler == null) {
LooperThread lt = new LooperThread();
lt.start();
try {
sHandler = lt.mHandlerQueue.take();
} catch (InterruptedException ie) {}
}
return sHandler;
}
public static File getCacheDir() {
if (sCacheFile == null)
sCacheFile = GeckoApp.mAppContext.getCacheDir();
return sCacheFile;
}
public static long getFreeSpace() {
try {
if (sFreeSpace == -1) {
File cacheDir = getCacheDir();
if (cacheDir != null) {
StatFs cacheStats = new StatFs(cacheDir.getPath());
sFreeSpace = cacheStats.getFreeBlocks() *
cacheStats.getBlockSize();
} else {
Log.i("GeckoAppShell", "Unable to get cache dir");
}
}
} catch (Exception e) {
Log.e("GeckoAppShell", "exception while stating cache dir: ", e);
}
return sFreeSpace;
}
static boolean moveFile(File inFile, File outFile)
{
Log.i("GeckoAppShell", "moving " + inFile + " to " + outFile);
if (outFile.isDirectory())
outFile = new File(outFile, inFile.getName());
try {
if (inFile.renameTo(outFile))
return true;
} catch (SecurityException se) {
Log.w("GeckoAppShell", "error trying to rename file", se);
}
try {
long lastModified = inFile.lastModified();
outFile.createNewFile();
// so copy it instead
FileChannel inChannel = new FileInputStream(inFile).getChannel();
FileChannel outChannel = new FileOutputStream(outFile).getChannel();
long size = inChannel.size();
long transferred = inChannel.transferTo(0, size, outChannel);
inChannel.close();
outChannel.close();
outFile.setLastModified(lastModified);
if (transferred == size)
inFile.delete();
else
return false;
} catch (Exception e) {
Log.e("GeckoAppShell", "exception while moving file: ", e);
try {
outFile.delete();
} catch (SecurityException se) {
Log.w("GeckoAppShell", "error trying to delete file", se);
}
return false;
}
return true;
}
static boolean moveDir(File from, File to) {
try {
to.mkdirs();
if (from.renameTo(to))
return true;
} catch (SecurityException se) {
Log.w("GeckoAppShell", "error trying to rename file", se);
}
File[] files = from.listFiles();
boolean retVal = true;
if (files == null)
return false;
try {
Iterator fileIterator = Arrays.asList(files).iterator();
while (fileIterator.hasNext()) {
File file = (File)fileIterator.next();
File dest = new File(to, file.getName());
if (file.isDirectory())
retVal = moveDir(file, dest) ? retVal : false;
else
retVal = moveFile(file, dest) ? retVal : false;
}
from.delete();
} catch(Exception e) {
Log.e("GeckoAppShell", "error trying to move file", e);
}
return retVal;
}
// java-side stuff
public static void loadGeckoLibs(String apkName) {
// The package data lib directory isn't placed in ld.so's
// search path, so we have to manually load libraries that
// libxul will depend on. Not ideal.
System.loadLibrary("mozutils");
GeckoApp geckoApp = GeckoApp.mAppContext;
String homeDir;
if (Build.VERSION.SDK_INT < 8 ||
geckoApp.getApplication().getPackageResourcePath().startsWith("/data")) {
File home = geckoApp.getFilesDir();
homeDir = home.getPath();
// handle the application being moved to phone from sdcard
File profileDir = new File(homeDir, "mozilla");
File oldHome = new File("/data/data/" +
GeckoApp.mAppContext.getPackageName() + "/mozilla");
if (oldHome.exists())
moveDir(oldHome, profileDir);
if (Build.VERSION.SDK_INT >= 8) {
File extHome = geckoApp.getExternalFilesDir(null);
File extProf = new File (extHome, "mozilla");
if (extHome != null && extProf != null && extProf.exists())
moveDir(extProf, profileDir);
}
} else {
File home = geckoApp.getExternalFilesDir(null);
homeDir = home.getPath();
// handle the application being moved to phone from sdcard
File profileDir = new File(homeDir, "mozilla");
File oldHome = new File("/data/data/" +
GeckoApp.mAppContext.getPackageName() + "/mozilla");
if (oldHome.exists())
moveDir(oldHome, profileDir);
File intHome = geckoApp.getFilesDir();
File intProf = new File(intHome, "mozilla");
if (intHome != null && intProf != null && intProf.exists())
moveDir(intProf, profileDir);
}
GeckoAppShell.putenv("HOME=" + homeDir);
GeckoAppShell.putenv("GRE_HOME=" + GeckoApp.sGREDir.getPath());
Intent i = geckoApp.getIntent();
String env = i.getStringExtra("env0");
Log.i("GeckoApp", "env0: "+ env);
for (int c = 1; env != null; c++) {
GeckoAppShell.putenv(env);
env = i.getStringExtra("env" + c);
Log.i("GeckoApp", "env"+ c +": "+ env);
}
File f = geckoApp.getDir("tmp", Context.MODE_WORLD_READABLE |
Context.MODE_WORLD_WRITEABLE );
if (!f.exists())
f.mkdirs();
GeckoAppShell.putenv("TMPDIR=" + f.getPath());
f = Environment.getDownloadCacheDirectory();
GeckoAppShell.putenv("EXTERNAL_STORAGE=" + f.getPath());
File cacheFile = getCacheDir();
GeckoAppShell.putenv("CACHE_PATH=" + cacheFile.getPath());
// gingerbread introduces File.getUsableSpace(). We should use that.
long freeSpace = getFreeSpace();
try {
File downloadDir = null;
File updatesDir = null;
if (Build.VERSION.SDK_INT >= 8) {
downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
updatesDir = GeckoApp.mAppContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
} else {
updatesDir = downloadDir = new File(Environment.getExternalStorageDirectory().getPath(), "download");
}
GeckoAppShell.putenv("DOWNLOADS_DIRECTORY=" + downloadDir.getPath());
GeckoAppShell.putenv("UPDATES_DIRECTORY=" + updatesDir.getPath());
}
catch (Exception e) {
Log.i("GeckoApp", "No download directory has been found: " + e);
}
putLocaleEnv();
if (freeSpace + kLibFreeSpaceBuffer < kFreeSpaceThreshold) {
// remove any previously extracted libs since we're apparently low
File[] files = cacheFile.listFiles();
if (files != null) {
Iterator cacheFiles = Arrays.asList(files).iterator();
while (cacheFiles.hasNext()) {
File libFile = (File)cacheFiles.next();
if (libFile.getName().endsWith(".so"))
libFile.delete();
}
}
}
loadLibs(apkName, freeSpace > kFreeSpaceThreshold);
}
private static void putLocaleEnv() {
GeckoAppShell.putenv("LANG=" + Locale.getDefault().toString());
NumberFormat nf = NumberFormat.getInstance();
if (nf instanceof DecimalFormat) {
DecimalFormat df = (DecimalFormat)nf;
DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
GeckoAppShell.putenv("LOCALE_DECIMAL_POINT=" + dfs.getDecimalSeparator());
GeckoAppShell.putenv("LOCALE_THOUSANDS_SEP=" + dfs.getGroupingSeparator());
GeckoAppShell.putenv("LOCALE_GROUPING=" + (char)df.getGroupingSize());
}
}
public static void runGecko(String apkPath, String args, String url) {
// run gecko -- it will spawn its own thread
GeckoAppShell.nativeInit();
// Tell Gecko where the target surface view is for rendering
GeckoAppShell.setSurfaceView(GeckoApp.surfaceView);
// First argument is the .apk path
String combinedArgs = apkPath + " -greomni " + apkPath;
if (args != null)
combinedArgs += " " + args;
if (url != null)
combinedArgs += " " + url;
// and go
GeckoAppShell.nativeRun(combinedArgs);
}
private static GeckoEvent mLastDrawEvent;
private static void sendPendingEventsToGecko() {
try {
while (!gPendingEvents.isEmpty()) {
GeckoEvent e = gPendingEvents.removeFirst();
notifyGeckoOfEvent(e);
}
} catch (NoSuchElementException e) {}
}
public static void sendEventToGecko(GeckoEvent e) {
if (GeckoApp.checkLaunchState(GeckoApp.LaunchState.GeckoRunning)) {
notifyGeckoOfEvent(e);
} else {
gPendingEvents.addLast(e);
}
}
public static void sendEventToGeckoSync(GeckoEvent e) {
sendEventToGecko(e);
geckoEventSync();
}
// Tell the Gecko event loop that an event is available.
public static native void notifyGeckoOfEvent(GeckoEvent event);
/*
* The Gecko-side API: API methods that Gecko calls
*/
public static void scheduleRedraw() {
// Redraw everything
scheduleRedraw(0, -1, -1, -1, -1);
}
public static void scheduleRedraw(int nativeWindow, int x, int y, int w, int h) {
GeckoEvent e;
if (x == -1) {
e = new GeckoEvent(GeckoEvent.DRAW, null);
} else {
e = new GeckoEvent(GeckoEvent.DRAW, new Rect(x, y, w, h));
}
e.mNativeWindow = nativeWindow;
sendEventToGecko(e);
}
/* Delay updating IME states (see bug 573800) */
private static final class IMEStateUpdater extends TimerTask
{
static private IMEStateUpdater instance;
private boolean mEnable, mReset;
static private IMEStateUpdater getInstance() {
if (instance == null) {
instance = new IMEStateUpdater();
mIMETimer.schedule(instance, 200);
}
return instance;
}
static public synchronized void enableIME() {
getInstance().mEnable = true;
}
static public synchronized void resetIME() {
getInstance().mReset = true;
}
public void run() {
synchronized(IMEStateUpdater.class) {
instance = null;
}
InputMethodManager imm = (InputMethodManager)
GeckoApp.surfaceView.getContext().getSystemService(
Context.INPUT_METHOD_SERVICE);
if (imm == null)
return;
if (mReset)
imm.restartInput(GeckoApp.surfaceView);
if (!mEnable)
return;
int state = GeckoApp.surfaceView.mIMEState;
if (state != GeckoSurfaceView.IME_STATE_DISABLED &&
state != GeckoSurfaceView.IME_STATE_PLUGIN)
imm.showSoftInput(GeckoApp.surfaceView, 0);
else
imm.hideSoftInputFromWindow(
GeckoApp.surfaceView.getWindowToken(), 0);
}
}
public static void notifyIME(int type, int state) {
if (GeckoApp.surfaceView == null)
return;
switch (type) {
case NOTIFY_IME_RESETINPUTSTATE:
// Composition event is already fired from widget.
// So reset IME flags.
GeckoApp.surfaceView.inputConnection.reset();
// Don't use IMEStateUpdater for reset.
// Because IME may not work showSoftInput()
// after calling restartInput() immediately.
// So we have to call showSoftInput() delay.
InputMethodManager imm = (InputMethodManager)
GeckoApp.surfaceView.getContext().getSystemService(
Context.INPUT_METHOD_SERVICE);
if (imm == null) {
// no way to reset IME status directly
IMEStateUpdater.resetIME();
} else {
imm.restartInput(GeckoApp.surfaceView);
}
// keep current enabled state
IMEStateUpdater.enableIME();
break;
case NOTIFY_IME_CANCELCOMPOSITION:
IMEStateUpdater.resetIME();
break;
case NOTIFY_IME_FOCUSCHANGE:
IMEStateUpdater.resetIME();
break;
}
}
public static void notifyIMEEnabled(int state, String typeHint,
String actionHint, boolean landscapeFS)
{
if (GeckoApp.surfaceView == null)
return;
/* When IME is 'disabled', IME processing is disabled.
In addition, the IME UI is hidden */
GeckoApp.surfaceView.mIMEState = state;
GeckoApp.surfaceView.mIMETypeHint = typeHint;
GeckoApp.surfaceView.mIMEActionHint = actionHint;
GeckoApp.surfaceView.mIMELandscapeFS = landscapeFS;
IMEStateUpdater.enableIME();
}
public static void notifyIMEChange(String text, int start, int end, int newEnd) {
if (GeckoApp.surfaceView == null ||
GeckoApp.surfaceView.inputConnection == null)
return;
InputMethodManager imm = (InputMethodManager)
GeckoApp.surfaceView.getContext().getSystemService(
Context.INPUT_METHOD_SERVICE);
if (imm == null)
return;
if (newEnd < 0)
GeckoApp.surfaceView.inputConnection.notifySelectionChange(
imm, start, end);
else
GeckoApp.surfaceView.inputConnection.notifyTextChange(
imm, text, start, end, newEnd);
}
private static CountDownLatch sGeckoPendingAcks = null;
// Block the current thread until the Gecko event loop is caught up
synchronized public static void geckoEventSync() {
sGeckoPendingAcks = new CountDownLatch(1);
GeckoAppShell.sendEventToGecko(
new GeckoEvent(GeckoEvent.GECKO_EVENT_SYNC));
while (sGeckoPendingAcks.getCount() != 0) {
try {
sGeckoPendingAcks.await();
} catch (InterruptedException e) {}
}
sGeckoPendingAcks = null;
}
// Signal the Java thread that it's time to wake up
public static void acknowledgeEventSync() {
CountDownLatch tmp = sGeckoPendingAcks;
if (tmp != null)
tmp.countDown();
}
public static void enableAccelerometer(boolean enable) {
SensorManager sm = (SensorManager)
GeckoApp.surfaceView.getContext().getSystemService(Context.SENSOR_SERVICE);
if (enable) {
Sensor accelSensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if (accelSensor == null)
return;
sm.registerListener(GeckoApp.surfaceView, accelSensor, SensorManager.SENSOR_DELAY_GAME);
} else {
sm.unregisterListener(GeckoApp.surfaceView);
}
}
public static void enableLocation(final boolean enable) {
getMainHandler().post(new Runnable() {
public void run() {
GeckoSurfaceView view = GeckoApp.surfaceView;
LocationManager lm = (LocationManager)
view.getContext().getSystemService(Context.LOCATION_SERVICE);
if (enable) {
Criteria crit = new Criteria();
crit.setAccuracy(Criteria.ACCURACY_FINE);
String provider = lm.getBestProvider(crit, true);
if (provider == null)
return;
Looper l = Looper.getMainLooper();
Location loc = lm.getLastKnownLocation(provider);
if (loc != null) {
view.onLocationChanged(loc);
}
lm.requestLocationUpdates(provider, 100, (float).5, view, l);
} else {
lm.removeUpdates(view);
}
}
});
}
public static void moveTaskToBack() {
GeckoApp.mAppContext.moveTaskToBack(true);
}
public static void returnIMEQueryResult(String result, int selectionStart, int selectionLength) {
GeckoApp.surfaceView.inputConnection.mSelectionStart = selectionStart;
GeckoApp.surfaceView.inputConnection.mSelectionLength = selectionLength;
try {
GeckoApp.surfaceView.inputConnection.mQueryResult.put(result);
} catch (InterruptedException e) {
}
}
static void onAppShellReady()
{
// mLaunchState can only be Launched at this point
GeckoApp.setLaunchState(GeckoApp.LaunchState.GeckoRunning);
sendPendingEventsToGecko();
}
static void onXreExit() {
// mLaunchState can only be Launched or GeckoRunning at this point
GeckoApp.setLaunchState(GeckoApp.LaunchState.GeckoExiting);
Log.i("GeckoAppJava", "XRE exited");
if (gRestartScheduled) {
GeckoApp.mAppContext.doRestart();
} else {
Log.i("GeckoAppJava", "we're done, good bye");
GeckoApp.mAppContext.finish();
}
System.exit(0);
}
static void scheduleRestart() {
Log.i("GeckoAppJava", "scheduling restart");
gRestartScheduled = true;
}
// "Installs" an application by creating a shortcut
static void installWebApplication(String aURI, String aTitle, String aIconData) {
Log.w("GeckoAppJava", "installWebApplication for " + aURI + " [" + aTitle + "]");
// the intent to be launched by the shortcut
Intent shortcutIntent = new Intent("org.mozilla.fennec.WEBAPP");
shortcutIntent.setClassName(GeckoApp.mAppContext,
GeckoApp.mAppContext.getPackageName() + ".App");
shortcutIntent.putExtra("args", "--webapp=" + aURI);
Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aTitle);
byte[] raw = Base64.decode(aIconData.substring(22), Base64.DEFAULT);
Bitmap bitmap = BitmapFactory.decodeByteArray(raw, 0, raw.length);
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap);
intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
GeckoApp.mAppContext.sendBroadcast(intent);
}
static String[] getHandlersForMimeType(String aMimeType, String aAction) {
Intent intent = getIntentForActionString(aAction);
if (aMimeType != null && aMimeType.length() > 0)
intent.setType(aMimeType);
return getHandlersForIntent(intent);
}
static String[] getHandlersForURL(String aURL, String aAction) {
// aURL may contain the whole URL or just the protocol
Uri uri = aURL.indexOf(':') >= 0 ? Uri.parse(aURL) : new Uri.Builder().scheme(aURL).build();
Intent intent = getIntentForActionString(aAction);
intent.setData(uri);
return getHandlersForIntent(intent);
}
static String[] getHandlersForIntent(Intent intent) {
PackageManager pm =
GeckoApp.surfaceView.getContext().getPackageManager();
List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
int numAttr = 4;
String[] ret = new String[list.size() * numAttr];
for (int i = 0; i < list.size(); i++) {
ResolveInfo resolveInfo = list.get(i);
ret[i * numAttr] = resolveInfo.loadLabel(pm).toString();
if (resolveInfo.isDefault)
ret[i * numAttr + 1] = "default";
else
ret[i * numAttr + 1] = "";
ret[i * numAttr + 2] = resolveInfo.activityInfo.applicationInfo.packageName;
ret[i * numAttr + 3] = resolveInfo.activityInfo.name;
}
return ret;
}
static Intent getIntentForActionString(String aAction) {
// Default to the view action if no other action as been specified.
if (aAction != null && aAction.length() > 0)
return new Intent(aAction);
else
return new Intent(Intent.ACTION_VIEW);
}
static String getExtensionFromMimeType(String aMimeType) {
return MimeTypeMap.getSingleton().getExtensionFromMimeType(aMimeType);
}
static String getMimeTypeFromExtensions(String aFileExt) {
MimeTypeMap mtm = MimeTypeMap.getSingleton();
StringTokenizer st = new StringTokenizer(aFileExt, "., ");
String type = null;
String subType = null;
while (st.hasMoreElements()) {
String ext = st.nextToken();
String mt = mtm.getMimeTypeFromExtension(ext);
if (mt == null)
continue;
int slash = mt.indexOf('/');
String tmpType = mt.substring(0, slash);
if (!tmpType.equalsIgnoreCase(type))
type = type == null ? tmpType : "*";
String tmpSubType = mt.substring(slash + 1);
if (!tmpSubType.equalsIgnoreCase(subType))
subType = subType == null ? tmpSubType : "*";
}
if (type == null)
type = "*";
if (subType == null)
subType = "*";
return type + "/" + subType;
}
static boolean openUriExternal(String aUriSpec, String aMimeType, String aPackageName,
String aClassName, String aAction, String aTitle) {
Intent intent = getIntentForActionString(aAction);
if (aAction.equalsIgnoreCase(Intent.ACTION_SEND)) {
intent.putExtra(Intent.EXTRA_TEXT, aUriSpec);
intent.putExtra(Intent.EXTRA_SUBJECT, aTitle);
if (aMimeType != null && aMimeType.length() > 0)
intent.setType(aMimeType);
} else if (aMimeType.length() > 0) {
intent.setDataAndType(Uri.parse(aUriSpec), aMimeType);
} else {
intent.setData(Uri.parse(aUriSpec));
}
if (aPackageName.length() > 0 && aClassName.length() > 0)
intent.setClassName(aPackageName, aClassName);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
try {
GeckoApp.surfaceView.getContext().startActivity(intent);
return true;
} catch(ActivityNotFoundException e) {
return false;
}
}
static SynchronousQueue<String> sClipboardQueue =
new SynchronousQueue<String>();
// On some devices, access to the clipboard service needs to happen
// on a thread with a looper, so dispatch this to our looper thread
// Note: the main looper won't work because it may be blocked on the
// gecko thread, which is most likely this thread
static String getClipboardText() {
getHandler().post(new Runnable() {
public void run() {
Context context = GeckoApp.surfaceView.getContext();
ClipboardManager cm = (ClipboardManager)
context.getSystemService(Context.CLIPBOARD_SERVICE);
try {
sClipboardQueue.put(cm.hasText() ? cm.getText().toString() : "");
} catch (InterruptedException ie) {}
}});
try {
String ret = sClipboardQueue.take();
return ret == "" ? null : ret;
} catch (InterruptedException ie) {}
return null;
}
static void setClipboardText(final String text) {
getHandler().post(new Runnable() {
public void run() {
Context context = GeckoApp.surfaceView.getContext();
ClipboardManager cm = (ClipboardManager)
context.getSystemService(Context.CLIPBOARD_SERVICE);
cm.setText(text);
}});
}
public static void showAlertNotification(String aImageUrl, String aAlertTitle, String aAlertText,
String aAlertCookie, String aAlertName) {
Log.i("GeckoAppJava", "GeckoAppShell.showAlertNotification\n" +
"- image = '" + aImageUrl + "'\n" +
"- title = '" + aAlertTitle + "'\n" +
"- text = '" + aAlertText +"'\n" +
"- cookie = '" + aAlertCookie +"'\n" +
"- name = '" + aAlertName + "'");
int icon = R.drawable.icon; // Just use the app icon by default
Uri imageUri = Uri.parse(aImageUrl);
String scheme = imageUri.getScheme();
if ("drawable".equals(scheme)) {
String resource = imageUri.getSchemeSpecificPart();
resource = resource.substring(resource.lastIndexOf('/') + 1);
try {
Class drawableClass = R.drawable.class;
Field f = drawableClass.getField(resource);
icon = f.getInt(null);
} catch (Exception e) {} // just means the resource doesn't exist
imageUri = null;
}
int notificationID = aAlertName.hashCode();
// Remove the old notification with the same ID, if any
removeNotification(notificationID);
AlertNotification notification =
new AlertNotification(GeckoApp.mAppContext,notificationID, icon,
aAlertTitle, aAlertText,
System.currentTimeMillis());
// The intent to launch when the user clicks the expanded notification
Intent notificationIntent = new Intent(GeckoApp.ACTION_ALERT_CLICK);
notificationIntent.setClassName(GeckoApp.mAppContext,
GeckoApp.mAppContext.getPackageName() + ".NotificationHandler");
// Put the strings into the intent as an URI "alert:<name>#<cookie>"
Uri dataUri = Uri.fromParts("alert", aAlertName, aAlertCookie);
notificationIntent.setData(dataUri);
PendingIntent contentIntent = PendingIntent.getBroadcast(GeckoApp.mAppContext, 0, notificationIntent, 0);
notification.setLatestEventInfo(GeckoApp.mAppContext, aAlertTitle, aAlertText, contentIntent);
notification.setCustomIcon(imageUri);
// The intent to execute when the status entry is deleted by the user with the "Clear All Notifications" button
Intent clearNotificationIntent = new Intent(GeckoApp.ACTION_ALERT_CLEAR);
clearNotificationIntent.setClassName(GeckoApp.mAppContext,
GeckoApp.mAppContext.getPackageName() + ".NotificationHandler");
clearNotificationIntent.setData(dataUri);
notification.deleteIntent = PendingIntent.getBroadcast(GeckoApp.mAppContext, 0, clearNotificationIntent, 0);
mAlertNotifications.put(notificationID, notification);
notification.show();
Log.i("GeckoAppJava", "Created notification ID " + notificationID);
}
public static void alertsProgressListener_OnProgress(String aAlertName, long aProgress, long aProgressMax, String aAlertText) {
Log.i("GeckoAppJava", "GeckoAppShell.alertsProgressListener_OnProgress\n" +
"- name = '" + aAlertName +"', " +
"progress = " + aProgress +" / " + aProgressMax + ", text = '" + aAlertText + "'");
int notificationID = aAlertName.hashCode();
AlertNotification notification = mAlertNotifications.get(notificationID);
if (notification != null)
notification.updateProgress(aAlertText, aProgress, aProgressMax);
if (aProgress == aProgressMax) {
// Hide the notification at 100%
removeObserver(aAlertName);
removeNotification(notificationID);
}
}
public static void alertsProgressListener_OnCancel(String aAlertName) {
Log.i("GeckoAppJava", "GeckoAppShell.alertsProgressListener_OnCancel('" + aAlertName + "'");
removeObserver(aAlertName);
int notificationID = aAlertName.hashCode();
removeNotification(notificationID);
}
public static void handleNotification(String aAction, String aAlertName, String aAlertCookie) {
int notificationID = aAlertName.hashCode();
if (GeckoApp.ACTION_ALERT_CLICK.equals(aAction)) {
Log.i("GeckoAppJava", "GeckoAppShell.handleNotification: callObserver(alertclickcallback)");
callObserver(aAlertName, "alertclickcallback", aAlertCookie);
AlertNotification notification = mAlertNotifications.get(notificationID);
if (notification != null && notification.isProgressStyle()) {
// When clicked, keep the notification, if it displays a progress
return;
}
}
callObserver(aAlertName, "alertfinished", aAlertCookie);
removeObserver(aAlertName);
removeNotification(notificationID);
}
private static void removeNotification(int notificationID) {
mAlertNotifications.remove(notificationID);
NotificationManager notificationManager = (NotificationManager)
GeckoApp.mAppContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(notificationID);
}
public static int getDpi() {
DisplayMetrics metrics = new DisplayMetrics();
GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
return metrics.densityDpi;
}
public static void setFullScreen(boolean fullscreen) {
GeckoApp.mFullscreen = fullscreen;
// force a reconfiguration to hide/show the system bar
GeckoApp.mAppContext.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
GeckoApp.mAppContext.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
GeckoApp.mAppContext.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER);
}
public static String showFilePicker(String aFilters) {
return GeckoApp.mAppContext.
showFilePicker(getMimeTypeFromExtensions(aFilters));
}
public static void performHapticFeedback(boolean aIsLongPress) {
GeckoApp.surfaceView.
performHapticFeedback(aIsLongPress ?
HapticFeedbackConstants.LONG_PRESS :
HapticFeedbackConstants.VIRTUAL_KEY);
}
public static void showInputMethodPicker() {
InputMethodManager imm = (InputMethodManager) GeckoApp.surfaceView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showInputMethodPicker();
}
public static void hideProgressDialog() {
GeckoApp.surfaceView.mShowingSplashScreen = false;
}
public static void setKeepScreenOn(final boolean on) {
GeckoApp.mAppContext.runOnUiThread(new Runnable() {
public void run() {
GeckoApp.surfaceView.setKeepScreenOn(on);
}
});
}
public static boolean isNetworkLinkUp() {
ConnectivityManager cm = (ConnectivityManager)
GeckoApp.mAppContext.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();
if (info == null || !info.isConnected())
return false;
return true;
}
public static boolean isNetworkLinkKnown() {
ConnectivityManager cm = (ConnectivityManager)
GeckoApp.mAppContext.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm.getActiveNetworkInfo() == null)
return false;
return true;
}
public static void setSelectedLocale(String localeCode) {
SharedPreferences settings =
GeckoApp.mAppContext.getPreferences(Activity.MODE_PRIVATE);
settings.edit().putString(GeckoApp.mAppContext.getPackageName() + ".locale",
localeCode).commit();
Locale locale;
int index;
if ((index = localeCode.indexOf('-')) != -1 ||
(index = localeCode.indexOf('_')) != -1) {
String langCode = localeCode.substring(0, index);
String countryCode = localeCode.substring(index + 1);
locale = new Locale(langCode, countryCode);
} else {
locale = new Locale(localeCode);
}
Locale.setDefault(locale);
Resources res = GeckoApp.mAppContext.getBaseContext().getResources();
Configuration config = res.getConfiguration();
config.locale = locale;
res.updateConfiguration(config, res.getDisplayMetrics());
}
public static int[] getSystemColors() {
// attrsAppearance[] must correspond to AndroidSystemColors structure in android/AndroidBridge.h
final int[] attrsAppearance = {
android.R.attr.textColor,
android.R.attr.textColorPrimary,
android.R.attr.textColorPrimaryInverse,
android.R.attr.textColorSecondary,
android.R.attr.textColorSecondaryInverse,
android.R.attr.textColorTertiary,
android.R.attr.textColorTertiaryInverse,
android.R.attr.textColorHighlight,
android.R.attr.colorForeground,
android.R.attr.colorBackground,
android.R.attr.panelColorForeground,
android.R.attr.panelColorBackground
};
int[] result = new int[attrsAppearance.length];
final ContextThemeWrapper contextThemeWrapper =
new ContextThemeWrapper(GeckoApp.mAppContext, android.R.style.TextAppearance);
final TypedArray appearance = contextThemeWrapper.getTheme().obtainStyledAttributes(attrsAppearance);
if (appearance != null) {
for (int i = 0; i < appearance.getIndexCount(); i++) {
int idx = appearance.getIndex(i);
int color = appearance.getColor(idx, 0);
result[idx] = color;
}
appearance.recycle();
}
return result;
}
public static void killAnyZombies() {
GeckoProcessesVisitor visitor = new GeckoProcessesVisitor() {
public boolean callback(int pid) {
if (pid != android.os.Process.myPid())
android.os.Process.killProcess(pid);
return true;
}
};
EnumerateGeckoProcesses(visitor);
}
public static boolean checkForGeckoProcs() {
class GeckoPidCallback implements GeckoProcessesVisitor {
public boolean otherPidExist = false;
public boolean callback(int pid) {
if (pid != android.os.Process.myPid()) {
otherPidExist = true;
return false;
}
return true;
}
}
GeckoPidCallback visitor = new GeckoPidCallback();
EnumerateGeckoProcesses(visitor);
return visitor.otherPidExist;
}
interface GeckoProcessesVisitor{
boolean callback(int pid);
}
static int sPidColumn = -1;
static int sUserColumn = -1;
private static void EnumerateGeckoProcesses(GeckoProcessesVisitor visiter) {
try {
// run ps and parse its output
java.lang.Process ps = Runtime.getRuntime().exec("ps");
BufferedReader in = new BufferedReader(new InputStreamReader(ps.getInputStream()),
2048);
String headerOutput = in.readLine();
// figure out the column offsets. We only care about the pid and user fields
if (sPidColumn == -1 || sUserColumn == -1) {
StringTokenizer st = new StringTokenizer(headerOutput);
int tokenSoFar = 0;
while(st.hasMoreTokens()) {
String next = st.nextToken();
if (next.equalsIgnoreCase("PID"))
sPidColumn = tokenSoFar;
else if (next.equalsIgnoreCase("USER"))
sUserColumn = tokenSoFar;
tokenSoFar++;
}
}
// alright, the rest are process entries.
String psOutput = null;
while ((psOutput = in.readLine()) != null) {
String[] split = psOutput.split("\\s+");
if (split.length <= sPidColumn || split.length <= sUserColumn)
continue;
int uid = android.os.Process.getUidForName(split[sUserColumn]);
if (uid == android.os.Process.myUid() &&
!split[split.length - 1].equalsIgnoreCase("ps")) {
int pid = Integer.parseInt(split[sPidColumn]);
boolean keepGoing = visiter.callback(pid);
if (keepGoing == false)
break;
}
}
in.close();
}
catch (Exception e) {
Log.i("GeckoAppShell", "finding procs throws ", e);
}
}
public static void waitForAnotherGeckoProc(){
int countdown = 40;
while (!checkForGeckoProcs() && --countdown > 0) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException ie) {}
}
}
public static void scanMedia(String aFile, String aMimeType) {
Context context = GeckoApp.surfaceView.getContext();
GeckoMediaScannerClient client = new GeckoMediaScannerClient(context, aFile, aMimeType);
}
}