Bug 1235869 - Remove web runtime from android. r=myk

This commit is contained in:
Brendan Dahl 2016-02-29 10:31:00 +01:00
parent a3ad28794e
commit 2ece6215a9
51 changed files with 37 additions and 3711 deletions

View File

@ -151,10 +151,6 @@ mobile/android/locales/
# Pretty sure we're disabling this one anyway
mobile/android/modules/ContactService.jsm
# es7 proposed: array comprehensions
# https://github.com/eslint/espree/issues/125
mobile/android/modules/WebappManager.jsm
# Non-standard `(catch ex if ...)`
mobile/android/components/Snippets.js

View File

@ -142,8 +142,8 @@ HTTP(..) == bug533251.html bug533251-ref.html
HTTP(..) == font-familiy-whitespace-1.html font-familiy-whitespace-1-ref.html
HTTP(..) != font-familiy-whitespace-1.html font-familiy-whitespace-1-notref.html
skip-if(B2G||Mulet) HTTP(..) == ivs-1.html ivs-1-ref.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop
skip-if(B2G||Mulet) HTTP(..) == cjkcisvs-1.html cjkcisvs-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
skip-if(B2G||Mulet||Android) HTTP(..) == ivs-1.html ivs-1-ref.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop # Android bug 1250229
skip-if(B2G||Mulet||Android) HTTP(..) == cjkcisvs-1.html cjkcisvs-1-ref.html # Initial mulet triage: parity with B2G/B2G Desktop # Android bug 1250229
skip-if(B2G||Mulet) HTTP(..) == missing-names.html missing-names-ref.html # bug 773482 # Initial mulet triage: parity with B2G/B2G Desktop

View File

@ -169,7 +169,7 @@ fuzzy-if(gtkWidget||B2G,255,6) fuzzy-if(cocoaWidget,65,69) == 1193519-sideways-l
== 1243125-1-floats-overflowing.html 1243125-1-floats-overflowing-ref.html
HTTP(..) == 1248248-1-orientation-break-glyphrun.html 1248248-1-orientation-break-glyphrun-ref.html
skip-if(Android) HTTP(..) == 1248248-1-orientation-break-glyphrun.html 1248248-1-orientation-break-glyphrun-ref.html # Android bug 1250229
# Suite of tests from Gérard Talbot in bug 1079151
# Frequent Windows 7 load failed: timed out waiting for test to complete (waiting for onload scripts to complete), bug 1167155 and friends

View File

@ -27,7 +27,6 @@ globals:
TelemetryStopwatch: false
UITelemetry: false
UserAgentOverrides: 0
WebappManager: false
XPCOMUtils: false
ctypes: false
dump: false

View File

@ -865,30 +865,6 @@ pref("browser.snippets.enabled", true);
pref("browser.snippets.syncPromo.enabled", true);
pref("browser.snippets.firstrunHomepage.enabled", true);
// The URL of the APK factory from which we obtain APKs for webapps.
pref("browser.webapps.apkFactoryUrl", "https://controller.apk.firefox.com/application.apk");
// How frequently to check for webapp updates, in seconds (86400 is daily).
pref("browser.webapps.updateInterval", 86400);
// Whether or not to check for updates. Enabled by default, but the runtime
// disables it for webapp profiles on firstrun, so only the main Fennec process
// checks for updates (to avoid duplicate update notifications).
//
// In the future, we might want to make each webapp process check for updates
// for its own webapp, in which case we'll need to have a third state for this
// preference. Thus it's an integer rather than a boolean.
//
// Possible values:
// 0: don't check for updates
// 1: do check for updates
pref("browser.webapps.checkForUpdates", 1);
// The URL of the service that checks for updates.
// To test updates, set this to http://apk-update-checker.paas.allizom.org,
// which is a test server that always reports all apps as having updates.
pref("browser.webapps.updateCheckUrl", "https://controller.apk.firefox.com/app_updates");
// The mode of home provider syncing.
// 0: Sync always
// 1: Sync only when on wifi

View File

@ -184,44 +184,12 @@
</intent-filter>
</activity>
<activity android:name="org.mozilla.gecko.webapp.Dispatcher"
android:noHistory="true" >
<intent-filter>
<!-- catch links from synthetic apks -->
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/webapp" />
</intent-filter>
</activity>
<receiver android:name="org.mozilla.gecko.webapp.UninstallListener" >
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<data android:scheme="package" />
</intent-filter>
</receiver>
<receiver android:name="org.mozilla.gecko.webapp.TaskKiller">
<intent-filter>
<action android:name="org.mozilla.webapp.TASK_REMOVED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver android:name="org.mozilla.gecko.restrictions.RestrictionProvider">
<intent-filter>
<action android:name="android.intent.action.GET_RESTRICTION_ENTRIES" />
</intent-filter>
</receiver>
<!-- Declare a predefined number of Webapp<num> activities. These are
used so that each web app can launch in its own process. Keep
this number in sync with the total number of web apps handled in
WebappAllocator. -->
#define FRAGMENT WebappManifestFragment.xml.frag.in
#include WebappFragmentRepeater.inc
<!-- Masquerade as the Resolver so that we can be opened from the Marketplace. -->
<activity-alias
android:name="com.android.internal.app.ResolverActivity"

View File

@ -1,301 +0,0 @@
#define APPNUM 0
#include @FRAGMENT@
#define APPNUM 1
#include @FRAGMENT@
#define APPNUM 2
#include @FRAGMENT@
#define APPNUM 3
#include @FRAGMENT@
#define APPNUM 4
#include @FRAGMENT@
#define APPNUM 5
#include @FRAGMENT@
#define APPNUM 6
#include @FRAGMENT@
#define APPNUM 7
#include @FRAGMENT@
#define APPNUM 8
#include @FRAGMENT@
#define APPNUM 9
#include @FRAGMENT@
#define APPNUM 10
#include @FRAGMENT@
#define APPNUM 11
#include @FRAGMENT@
#define APPNUM 12
#include @FRAGMENT@
#define APPNUM 13
#include @FRAGMENT@
#define APPNUM 14
#include @FRAGMENT@
#define APPNUM 15
#include @FRAGMENT@
#define APPNUM 16
#include @FRAGMENT@
#define APPNUM 17
#include @FRAGMENT@
#define APPNUM 18
#include @FRAGMENT@
#define APPNUM 19
#include @FRAGMENT@
#define APPNUM 20
#include @FRAGMENT@
#define APPNUM 21
#include @FRAGMENT@
#define APPNUM 22
#include @FRAGMENT@
#define APPNUM 23
#include @FRAGMENT@
#define APPNUM 24
#include @FRAGMENT@
#define APPNUM 25
#include @FRAGMENT@
#define APPNUM 26
#include @FRAGMENT@
#define APPNUM 27
#include @FRAGMENT@
#define APPNUM 28
#include @FRAGMENT@
#define APPNUM 29
#include @FRAGMENT@
#define APPNUM 30
#include @FRAGMENT@
#define APPNUM 31
#include @FRAGMENT@
#define APPNUM 32
#include @FRAGMENT@
#define APPNUM 33
#include @FRAGMENT@
#define APPNUM 34
#include @FRAGMENT@
#define APPNUM 35
#include @FRAGMENT@
#define APPNUM 36
#include @FRAGMENT@
#define APPNUM 37
#include @FRAGMENT@
#define APPNUM 38
#include @FRAGMENT@
#define APPNUM 39
#include @FRAGMENT@
#define APPNUM 40
#include @FRAGMENT@
#define APPNUM 41
#include @FRAGMENT@
#define APPNUM 42
#include @FRAGMENT@
#define APPNUM 43
#include @FRAGMENT@
#define APPNUM 44
#include @FRAGMENT@
#define APPNUM 45
#include @FRAGMENT@
#define APPNUM 46
#include @FRAGMENT@
#define APPNUM 47
#include @FRAGMENT@
#define APPNUM 48
#include @FRAGMENT@
#define APPNUM 49
#include @FRAGMENT@
#define APPNUM 50
#include @FRAGMENT@
#define APPNUM 51
#include @FRAGMENT@
#define APPNUM 52
#include @FRAGMENT@
#define APPNUM 53
#include @FRAGMENT@
#define APPNUM 54
#include @FRAGMENT@
#define APPNUM 55
#include @FRAGMENT@
#define APPNUM 56
#include @FRAGMENT@
#define APPNUM 57
#include @FRAGMENT@
#define APPNUM 58
#include @FRAGMENT@
#define APPNUM 59
#include @FRAGMENT@
#define APPNUM 60
#include @FRAGMENT@
#define APPNUM 61
#include @FRAGMENT@
#define APPNUM 62
#include @FRAGMENT@
#define APPNUM 63
#include @FRAGMENT@
#define APPNUM 64
#include @FRAGMENT@
#define APPNUM 65
#include @FRAGMENT@
#define APPNUM 66
#include @FRAGMENT@
#define APPNUM 67
#include @FRAGMENT@
#define APPNUM 68
#include @FRAGMENT@
#define APPNUM 69
#include @FRAGMENT@
#define APPNUM 70
#include @FRAGMENT@
#define APPNUM 71
#include @FRAGMENT@
#define APPNUM 72
#include @FRAGMENT@
#define APPNUM 73
#include @FRAGMENT@
#define APPNUM 74
#include @FRAGMENT@
#define APPNUM 75
#include @FRAGMENT@
#define APPNUM 76
#include @FRAGMENT@
#define APPNUM 77
#include @FRAGMENT@
#define APPNUM 78
#include @FRAGMENT@
#define APPNUM 79
#include @FRAGMENT@
#define APPNUM 80
#include @FRAGMENT@
#define APPNUM 81
#include @FRAGMENT@
#define APPNUM 82
#include @FRAGMENT@
#define APPNUM 83
#include @FRAGMENT@
#define APPNUM 84
#include @FRAGMENT@
#define APPNUM 85
#include @FRAGMENT@
#define APPNUM 86
#include @FRAGMENT@
#define APPNUM 87
#include @FRAGMENT@
#define APPNUM 88
#include @FRAGMENT@
#define APPNUM 89
#include @FRAGMENT@
#define APPNUM 90
#include @FRAGMENT@
#define APPNUM 91
#include @FRAGMENT@
#define APPNUM 92
#include @FRAGMENT@
#define APPNUM 93
#include @FRAGMENT@
#define APPNUM 94
#include @FRAGMENT@
#define APPNUM 95
#include @FRAGMENT@
#define APPNUM 96
#include @FRAGMENT@
#define APPNUM 97
#include @FRAGMENT@
#define APPNUM 98
#include @FRAGMENT@
#define APPNUM 99
#include @FRAGMENT@
#undef APPNUM
#undef FRAGMENT

View File

@ -1,9 +0,0 @@
<activity android:name="org.mozilla.gecko.webapp.Webapps$Webapp@APPNUM@"
android:label="@string/webapp_generic_name"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize"
android:windowSoftInputMode="stateUnspecified|adjustResize"
android:process=":@ANDROID_PACKAGE_NAME@.Webapp@APPNUM@"
android:theme="@style/Gecko.App"
android:launchMode="singleTop"
android:exported="true"
/>

View File

@ -679,6 +679,7 @@ public class BrowserApp extends GeckoApp
mMediaCastingBar = (MediaCastingBar) findViewById(R.id.media_casting);
EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener)this,
"Gecko:DelayedStartup",
"Menu:Open",
"Menu:Update",
"LightweightTheme:Update",
@ -1390,6 +1391,7 @@ public class BrowserApp extends GeckoApp
}
EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener) this,
"Gecko:DelayedStartup",
"Menu:Open",
"Menu:Update",
"LightweightTheme:Update",

View File

@ -43,8 +43,6 @@ import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.util.PrefUtils;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.webapp.EventListener;
import org.mozilla.gecko.webapp.UninstallListener;
import org.mozilla.gecko.widget.ButtonToast;
import android.annotation.SuppressLint;
@ -144,7 +142,6 @@ public abstract class GeckoApp
NORMAL, /* normal application start */
URL, /* launched with a passed URL */
PREFETCH, /* launched with a passed URL that we prefetch */
WEBAPP, /* launched as a webapp runtime */
GUEST, /* launched in guest browsing */
RESTRICTED, /* launched with restricted profile */
SHORTCUT /* launched from a homescreen shortcut */
@ -214,8 +211,6 @@ public abstract class GeckoApp
private volatile HealthRecorder mHealthRecorder;
private volatile Locale mLastLocale;
private EventListener mWebappEventListener;
private Intent mRestartIntent;
abstract public int getLayout();
@ -691,9 +686,7 @@ public abstract class GeckoApp
@Override
public void handleMessage(String event, JSONObject message) {
try {
if (event.equals("Gecko:DelayedStartup")) {
ThreadUtils.postToBackgroundThread(new UninstallListener.DelayedStartupTask(this));
} else if (event.equals("Gecko:Ready")) {
if (event.equals("Gecko:Ready")) {
mGeckoReadyStartupTimer.stop();
geckoConnected();
@ -711,10 +704,6 @@ public abstract class GeckoApp
doShutdown();
return;
} else if ("NativeApp:IsDebuggable".equals(event)) {
JSONObject ret = new JSONObject();
ret.put("isDebuggable", getIsDebuggable());
EventDispatcher.sendResponse(message, ret);
} else if (event.equals("Accessibility:Event")) {
GeckoAccessibility.sendAccessibilityEvent(message);
}
@ -1268,10 +1257,8 @@ public abstract class GeckoApp
EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener)this,
"Gecko:Ready",
"Gecko:DelayedStartup",
"Gecko:Exited",
"Accessibility:Event",
"NativeApp:IsDebuggable");
"Accessibility:Event");
EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener)this,
"Accessibility:Ready",
@ -1299,11 +1286,6 @@ public abstract class GeckoApp
EventDispatcher.getInstance().registerBackgroundThreadListener((BundleEventListener) this,
"History:GetPrePathLastVisitedTimeMilliseconds");
if (mWebappEventListener == null) {
mWebappEventListener = new EventListener();
mWebappEventListener.registerEvents();
}
GeckoThread.launch();
Bundle stateBundle = ContextUtils.getBundleExtra(getIntent(), EXTRA_STATE_BUNDLE);
@ -2103,10 +2085,8 @@ public abstract class GeckoApp
EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener)this,
"Gecko:Ready",
"Gecko:DelayedStartup",
"Gecko:Exited",
"Accessibility:Event",
"NativeApp:IsDebuggable");
"Accessibility:Event");
EventDispatcher.getInstance().unregisterGeckoThreadListener((NativeEventListener)this,
"Accessibility:Ready",
@ -2133,11 +2113,6 @@ public abstract class GeckoApp
EventDispatcher.getInstance().unregisterBackgroundThreadListener((BundleEventListener) this,
"History:GetPrePathLastVisitedTimeMilliseconds");
if (mWebappEventListener != null) {
mWebappEventListener.unregisterEvents();
mWebappEventListener = null;
}
deleteTempFiles();
if (mDoorHangerPopup != null)
@ -2673,23 +2648,6 @@ public abstract class GeckoApp
return versionCode;
}
protected boolean getIsDebuggable() {
// Return false so Fennec doesn't appear to be debuggable. WebappImpl
// then overrides this and returns the value of android:debuggable for
// the webapp APK, so webapps get the behavior supported by this method
// (i.e. automatic configuration and enabling of the remote debugger).
return false;
// If we ever want to expose this for Fennec, here's how we would do it:
// int flags = 0;
// try {
// flags = getPackageManager().getPackageInfo(getPackageName(), 0).applicationInfo.flags;
// } catch (NameNotFoundException e) {
// Log.wtf(LOGTAG, getPackageName() + " not found", e);
// }
// return (flags & android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
// FHR reason code for a session end prior to a restart for a
// locale change.
private static final String SESSION_END_LOCALE_CHANGED = "L";

View File

@ -2835,9 +2835,4 @@ public class GeckoAppShell
final Display disp = wm.getDefaultDisplay();
return new Rect(0, 0, disp.getWidth(), disp.getHeight());
}
@JNITarget
static boolean isWebAppProcess() {
return GeckoProfile.get(getApplicationContext()).isWebAppProfile();
}
}

View File

@ -145,7 +145,6 @@ public class GeckoApplication extends Application
// Make sure that all browser-ish applications default to the real LocalBrowserDB.
// GeckoView consumers use their own Application class, so this doesn't affect them.
// WebappImpl overrides this on creation.
//
// We need to do this before any access to the profile; it controls
// which database class is used.

View File

@ -83,7 +83,6 @@ public final class GeckoProfile {
private final String mName;
private final File mMozillaDir;
private final boolean mIsWebAppProfile;
private final Context mApplicationContext;
private final BrowserDB mDB;
@ -306,6 +305,8 @@ public final class GeckoProfile {
}
}
// Currently unused outside of testing.
@RobocopTarget
public static boolean removeProfile(Context context, String profileName) {
if (profileName == null) {
Log.w(LOGTAG, "Unable to remove profile: null profile name.");
@ -457,7 +458,6 @@ public final class GeckoProfile {
mApplicationContext = context.getApplicationContext();
mName = profileName;
mIsWebAppProfile = profileName.startsWith("webapp");
mMozillaDir = GeckoProfileDirectories.getMozillaDirectory(context);
// This apes the behavior of setDir.
@ -473,9 +473,6 @@ public final class GeckoProfile {
return mDB;
}
public boolean isWebAppProfile() {
return mIsWebAppProfile;
}
// Warning, Changing the lock file state from outside apis will cause this to become out of sync
public boolean locked() {
@ -1004,9 +1001,8 @@ public final class GeckoProfile {
parser.addSection(generalSection);
}
if (!isDefaultSet && !mIsWebAppProfile) {
// only set as default if this is the first non-webapp
// profile we're creating
if (!isDefaultSet) {
// only set as default if this is the first profile we're creating
profileSection.setProperty("Default", 1);
// We have no intention of stopping this session. The FIRSTRUN session
@ -1018,10 +1014,7 @@ public final class GeckoProfile {
parser.addSection(profileSection);
parser.write();
// Trigger init for non-webapp profiles.
if (!mIsWebAppProfile) {
enqueueInitialization(profileDir);
}
enqueueInitialization(profileDir);
// Write out profile creation time, mirroring the logic in nsToolkitProfileService.
try {
@ -1037,11 +1030,9 @@ public final class GeckoProfile {
Log.w(LOGTAG, "Couldn't write times.json.", e);
}
// Initialize pref flag for displaying the start pane for a new non-webapp profile.
if (!mIsWebAppProfile) {
final SharedPreferences prefs = GeckoSharedPrefs.forProfile(mApplicationContext);
prefs.edit().putBoolean(FirstrunAnimationContainer.PREF_FIRSTRUN_ENABLED, true).apply();
}
// Initialize pref flag for displaying the start pane for a new profile.
final SharedPreferences prefs = GeckoSharedPrefs.forProfile(mApplicationContext);
prefs.edit().putBoolean(FirstrunAnimationContainer.PREF_FIRSTRUN_ENABLED, true).apply();
return profileDir;
}

View File

@ -1,13 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.webapp.WebappImpl;
/**
* This class serves only as a namespace wrapper for WebappImpl.
*/
public class Webapp extends WebappImpl {}

View File

@ -1,174 +0,0 @@
/* -*- 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.webapp;
import java.util.ArrayList;
import org.mozilla.gecko.GeckoAppShell;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
public class Allocator {
private final String LOGTAG = "GeckoWebappAllocator";
private static final String PREFIX_ORIGIN = "webapp-origin-";
private static final String PREFIX_PACKAGE_NAME = "webapp-package-name-";
// These prefixes are for prefs used by the old shortcut-based runtime.
// We define them here so maybeMigrateOldPrefs can migrate them to their
// new equivalents if this app was originally installed as a shortcut.
// Maybe we can remove this code in the future!
private static final String PREFIX_OLD_APP = "app";
private static final String PREFIX_OLD_ICON = "icon";
// The number of Webapp# and WEBAPP# activities/apps/intents
private final static int MAX_WEB_APPS = 100;
protected static Allocator sInstance;
public static Allocator getInstance() {
return getInstance(GeckoAppShell.getContext());
}
public static synchronized Allocator getInstance(Context cx) {
if (sInstance == null) {
sInstance = new Allocator(cx);
}
return sInstance;
}
SharedPreferences mPrefs;
@SuppressWarnings("deprecation") // Suppressing deprecation notification for Context.MODE_MULTI_PROCESS until we
// reach a timeline for removal of the whole feature. (Bug 1171213)
protected Allocator(Context context) {
mPrefs = context.getSharedPreferences("webapps", Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS);
}
private static String appKey(int index) {
return PREFIX_PACKAGE_NAME + index;
}
public static String iconKey(int index) {
return "web-app-color-" + index;
}
public static String originKey(int i) {
return PREFIX_ORIGIN + i;
}
private static String oldAppKey(int index) {
return PREFIX_OLD_APP + index;
}
private static String oldIconKey(int index) {
return PREFIX_OLD_ICON + index;
}
public ArrayList<String> getInstalledPackageNames() {
ArrayList<String> installedPackages = new ArrayList<String>();
for (int i = 0; i < MAX_WEB_APPS; ++i) {
if (mPrefs.contains(appKey(i))) {
installedPackages.add(mPrefs.getString(appKey(i), ""));
}
}
return installedPackages;
}
public synchronized int findOrAllocatePackage(final String packageName) {
int index = getIndexForApp(packageName);
if (index != -1)
return index;
for (int i = 0; i < MAX_WEB_APPS; ++i) {
if (!mPrefs.contains(appKey(i))) {
// found unused index i
putPackageName(i, packageName);
return i;
}
}
// no more apps!
return -1;
}
public synchronized void putPackageName(final int index, final String packageName) {
mPrefs.edit().putString(appKey(index), packageName).apply();
}
public void updateColor(int index, int color) {
mPrefs.edit().putInt(iconKey(index), color).apply();
}
public synchronized int getIndexForApp(String packageName) {
return findSlotForPrefix(PREFIX_PACKAGE_NAME, packageName);
}
protected int findSlotForPrefix(String prefix, String value) {
for (int i = 0; i < MAX_WEB_APPS; ++i) {
if (mPrefs.getString(prefix + i, "").equals(value)) {
return i;
}
}
return -1;
}
public synchronized String getAppForIndex(int index) {
return mPrefs.getString(appKey(index), null);
}
public synchronized int releaseIndexForApp(String app) {
int index = getIndexForApp(app);
if (index == -1)
return -1;
releaseIndex(index);
return index;
}
public synchronized void releaseIndex(final int index) {
mPrefs.edit().remove(appKey(index)).remove(iconKey(index)).remove(originKey(index)).apply();
}
public void putOrigin(int index, String origin) {
mPrefs.edit().putString(originKey(index), origin).apply();
}
public String getOrigin(int index) {
return mPrefs.getString(originKey(index), null);
}
public int getColor(int index) {
return mPrefs.getInt(iconKey(index), -1);
}
/**
* Migrate old prefs to their new equivalents if this app was originally
* installed by the shortcut-based implementation.
*/
public void maybeMigrateOldPrefs(int index) {
if (!mPrefs.contains(oldAppKey(index))) {
return;
}
Log.i(LOGTAG, "migrating old prefs");
// The old appKey pref stored the origin, while the new appKey pref
// stores the packageName, so we migrate oldAppKey to the origin pref.
putOrigin(index, mPrefs.getString(oldAppKey(index), null));
// The old iconKey pref actually stored the splash screen background
// color, so we migrate oldIconKey to the color pref.
updateColor(index, mPrefs.getInt(oldIconKey(index), -1));
// Remove the old prefs so we don't migrate them the next time around.
mPrefs.edit().remove(oldAppKey(index)).remove(oldIconKey(index)).apply();
}
}

View File

@ -1,138 +0,0 @@
/* -*- 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.webapp;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
public class ApkResources {
private static final String LOGTAG = "GeckoWebappApkResources";
private final String mPackageName;
private final ApplicationInfo mInfo;
private final Context mContext;
public ApkResources(Context context, String packageName) throws NameNotFoundException {
mPackageName = packageName;
mInfo = context.getPackageManager().getApplicationInfo(
mPackageName, PackageManager.GET_META_DATA);
mContext = context;
}
private ApplicationInfo info() {
return mInfo;
}
public String getPackageName() {
return mPackageName;
}
private Bundle metadata() {
return mInfo.metaData;
}
public String getManifest(Context context) {
return readResource(context, "manifest");
}
public String getMiniManifest(Context context) {
return readResource(context, "mini");
}
public String getManifestUrl() {
return metadata().getString("manifestUrl");
}
public boolean isPackaged() {
return "packaged".equals(getWebappType());
}
public boolean isDebuggable() {
return (mInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
private String readResource(Context context, String resourceName) {
Uri resourceUri = Uri.parse("android.resource://" + mPackageName
+ "/raw/" + resourceName);
StringBuilder fileContent = new StringBuilder();
try {
final BufferedReader r = new BufferedReader(new InputStreamReader(context
.getContentResolver().openInputStream(resourceUri)));
try {
String line;
while ((line = r.readLine()) != null) {
fileContent.append(line);
}
} finally {
r.close();
}
} catch (FileNotFoundException e) {
Log.e(LOGTAG, String.format("File not found: \"%s\"", resourceName));
} catch (IOException e) {
Log.e(LOGTAG, String.format("Couldn't read file: \"%s\"", resourceName));
}
return fileContent.toString();
}
public Uri getAppIconUri() {
return Uri.parse("android.resource://" + mPackageName + "/" + info().icon);
}
public Drawable getAppIcon() {
return info().loadIcon(mContext.getPackageManager());
}
public String getWebappType() {
return metadata().getString("webapp");
}
public String getAppName() {
return info().name;
}
/**
* Which APK installer installed this APK.
*
* For OEM backed marketplaces, this will be non-<code>null</code>. Otherwise, <code>null</code>.
*
* TODO check that the G+ package installer gives us non-null results.
*
* @return the package name of the APK that installed this.
*/
public String getPackageInstallerName() {
return mContext.getPackageManager().getInstallerPackageName(mPackageName);
}
public Uri getZipFileUri() {
return Uri.parse("android.resource://" + mPackageName + "/raw/application");
}
public File getFileDirectory() {
File dir = mContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
String path = dir.getAbsolutePath().replace(mContext.getPackageName(), mPackageName);
dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
return dir;
}
}

View File

@ -1,55 +0,0 @@
/* -*- 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.webapp;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
public class Dispatcher extends Activity {
private static final String LOGTAG = "GeckoWebappDispatcher";
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
Allocator allocator = Allocator.getInstance(getApplicationContext());
if (bundle == null) {
bundle = getIntent().getExtras();
}
if (bundle == null) {
Log.e(LOGTAG, "Passed intent data missing.");
return;
}
String packageName = bundle.getString("packageName");
if (packageName == null) {
Log.e(LOGTAG, "Package name data missing.");
return;
}
int index = allocator.getIndexForApp(packageName);
boolean isInstalled = index >= 0;
if (!isInstalled) {
index = allocator.findOrAllocatePackage(packageName);
}
// Copy the intent, without interfering with it.
Intent intent = new Intent(getIntent());
// Only change its destination.
intent.setClassName(getApplicationContext(), "org.mozilla.gecko.webapp.Webapps$Webapp" + index);
// If and only if we haven't seen this before.
intent.putExtra("isInstalled", isInstalled);
startActivity(intent);
}
}

View File

@ -1,248 +0,0 @@
/* -*- 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.webapp;
import java.io.File;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.ActivityHandlerHelper;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.util.ActivityResultHandler;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.util.ThreadUtils;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.util.Log;
public class EventListener implements NativeEventListener {
private static final String LOGTAG = "GeckoWebappEventListener";
public void registerEvents() {
EventDispatcher.getInstance().registerGeckoThreadListener(this,
"Webapps:Preinstall",
"Webapps:InstallApk",
"Webapps:UninstallApk",
"Webapps:Postinstall",
"Webapps:Launch",
"Webapps:Uninstall",
"Webapps:GetApkVersions");
}
public void unregisterEvents() {
EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
"Webapps:Preinstall",
"Webapps:InstallApk",
"Webapps:UninstallApk",
"Webapps:Postinstall",
"Webapps:Launch",
"Webapps:Uninstall",
"Webapps:GetApkVersions");
}
@Override
public void handleMessage(String event, NativeJSObject message, EventCallback callback) {
try {
if (event.equals("Webapps:InstallApk")) {
installApk(GeckoAppShell.getGeckoInterface().getActivity(), message, callback);
} else if (event.equals("Webapps:UninstallApk")) {
uninstallApk(GeckoAppShell.getGeckoInterface().getActivity(), message);
} else if (event.equals("Webapps:Postinstall")) {
postInstallWebapp(message.getString("apkPackageName"), message.getString("origin"));
} else if (event.equals("Webapps:Launch")) {
launchWebapp(message.getString("packageName"));
} else if (event.equals("Webapps:GetApkVersions")) {
JSONObject obj = new JSONObject();
obj.put("versions", getApkVersions(GeckoAppShell.getGeckoInterface().getActivity(),
message.getStringArray("packageNames")));
callback.sendSuccess(obj);
}
} catch (Exception e) {
Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
}
}
public static void postInstallWebapp(String aPackageName, String aOrigin) {
Allocator allocator = Allocator.getInstance(GeckoAppShell.getContext());
int index = allocator.findOrAllocatePackage(aPackageName);
allocator.putOrigin(index, aOrigin);
}
private void launchWebapp(String aPackageName) {
Intent intent = GeckoAppShell.getContext().getPackageManager().getLaunchIntentForPackage(aPackageName);
GeckoAppShell.getGeckoInterface().getActivity().startActivity(intent);
}
public static void uninstallWebapp(final String packageName) {
// On uninstall, we need to do a couple of things:
// 1. nuke the running app process.
// 2. nuke the profile that was assigned to that webapp
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
int index = Allocator.getInstance(GeckoAppShell.getContext()).releaseIndexForApp(packageName);
// if -1, nothing to do; we didn't think it was installed anyway
if (index == -1)
return;
killWebappSlot(GeckoAppShell.getContext(), index);
// then nuke the profile
GeckoProfile.removeProfile(GeckoAppShell.getContext(), "webapp" + index);
}
});
}
/**
* Used in both uninstall and swiping away from the recent task list.
*
* @param context
* @param slot
*/
public static void killWebappSlot(Context context, int slot) {
// kill the app if it's running
String targetProcessName = context.getPackageName();
targetProcessName = targetProcessName + ":" + targetProcessName + ".Webapp" + slot;
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> procs = am.getRunningAppProcesses();
if (procs != null) {
for (ActivityManager.RunningAppProcessInfo proc : procs) {
if (proc.processName.equals(targetProcessName)) {
android.os.Process.killProcess(proc.pid);
break;
}
}
}
}
public static void installApk(final Activity context, NativeJSObject message, final EventCallback callback) {
final JSONObject messageData;
// We get the manifest url out of javascript here so we can use it as a checksum
// in InstallListener when a package has been installed.
String manifestUrl;
String filePath;
try {
filePath = message.getString("filePath");
messageData = new JSONObject(message.getObject("data").toString());
manifestUrl = messageData.getJSONObject("app").getString("manifestURL");
} catch (JSONException e) {
Log.wtf(LOGTAG, "Error getting file path and data", e);
callback.sendError("Error getting file path and data: " + e.toString());
return;
}
final File file = new File(filePath);
if (!file.exists()) {
Log.wtf(LOGTAG, "APK file doesn't exist at path " + filePath);
callback.sendError("APK file doesn't exist at path " + filePath);
return;
}
// We will check the manifestUrl from the one in the APK.
// Thus, we can have a one-to-one mapping of apk to receiver.
final InstallListener receiver = new InstallListener(manifestUrl, messageData, file);
// Listen for packages being installed.
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addDataScheme("package");
// As of API 19 we can do something like this to only trigger this receiver
// for a specific package name:
// int currentApiVersion = android.os.Build.VERSION.SDK_INT;
// if (currentApiVersion >= android.os.Build.VERSION_CODES.KITKAT){ // KITKAT == 19
// filter.addDataSchemeSpecificPart("com.example.someapp", PatternMatcher.PATTERN_LITERAL);
// }
// TODO: Implement package name filtering to IntentFilter.
context.registerReceiver(receiver, filter);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
// Now call the package installer.
ActivityHandlerHelper.startIntentForActivity(context, intent, new ActivityResultHandler() {
// Invoked if the user cancels installation or presses the 'Done'
// button once the app has been successfully installed. It may also
// be called when the user presses Open and then returns to Fennec.
@Override
public void onActivityResult(int resultCode, Intent data) {
if (!receiver.isReceived()) {
callback.sendError("APK installation cancelled by user");
context.unregisterReceiver(receiver);
}
if (file.delete()) {
Log.i(LOGTAG, "Downloaded APK file deleted");
}
}
});
}
public static void uninstallApk(final Activity context, NativeJSObject message) {
final String packageName = message.getString("apkPackageName");
final Uri packageUri = Uri.parse("package:" + packageName);
final Intent intent;
if (Versions.preICS) {
intent = new Intent(Intent.ACTION_DELETE, packageUri);
} else {
intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri);
}
context.startActivity(intent);
}
private static final int DEFAULT_VERSION_CODE = -1;
public static JSONObject getApkVersions(Activity context, String[] packageNames) {
Set<String> packageNameSet = new HashSet<String>();
packageNameSet.addAll(Arrays.asList(packageNames));
final PackageManager pm = context.getPackageManager();
List<ApplicationInfo> apps = pm.getInstalledApplications(0);
JSONObject jsonMessage = new JSONObject();
for (ApplicationInfo app : apps) {
if (packageNameSet.contains(app.packageName)) {
int versionCode = DEFAULT_VERSION_CODE;
try {
versionCode = pm.getPackageInfo(app.packageName, 0).versionCode;
} catch (PackageManager.NameNotFoundException e) {
Log.e(LOGTAG, "couldn't get version for app " + app.packageName, e);
}
try {
jsonMessage.put(app.packageName, versionCode);
} catch (JSONException e) {
Log.e(LOGTAG, "unable to store version code field for app " + app.packageName, e);
}
}
}
return jsonMessage;
}
}

View File

@ -1,174 +0,0 @@
/* -*- 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.webapp;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.util.ThreadUtils;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.util.Log;
public class InstallHelper implements NativeEventListener {
private static final String LOGTAG = "GeckoWebappInstallHelper";
private static final String[] INSTALL_EVENT_NAMES = new String[] {"Webapps:Postinstall"};
private final Context mContext;
private final InstallCallback mCallback;
private final ApkResources mApkResources;
public static interface InstallCallback {
// on the GeckoThread
void installCompleted(InstallHelper installHelper, String event, NativeJSObject message);
// on the GeckoBackgroundThread
void installErrored(InstallHelper installHelper, Exception exception);
}
public InstallHelper(Context context, ApkResources apkResources, InstallCallback cb) {
mContext = context;
mCallback = cb;
mApkResources = apkResources;
}
public void startInstall(String profileName) throws IOException {
startInstall(profileName, null);
}
public void startInstall(final String profileName, final JSONObject message) throws IOException {
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
try {
install(profileName, message);
} catch (IOException e) {
handleException(e);
}
}
});
}
protected void handleException(Exception e) {
if (mCallback != null) {
mCallback.installErrored(this, e);
} else {
Log.e(LOGTAG, "mozApps.install failed", e);
}
}
void install(String profileName, JSONObject message) throws IOException {
if (message == null) {
message = new JSONObject();
}
// we can change the profile to be in the app's area here
GeckoProfile profile = GeckoProfile.get(mContext, profileName);
try {
message.put("apkPackageName", mApkResources.getPackageName());
message.put("manifestURL", mApkResources.getManifestUrl());
message.put("title", mApkResources.getAppName());
message.put("manifest", new JSONObject(mApkResources.getManifest(mContext)));
String appType = mApkResources.getWebappType();
message.putOpt("type", appType);
if ("packaged".equals(appType)) {
message.putOpt("updateManifest", new JSONObject(mApkResources.getMiniManifest(mContext)));
}
message.putOpt("profilePath", profile.getDir());
if (mApkResources.isPackaged()) {
File zipFile = copyApplicationZipFile();
message.putOpt("zipFilePath", Uri.fromFile(zipFile).toString());
}
} catch (JSONException e) {
handleException(e);
return;
}
registerGeckoListener();
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Webapps:AutoInstall", message.toString()));
calculateColor();
}
public File copyApplicationZipFile() throws IOException {
if (!mApkResources.isPackaged()) {
return null;
}
Uri uri = mApkResources.getZipFileUri();
InputStream in = null;
OutputStream out = null;
File destPath = new File(mApkResources.getFileDirectory(), "application.zip");
try {
in = mContext.getContentResolver().openInputStream(uri);
out = new FileOutputStream(destPath);
byte[] buffer = new byte[1024];
int read = 0;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
out.flush();
} catch (IOException e) {
throw e;
} finally {
close(in);
close(out);
}
return destPath;
}
private static void close(Closeable close) {
if (close == null) {
return;
}
try {
close.close();
} catch (IOException e) {
// NOP
}
}
public void registerGeckoListener() {
EventDispatcher.getInstance().registerGeckoThreadListener(this, INSTALL_EVENT_NAMES);
}
private void calculateColor() {
ThreadUtils.assertOnBackgroundThread();
Allocator slots = Allocator.getInstance(mContext);
int index = slots.getIndexForApp(mApkResources.getPackageName());
Bitmap bitmap = BitmapUtils.getBitmapFromDrawable(mApkResources.getAppIcon());
slots.updateColor(index, BitmapUtils.getDominantColor(bitmap));
}
@Override
public void handleMessage(String event, NativeJSObject message, EventCallback callback) {
EventDispatcher.getInstance().unregisterGeckoThreadListener(this, INSTALL_EVENT_NAMES);
if (mCallback != null) {
mCallback.installCompleted(this, event, message);
}
}
}

View File

@ -1,115 +0,0 @@
/* -*- 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.webapp;
import java.io.File;
import java.io.IOException;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.GeckoThread;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.text.TextUtils;
import android.util.Log;
public class InstallListener extends BroadcastReceiver {
private static final String LOGTAG = "GeckoWebappInstallListener";
private final JSONObject mData;
private final String mManifestUrl;
private boolean mReceived;
private final File mApkFile;
public InstallListener(String manifestUrl, JSONObject data, File apkFile) {
mData = data;
mApkFile = apkFile;
mManifestUrl = manifestUrl;
if (mManifestUrl == null) {
throw new IllegalArgumentException("manifestUrl must not be null");
}
if (mApkFile == null || mApkFile.exists()) {
throw new IllegalArgumentException("apkFile must not be null and must exist");
}
}
public boolean isReceived() {
return mReceived;
}
@Override
public void onReceive(Context context, Intent intent) {
String packageName = intent.getData().getSchemeSpecificPart();
if (TextUtils.isEmpty(packageName)) {
Log.i(LOGTAG, "No package name defined in intent");
return;
}
ApkResources apkResources = null;
try {
apkResources = new ApkResources(context, packageName);
} catch (NameNotFoundException e) {
Log.e(LOGTAG, "Can't find package that's just been installed");
return;
}
String manifestUrl = apkResources.getManifestUrl();
if (TextUtils.isEmpty(manifestUrl)) {
Log.i(LOGTAG, "No manifest URL present in metadata");
return;
}
if (!isCorrectManifest(manifestUrl)) {
// This happens when the updater triggers installation of multiple
// APK updates simultaneously. If we're the receiver for another
// update, then simply ignore this intent by returning early.
Log.i(LOGTAG, "Manifest URL is for a different install; ignoring");
return;
}
// If we're here then everything is looking good and installation can continue.
mReceived = true;
context.unregisterReceiver(this);
if (mApkFile != null && mApkFile.delete()) {
Log.i(LOGTAG, "Downloaded APK file deleted");
}
if (GeckoThread.isRunning()) {
InstallHelper installHelper = new InstallHelper(context, apkResources, null);
try {
JSONObject dataObject = new JSONObject();
dataObject.put("request", mData);
Allocator slots = Allocator.getInstance(context);
int i = slots.findOrAllocatePackage(packageName);
installHelper.startInstall("webapp" + i, dataObject);
} catch (JSONException e) {
Log.e(LOGTAG, "Couldn't parse data from mozApps.install()", e);
} catch (IOException e) {
Log.e(LOGTAG, "Couldn't install packaged app", e);
}
}
}
public boolean isCorrectManifest(String manifestUrl) {
// Don't use URL and the sameFile method as this also includes the query which
// we want to ignore.
try {
String registeredUrl = mManifestUrl.split("\\?")[0];
String observedUrl = manifestUrl.split("\\?")[0];
return registeredUrl.equals(observedUrl);
} catch (NullPointerException e) {
Log.e(LOGTAG, "One or both of the manifest URLs is null", e);
}
return false;
}
}

View File

@ -1,35 +0,0 @@
/* -*- 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.webapp;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
/**
* This BroadcastReceiver is registered in the AndroidManifest.xml.in file.
*
* <p>It listens for intents sent by synthesized APKs when the task has been ended.
* e.g. when the user has swiped it out of the Recent Apps List.</p>
*
*/
public class TaskKiller extends BroadcastReceiver {
private static final String LOGTAG = "GeckoWebappTaskKiller";
@Override
public void onReceive(Context context, Intent intent) {
String packageName = intent.getStringExtra("packageName");
int slot = Allocator.getInstance(context).getIndexForApp(packageName);
if (slot >= 0) {
EventListener.killWebappSlot(context, slot);
} else {
Log.w(LOGTAG, "Asked to kill " + packageName + " but this runtime (" + context.getPackageName() + ") doesn't know about it.");
}
}
}

View File

@ -1,153 +0,0 @@
/* -*- 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.webapp;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.util.ThreadUtils;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONArray;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.ArrayList;
public class UninstallListener extends BroadcastReceiver {
private static final String LOGTAG = "GeckoWebappUninstallListener";
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
Log.i(LOGTAG, "Package is being replaced; ignoring removal intent");
return;
}
String packageName = intent.getData().getSchemeSpecificPart();
if (TextUtils.isEmpty(packageName)) {
Log.i(LOGTAG, "No package name defined in intent");
return;
}
Allocator allocator = Allocator.getInstance(context);
ArrayList<String> installedPackages = allocator.getInstalledPackageNames();
if (installedPackages.contains(packageName)) {
doUninstall(context, packageName);
}
}
private static void doUninstall(Context context, String packageName) {
ArrayList<String> uninstalledPackages = new ArrayList<String>();
uninstalledPackages.add(packageName);
doUninstall(context, uninstalledPackages);
}
private static void doUninstall(Context context, ArrayList<String> packageNames) {
Allocator allocator = Allocator.getInstance(context);
JSONObject message = new JSONObject();
JSONArray jsonPackages = new JSONArray();
for (String packageName : packageNames) {
// Although its unlikely that an app is not allocated, but is installed in Gecko, it
// is possible. We always send the packageName to JS to be removed from Gecko's registry.
jsonPackages.put(packageName);
int index = allocator.getIndexForApp(packageName);
// If -1, nothing more to do; we didn't think it was installed anyway.
if (index == -1)
continue;
allocator.releaseIndex(index);
// kill the app if it's running
String targetProcessName = context.getPackageName();
targetProcessName = targetProcessName + ":" + targetProcessName + ".Webapp" + index;
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> procs = am.getRunningAppProcesses();
if (procs != null) {
for (ActivityManager.RunningAppProcessInfo proc : procs) {
if (proc.processName.equals(targetProcessName)) {
android.os.Process.killProcess(proc.pid);
break;
}
}
}
// then nuke the profile
GeckoProfile.removeProfile(context, "webapp" + index);
}
try {
message.put("apkPackageNames", jsonPackages);
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Webapps:AutoUninstall", message.toString()));
} catch (JSONException e) {
Log.e(LOGTAG, "Error sending uninstall packages to Gecko", e);
}
}
public static void initUninstallPackageScan(Context context) {
// get list of packages we think are installed
Allocator allocator = Allocator.getInstance(context);
ArrayList<String> fennecPackages = allocator.getInstalledPackageNames();
ArrayList<String> uninstalledPackages = new ArrayList<String>();
final PackageManager pm = context.getPackageManager();
//get a list of installed apps on device
List<ApplicationInfo> packages = pm.getInstalledApplications(PackageManager.GET_META_DATA);
Set<String> allInstalledPackages = new HashSet<String>();
for (ApplicationInfo packageInfo : packages) {
allInstalledPackages.add(packageInfo.packageName);
}
for (String packageName : fennecPackages) {
if (!allInstalledPackages.contains(packageName)) {
uninstalledPackages.add(packageName);
}
}
if (uninstalledPackages.size() > 0) {
doUninstall(context, uninstalledPackages);
}
}
public static class DelayedStartupTask implements Runnable {
private final GeckoApp mApp;
public DelayedStartupTask(GeckoApp app) {
mApp = app;
}
@Override
public void run() {
ThreadUtils.assertOnBackgroundThread();
// Perform webapp uninstalls as appropriate.
UninstallListener.initUninstallPackageScan(mApp.getApplicationContext());
}
}
}

View File

@ -1,413 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.webapp;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.GeckoThread;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.StubBrowserDB;
import org.mozilla.gecko.mozglue.ContextUtils.SafeIntent;
import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.webapp.InstallHelper.InstallCallback;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.TextView;
public class WebappImpl extends GeckoApp implements InstallCallback {
private static final String LOGTAG = "GeckoWebappImpl";
private URI mOrigin;
private TextView mTitlebarText;
private View mTitlebar;
// Must only be accessed from the UI thread.
View mSplashscreen;
private boolean mIsApk = true;
private ApkResources mApkResources;
private String mManifestUrl;
private String mAppName;
protected int getIndex() { return 0; }
@Override
public int getLayout() { return R.layout.web_app; }
public WebappImpl() {
GeckoProfile.setBrowserDBFactory(new BrowserDB.Factory() {
@Override
public BrowserDB get(String profileName, File profileDir) {
return new StubBrowserDB(profileName);
}
});
}
@Override
public void onCreate(Bundle savedInstance) {
Bundle extras = getIntent().getExtras();
if (extras == null) {
extras = savedInstance;
}
if (extras == null) {
extras = new Bundle();
}
boolean isInstalled = extras.getBoolean("isInstalled", false);
String packageName = extras.getString("packageName");
if (packageName == null) {
Log.w(LOGTAG, "no package name; treating as legacy shortcut");
mIsApk = false;
// Shortcut apps are already installed.
isInstalled = true;
Uri data = getIntent().getData();
if (data == null) {
Log.wtf(LOGTAG, "can't get manifest URL from shortcut data");
setResult(RESULT_CANCELED);
finish();
return;
}
mManifestUrl = data.toString();
String shortcutName = extras.getString(Intent.EXTRA_SHORTCUT_NAME);
mAppName = shortcutName != null ? shortcutName : "Web App";
} else {
try {
mApkResources = new ApkResources(this, packageName);
} catch (NameNotFoundException e) {
Log.e(LOGTAG, "Can't find package for webapp " + packageName, e);
setResult(RESULT_CANCELED);
finish();
return;
}
mManifestUrl = mApkResources.getManifestUrl();
mAppName = mApkResources.getAppName();
}
// start Gecko.
super.onCreate(savedInstance);
mTitlebarText = (TextView)findViewById(R.id.webapp_title);
mTitlebar = findViewById(R.id.webapp_titlebar);
mSplashscreen = findViewById(R.id.splashscreen);
Allocator allocator = Allocator.getInstance(this);
int index = getIndex();
// We have to migrate old prefs before getting the origin because origin
// is one of the prefs we might migrate.
allocator.maybeMigrateOldPrefs(index);
String origin = allocator.getOrigin(index);
boolean isInstallCompleting = (origin == null);
if (!GeckoThread.isRunning() || !isInstalled || isInstallCompleting) {
// Show the splash screen if we need to start Gecko, or we need to install this.
overridePendingTransition(R.anim.grow_fade_in_center, android.R.anim.fade_out);
showSplash();
} else {
mSplashscreen.setVisibility(View.GONE);
}
if (!isInstalled || isInstallCompleting) {
InstallHelper installHelper = new InstallHelper(getApplicationContext(), mApkResources, this);
if (!isInstalled) {
// start the vanilla install.
try {
installHelper.startInstall(getDefaultProfileName());
} catch (IOException e) {
Log.e(LOGTAG, "Couldn't install packaged app", e);
}
} else {
// an install is already happening, so we should let it complete.
Log.i(LOGTAG, "Waiting for existing install to complete");
installHelper.registerGeckoListener();
}
return;
}
launchWebapp(origin);
setTitle(mAppName);
}
@Override
protected String getURIFromIntent(SafeIntent intent) {
String uri = super.getURIFromIntent(intent);
if (uri != null) {
return uri;
}
// This is where we construct the URL from the Intent from the
// the synthesized APK.
// TODO Translate AndroidIntents into WebActivities here.
if (mIsApk) {
return mApkResources.getManifestUrl();
}
// If this is a legacy shortcut, then we should have been able to get
// the URI from the intent data. Otherwise, we should have been able
// to get it from the APK resources. So we should never get here.
Log.wtf(LOGTAG, "Couldn't get URI from intent nor APK resources");
return null;
}
@Override
protected void loadStartupTab(final int flags) {
loadStartupTab(null, null, flags);
}
// Note: there is no support for loading with intent extras in
// Webapps at the moment because I don't have time to debug/test.
@Override
protected void loadStartupTab(final String uri, final SafeIntent unusedIntent, int flags) {
// Load a tab so it's available for any code that assumes a tab
// before the app tab itself is loaded in BrowserApp._loadWebapp.
flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_USER_ENTERED | Tabs.LOADURL_EXTERNAL;
super.loadStartupTab("about:blank", null, flags);
}
private void showSplash() {
// get the favicon dominant color, stored when the app was installed
int dominantColor = Allocator.getInstance().getColor(getIndex());
setBackgroundGradient(dominantColor);
ImageView image = (ImageView)findViewById(R.id.splashscreen_icon);
Drawable d = null;
if (mIsApk) {
Uri uri = mApkResources.getAppIconUri();
image.setImageURI(uri);
d = image.getDrawable();
} else {
// look for a logo.png in the profile dir and show it. If we can't find a logo show nothing
File profile = getProfile().getDir();
File logoFile = new File(profile, "logo.png");
if (logoFile.exists()) {
d = Drawable.createFromPath(logoFile.getPath());
image.setImageDrawable(d);
}
}
if (d != null) {
Animation fadein = AnimationUtils.loadAnimation(this, R.anim.grow_fade_in_center);
fadein.setStartOffset(500);
fadein.setDuration(1000);
image.startAnimation(fadein);
}
}
public void setBackgroundGradient(int dominantColor) {
int[] colors = new int[2];
// now lighten it, to ensure that the icon stands out in the center
float[] f = new float[3];
Color.colorToHSV(dominantColor, f);
f[2] = Math.min(f[2]*2, 1.0f);
colors[0] = Color.HSVToColor(255, f);
// now generate a second, slightly darker version of the same color
f[2] *= 0.75;
colors[1] = Color.HSVToColor(255, f);
// Draw the background gradient
GradientDrawable gd = new GradientDrawable(GradientDrawable.Orientation.TL_BR, colors);
gd.setGradientType(GradientDrawable.RADIAL_GRADIENT);
Display display = getWindowManager().getDefaultDisplay();
gd.setGradientCenter(0.5f, 0.5f);
gd.setGradientRadius(Math.max(display.getWidth()/2, display.getHeight()/2));
mSplashscreen.setBackgroundDrawable(gd);
}
/* (non-Javadoc)
* @see org.mozilla.gecko.GeckoApp#getDefaultProfileName()
*/
@Override
protected String getDefaultProfileName() {
return "webapp" + getIndex();
}
@Override
protected boolean getSessionRestoreState(Bundle savedInstanceState) {
// for now webapps never restore your session
return false;
}
@Override
public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
switch(msg) {
case SELECTED:
case LOCATION_CHANGE:
if (Tabs.getInstance().isSelectedTab(tab)) {
final String urlString = tab.getURL();
// Don't show the titlebar for about:blank, which we load
// into the initial tab we create while waiting for the app
// to load.
if (urlString != null && urlString.equals("about:blank")) {
mTitlebar.setVisibility(View.GONE);
return;
}
final URI uri;
try {
uri = new URI(urlString);
} catch (java.net.URISyntaxException ex) {
mTitlebarText.setText(urlString);
// If we can't parse the url, and its an app protocol hide
// the titlebar and return, otherwise show the titlebar
// and the full url
if (urlString != null && !urlString.startsWith("app://")) {
mTitlebar.setVisibility(View.VISIBLE);
} else {
mTitlebar.setVisibility(View.GONE);
}
return;
}
if (mOrigin != null && mOrigin.getHost().equals(uri.getHost())) {
mTitlebar.setVisibility(View.GONE);
} else {
mTitlebarText.setText(uri.getScheme() + "://" + uri.getHost());
mTitlebar.setVisibility(View.VISIBLE);
}
}
break;
case LOADED:
hideSplash();
break;
case START:
if (mSplashscreen != null && mSplashscreen.getVisibility() == View.VISIBLE) {
View area = findViewById(R.id.splashscreen_progress);
area.setVisibility(View.VISIBLE);
Animation fadein = AnimationUtils.loadAnimation(this, android.R.anim.fade_in);
fadein.setDuration(1000);
area.startAnimation(fadein);
}
break;
}
super.onTabChanged(tab, msg, data);
}
protected void hideSplash() {
if (mSplashscreen != null && mSplashscreen.getVisibility() == View.VISIBLE) {
Animation fadeout = AnimationUtils.loadAnimation(this, android.R.anim.fade_out);
fadeout.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationEnd(Animation animation) {
mSplashscreen.setVisibility(View.GONE);
}
@Override
public void onAnimationRepeat(Animation animation) { }
@Override
public void onAnimationStart(Animation animation) { }
});
mSplashscreen.startAnimation(fadeout);
}
}
@Override
public void installCompleted(InstallHelper installHelper, String event, NativeJSObject message) {
if (event == null) {
return;
}
if (event.equals("Webapps:Postinstall")) {
String origin = message.optString("origin", null);
launchWebapp(origin);
}
}
@Override
public void installErrored(InstallHelper installHelper, Exception exception) {
Log.e(LOGTAG, "Install errored", exception);
}
private void setOrigin(String origin) {
try {
mOrigin = new URI(origin);
} catch (java.net.URISyntaxException ex) {
// If this isn't an app: URL, just settle for not having an origin.
if (!origin.startsWith("app://")) {
return;
}
// If that failed fall back to the origin stored in the shortcut.
if (!mIsApk) {
Log.i(LOGTAG, "Origin is app: URL; falling back to intent URL");
Uri data = getIntent().getData();
if (data != null) {
try {
mOrigin = new URI(data.toString());
} catch (java.net.URISyntaxException ex2) {
Log.e(LOGTAG, "Unable to parse intent URL: ", ex);
}
}
}
}
}
public void launchWebapp(String origin) {
setOrigin(origin);
try {
JSONObject launchObject = new JSONObject();
launchObject.putOpt("url", mManifestUrl);
launchObject.putOpt("name", mAppName);
Log.i(LOGTAG, "Trying to launch: " + launchObject);
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Webapps:Load", launchObject.toString()));
} catch (JSONException e) {
Log.e(LOGTAG, "Error populating launch message", e);
}
}
@Override
protected boolean getIsDebuggable() {
if (mIsApk) {
return mApkResources.isDebuggable();
}
// This is a legacy shortcut, which didn't provide a way to determine
// that the app is debuggable, so we say the app is not debuggable.
return false;
}
@Override
protected StartupAction getStartupAction(final String passedURL, final String action) {
return StartupAction.WEBAPP;
}
}

View File

@ -1,513 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.webapp;
/**
* Declare a predefined number of Webapp<num> classes to the Webapps class.
* These are used so that each web app can launch in its own process. Keep this
* number in sync with the number of web apps defined in the Android manifest.
*/
public final class Webapps {
public static class Webapp0 extends WebappImpl {
@Override
protected int getIndex() { return 0; }
}
public static class Webapp1 extends WebappImpl {
@Override
protected int getIndex() { return 1; }
}
public static class Webapp2 extends WebappImpl {
@Override
protected int getIndex() { return 2; }
}
public static class Webapp3 extends WebappImpl {
@Override
protected int getIndex() { return 3; }
}
public static class Webapp4 extends WebappImpl {
@Override
protected int getIndex() { return 4; }
}
public static class Webapp5 extends WebappImpl {
@Override
protected int getIndex() { return 5; }
}
public static class Webapp6 extends WebappImpl {
@Override
protected int getIndex() { return 6; }
}
public static class Webapp7 extends WebappImpl {
@Override
protected int getIndex() { return 7; }
}
public static class Webapp8 extends WebappImpl {
@Override
protected int getIndex() { return 8; }
}
public static class Webapp9 extends WebappImpl {
@Override
protected int getIndex() { return 9; }
}
public static class Webapp10 extends WebappImpl {
@Override
protected int getIndex() { return 10; }
}
public static class Webapp11 extends WebappImpl {
@Override
protected int getIndex() { return 11; }
}
public static class Webapp12 extends WebappImpl {
@Override
protected int getIndex() { return 12; }
}
public static class Webapp13 extends WebappImpl {
@Override
protected int getIndex() { return 13; }
}
public static class Webapp14 extends WebappImpl {
@Override
protected int getIndex() { return 14; }
}
public static class Webapp15 extends WebappImpl {
@Override
protected int getIndex() { return 15; }
}
public static class Webapp16 extends WebappImpl {
@Override
protected int getIndex() { return 16; }
}
public static class Webapp17 extends WebappImpl {
@Override
protected int getIndex() { return 17; }
}
public static class Webapp18 extends WebappImpl {
@Override
protected int getIndex() { return 18; }
}
public static class Webapp19 extends WebappImpl {
@Override
protected int getIndex() { return 19; }
}
public static class Webapp20 extends WebappImpl {
@Override
protected int getIndex() { return 20; }
}
public static class Webapp21 extends WebappImpl {
@Override
protected int getIndex() { return 21; }
}
public static class Webapp22 extends WebappImpl {
@Override
protected int getIndex() { return 22; }
}
public static class Webapp23 extends WebappImpl {
@Override
protected int getIndex() { return 23; }
}
public static class Webapp24 extends WebappImpl {
@Override
protected int getIndex() { return 24; }
}
public static class Webapp25 extends WebappImpl {
@Override
protected int getIndex() { return 25; }
}
public static class Webapp26 extends WebappImpl {
@Override
protected int getIndex() { return 26; }
}
public static class Webapp27 extends WebappImpl {
@Override
protected int getIndex() { return 27; }
}
public static class Webapp28 extends WebappImpl {
@Override
protected int getIndex() { return 28; }
}
public static class Webapp29 extends WebappImpl {
@Override
protected int getIndex() { return 29; }
}
public static class Webapp30 extends WebappImpl {
@Override
protected int getIndex() { return 30; }
}
public static class Webapp31 extends WebappImpl {
@Override
protected int getIndex() { return 31; }
}
public static class Webapp32 extends WebappImpl {
@Override
protected int getIndex() { return 32; }
}
public static class Webapp33 extends WebappImpl {
@Override
protected int getIndex() { return 33; }
}
public static class Webapp34 extends WebappImpl {
@Override
protected int getIndex() { return 34; }
}
public static class Webapp35 extends WebappImpl {
@Override
protected int getIndex() { return 35; }
}
public static class Webapp36 extends WebappImpl {
@Override
protected int getIndex() { return 36; }
}
public static class Webapp37 extends WebappImpl {
@Override
protected int getIndex() { return 37; }
}
public static class Webapp38 extends WebappImpl {
@Override
protected int getIndex() { return 38; }
}
public static class Webapp39 extends WebappImpl {
@Override
protected int getIndex() { return 39; }
}
public static class Webapp40 extends WebappImpl {
@Override
protected int getIndex() { return 40; }
}
public static class Webapp41 extends WebappImpl {
@Override
protected int getIndex() { return 41; }
}
public static class Webapp42 extends WebappImpl {
@Override
protected int getIndex() { return 42; }
}
public static class Webapp43 extends WebappImpl {
@Override
protected int getIndex() { return 43; }
}
public static class Webapp44 extends WebappImpl {
@Override
protected int getIndex() { return 44; }
}
public static class Webapp45 extends WebappImpl {
@Override
protected int getIndex() { return 45; }
}
public static class Webapp46 extends WebappImpl {
@Override
protected int getIndex() { return 46; }
}
public static class Webapp47 extends WebappImpl {
@Override
protected int getIndex() { return 47; }
}
public static class Webapp48 extends WebappImpl {
@Override
protected int getIndex() { return 48; }
}
public static class Webapp49 extends WebappImpl {
@Override
protected int getIndex() { return 49; }
}
public static class Webapp50 extends WebappImpl {
@Override
protected int getIndex() { return 50; }
}
public static class Webapp51 extends WebappImpl {
@Override
protected int getIndex() { return 51; }
}
public static class Webapp52 extends WebappImpl {
@Override
protected int getIndex() { return 52; }
}
public static class Webapp53 extends WebappImpl {
@Override
protected int getIndex() { return 53; }
}
public static class Webapp54 extends WebappImpl {
@Override
protected int getIndex() { return 54; }
}
public static class Webapp55 extends WebappImpl {
@Override
protected int getIndex() { return 55; }
}
public static class Webapp56 extends WebappImpl {
@Override
protected int getIndex() { return 56; }
}
public static class Webapp57 extends WebappImpl {
@Override
protected int getIndex() { return 57; }
}
public static class Webapp58 extends WebappImpl {
@Override
protected int getIndex() { return 58; }
}
public static class Webapp59 extends WebappImpl {
@Override
protected int getIndex() { return 59; }
}
public static class Webapp60 extends WebappImpl {
@Override
protected int getIndex() { return 60; }
}
public static class Webapp61 extends WebappImpl {
@Override
protected int getIndex() { return 61; }
}
public static class Webapp62 extends WebappImpl {
@Override
protected int getIndex() { return 62; }
}
public static class Webapp63 extends WebappImpl {
@Override
protected int getIndex() { return 63; }
}
public static class Webapp64 extends WebappImpl {
@Override
protected int getIndex() { return 64; }
}
public static class Webapp65 extends WebappImpl {
@Override
protected int getIndex() { return 65; }
}
public static class Webapp66 extends WebappImpl {
@Override
protected int getIndex() { return 66; }
}
public static class Webapp67 extends WebappImpl {
@Override
protected int getIndex() { return 67; }
}
public static class Webapp68 extends WebappImpl {
@Override
protected int getIndex() { return 68; }
}
public static class Webapp69 extends WebappImpl {
@Override
protected int getIndex() { return 69; }
}
public static class Webapp70 extends WebappImpl {
@Override
protected int getIndex() { return 70; }
}
public static class Webapp71 extends WebappImpl {
@Override
protected int getIndex() { return 71; }
}
public static class Webapp72 extends WebappImpl {
@Override
protected int getIndex() { return 72; }
}
public static class Webapp73 extends WebappImpl {
@Override
protected int getIndex() { return 73; }
}
public static class Webapp74 extends WebappImpl {
@Override
protected int getIndex() { return 74; }
}
public static class Webapp75 extends WebappImpl {
@Override
protected int getIndex() { return 75; }
}
public static class Webapp76 extends WebappImpl {
@Override
protected int getIndex() { return 76; }
}
public static class Webapp77 extends WebappImpl {
@Override
protected int getIndex() { return 77; }
}
public static class Webapp78 extends WebappImpl {
@Override
protected int getIndex() { return 78; }
}
public static class Webapp79 extends WebappImpl {
@Override
protected int getIndex() { return 79; }
}
public static class Webapp80 extends WebappImpl {
@Override
protected int getIndex() { return 80; }
}
public static class Webapp81 extends WebappImpl {
@Override
protected int getIndex() { return 81; }
}
public static class Webapp82 extends WebappImpl {
@Override
protected int getIndex() { return 82; }
}
public static class Webapp83 extends WebappImpl {
@Override
protected int getIndex() { return 83; }
}
public static class Webapp84 extends WebappImpl {
@Override
protected int getIndex() { return 84; }
}
public static class Webapp85 extends WebappImpl {
@Override
protected int getIndex() { return 85; }
}
public static class Webapp86 extends WebappImpl {
@Override
protected int getIndex() { return 86; }
}
public static class Webapp87 extends WebappImpl {
@Override
protected int getIndex() { return 87; }
}
public static class Webapp88 extends WebappImpl {
@Override
protected int getIndex() { return 88; }
}
public static class Webapp89 extends WebappImpl {
@Override
protected int getIndex() { return 89; }
}
public static class Webapp90 extends WebappImpl {
@Override
protected int getIndex() { return 90; }
}
public static class Webapp91 extends WebappImpl {
@Override
protected int getIndex() { return 91; }
}
public static class Webapp92 extends WebappImpl {
@Override
protected int getIndex() { return 92; }
}
public static class Webapp93 extends WebappImpl {
@Override
protected int getIndex() { return 93; }
}
public static class Webapp94 extends WebappImpl {
@Override
protected int getIndex() { return 94; }
}
public static class Webapp95 extends WebappImpl {
@Override
protected int getIndex() { return 95; }
}
public static class Webapp96 extends WebappImpl {
@Override
protected int getIndex() { return 96; }
}
public static class Webapp97 extends WebappImpl {
@Override
protected int getIndex() { return 97; }
}
public static class Webapp98 extends WebappImpl {
@Override
protected int getIndex() { return 98; }
}
public static class Webapp99 extends WebappImpl {
@Override
protected int getIndex() { return 99; }
}
}

View File

@ -49,8 +49,8 @@ public class ThemedEditText extends android.widget.EditText
}
private void initialize(final Context context, final AttributeSet attrs, final int defStyle) {
// The theme can be null, particularly for webapps: Bug 1089266. Or we
// might be instantiating this View in an IDE, with no ambient GeckoApplication.
// The theme can be null, particularly if we might be instantiating this
// View in an IDE, with no ambient GeckoApplication.
final Context applicationContext = context.getApplicationContext();
if (applicationContext instanceof GeckoApplication) {
theme = ((GeckoApplication) applicationContext).getLightweightTheme();

View File

@ -49,8 +49,8 @@ public class ThemedFrameLayout extends android.widget.FrameLayout
}
private void initialize(final Context context, final AttributeSet attrs, final int defStyle) {
// The theme can be null, particularly for webapps: Bug 1089266. Or we
// might be instantiating this View in an IDE, with no ambient GeckoApplication.
// The theme can be null, particularly if we might be instantiating this
// View in an IDE, with no ambient GeckoApplication.
final Context applicationContext = context.getApplicationContext();
if (applicationContext instanceof GeckoApplication) {
theme = ((GeckoApplication) applicationContext).getLightweightTheme();

View File

@ -49,8 +49,8 @@ public class ThemedImageButton extends android.widget.ImageButton
}
private void initialize(final Context context, final AttributeSet attrs, final int defStyle) {
// The theme can be null, particularly for webapps: Bug 1089266. Or we
// might be instantiating this View in an IDE, with no ambient GeckoApplication.
// The theme can be null, particularly if we might be instantiating this
// View in an IDE, with no ambient GeckoApplication.
final Context applicationContext = context.getApplicationContext();
if (applicationContext instanceof GeckoApplication) {
theme = ((GeckoApplication) applicationContext).getLightweightTheme();

View File

@ -49,8 +49,8 @@ public class ThemedImageView extends android.widget.ImageView
}
private void initialize(final Context context, final AttributeSet attrs, final int defStyle) {
// The theme can be null, particularly for webapps: Bug 1089266. Or we
// might be instantiating this View in an IDE, with no ambient GeckoApplication.
// The theme can be null, particularly if we might be instantiating this
// View in an IDE, with no ambient GeckoApplication.
final Context applicationContext = context.getApplicationContext();
if (applicationContext instanceof GeckoApplication) {
theme = ((GeckoApplication) applicationContext).getLightweightTheme();

View File

@ -44,8 +44,8 @@ public class ThemedLinearLayout extends android.widget.LinearLayout
}
private void initialize(final Context context, final AttributeSet attrs, final int defStyle) {
// The theme can be null, particularly for webapps: Bug 1089266. Or we
// might be instantiating this View in an IDE, with no ambient GeckoApplication.
// The theme can be null, particularly if we might be instantiating this
// View in an IDE, with no ambient GeckoApplication.
final Context applicationContext = context.getApplicationContext();
if (applicationContext instanceof GeckoApplication) {
theme = ((GeckoApplication) applicationContext).getLightweightTheme();

View File

@ -49,8 +49,8 @@ public class ThemedRelativeLayout extends android.widget.RelativeLayout
}
private void initialize(final Context context, final AttributeSet attrs, final int defStyle) {
// The theme can be null, particularly for webapps: Bug 1089266. Or we
// might be instantiating this View in an IDE, with no ambient GeckoApplication.
// The theme can be null, particularly if we might be instantiating this
// View in an IDE, with no ambient GeckoApplication.
final Context applicationContext = context.getApplicationContext();
if (applicationContext instanceof GeckoApplication) {
theme = ((GeckoApplication) applicationContext).getLightweightTheme();

View File

@ -44,8 +44,8 @@ public class ThemedTextSwitcher extends android.widget.TextSwitcher
}
private void initialize(final Context context, final AttributeSet attrs, final int defStyle) {
// The theme can be null, particularly for webapps: Bug 1089266. Or we
// might be instantiating this View in an IDE, with no ambient GeckoApplication.
// The theme can be null, particularly if we might be instantiating this
// View in an IDE, with no ambient GeckoApplication.
final Context applicationContext = context.getApplicationContext();
if (applicationContext instanceof GeckoApplication) {
theme = ((GeckoApplication) applicationContext).getLightweightTheme();

View File

@ -49,8 +49,8 @@ public class ThemedTextView extends android.widget.TextView
}
private void initialize(final Context context, final AttributeSet attrs, final int defStyle) {
// The theme can be null, particularly for webapps: Bug 1089266. Or we
// might be instantiating this View in an IDE, with no ambient GeckoApplication.
// The theme can be null, particularly if we might be instantiating this
// View in an IDE, with no ambient GeckoApplication.
final Context applicationContext = context.getApplicationContext();
if (applicationContext instanceof GeckoApplication) {
theme = ((GeckoApplication) applicationContext).getLightweightTheme();

View File

@ -49,8 +49,8 @@ public class ThemedView extends android.view.View
}
private void initialize(final Context context, final AttributeSet attrs, final int defStyle) {
// The theme can be null, particularly for webapps: Bug 1089266. Or we
// might be instantiating this View in an IDE, with no ambient GeckoApplication.
// The theme can be null, particularly if we might be instantiating this
// View in an IDE, with no ambient GeckoApplication.
final Context applicationContext = context.getApplicationContext();
if (applicationContext instanceof GeckoApplication) {
theme = ((GeckoApplication) applicationContext).getLightweightTheme();

View File

@ -52,8 +52,8 @@ public class Themed@VIEW_NAME_SUFFIX@ extends @BASE_TYPE@
//#endif
private void initialize(final Context context, final AttributeSet attrs, final int defStyle) {
// The theme can be null, particularly for webapps: Bug 1089266. Or we
// might be instantiating this View in an IDE, with no ambient GeckoApplication.
// The theme can be null, particularly if we might be instantiating this
// View in an IDE, with no ambient GeckoApplication.
final Context applicationContext = context.getApplicationContext();
if (applicationContext instanceof GeckoApplication) {
theme = ((GeckoApplication) applicationContext).getLightweightTheme();

View File

@ -650,8 +650,6 @@ just addresses the organization to follow, e.g. "This site is run by " -->
replaced with the search query. -->
<!ENTITY suggestion_for_engine "Search &formatS1; for &formatS2;">
<!ENTITY webapp_generic_name "App">
<!ENTITY searchable_description "Bookmarks and history">
<!-- Updater notifications -->

View File

@ -580,17 +580,6 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
'updater/UpdateService.java',
'updater/UpdateServiceHelper.java',
'util/Experiments.java',
'Webapp.java',
'webapp/Allocator.java',
'webapp/ApkResources.java',
'webapp/Dispatcher.java',
'webapp/EventListener.java',
'webapp/InstallHelper.java',
'webapp/InstallListener.java',
'webapp/TaskKiller.java',
'webapp/UninstallListener.java',
'webapp/WebappImpl.java',
'webapp/Webapps.java',
'widget/ActivityChooserModel.java',
'widget/AllCapsTextView.java',
'widget/AnchoredPopup.java',

View File

@ -4,7 +4,7 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- This file is used to include shared UI components in different gecko app
layouts, such as gecko_app.xml and web_app.xml -->
layouts, such as gecko_app.xml -->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto">

View File

@ -1,59 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_layout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout android:id="@+id/webapp_titlebar"
android:visibility="gone"
style="@style/WebView.Titlebar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView android:id="@+id/webapp_title"
style="@style/WebView.Titlebar.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"/>
</LinearLayout>
<RelativeLayout android:id="@+id/gecko_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/webapp_titlebar">
<include layout="@layout/shared_ui_components"/>
<RelativeLayout android:id="@+id/splashscreen"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView android:id="@+id/splashscreen_icon"
android:minWidth="128dip"
android:minHeight="128dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"/>
<ProgressBar android:id="@+id/splashscreen_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:paddingBottom="30dip"
android:visibility="gone"/>
</RelativeLayout>
</RelativeLayout>
<ViewStub android:id="@+id/toast_stub"
android:layout="@layout/button_toast"
style="@style/Toast"/>
</RelativeLayout>

View File

@ -523,8 +523,6 @@
<string name="bookmarkhistory_import_history">&bookmarkhistory_import_history;</string>
<string name="bookmarkhistory_import_wait">&bookmarkhistory_import_wait;</string>
<string name="webapp_generic_name">&webapp_generic_name;</string>
<string name="searchable_description">&searchable_description;</string>
<!-- Updater notifications -->

View File

@ -1,218 +0,0 @@
/* 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/. */
/*globals PermissionsInstaller */
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/PermissionsInstaller.jsm");
Cu.import("resource://gre/modules/ContactService.jsm");
Cu.import("resource://gre/modules/AppsUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Notifications", "resource://gre/modules/Notifications.jsm");
function pref(name, value) {
return {
name: name,
value: value
}
}
var WebappRT = {
prefs: [
// Disable all add-on locations other than the profile (which can't be disabled this way)
pref("extensions.enabledScopes", 1),
// Auto-disable any add-ons that are "dropped in" to the profile
pref("extensions.autoDisableScopes", 1),
// Disable add-on installation via the web-exposed APIs
pref("xpinstall.enabled", false),
pref("media.useAudioChannelAPI", true),
pref("dom.mozTCPSocket.enabled", true),
// Enabled system messages for web activity support
pref("dom.sysmsg.enabled", true),
],
init: function(aStatus, aUrl, aCallback) {
this.deck = document.getElementById("browsers");
this.deck.addEventListener("click", this, false, true);
// on first run, update any prefs
if (aStatus == "new") {
this.prefs.forEach(this.addPref);
// update the blocklist url to use a different app id
let blocklist = Services.prefs.getCharPref("extensions.blocklist.url");
blocklist = blocklist.replace(/%APP_ID%/g, "webapprt-mobile@mozilla.org");
Services.prefs.setCharPref("extensions.blocklist.url", blocklist);
}
// On firstrun, set permissions to their default values.
// When the webapp runtime is updated, update the permissions.
if (aStatus == "new" || aStatus == "upgrade") {
this.getManifestFor(aUrl, function (aManifest, aApp) {
if (aManifest) {
PermissionsInstaller.installPermissions(aApp, true);
}
});
}
// If the app is in debug mode, configure and enable the remote debugger.
Messaging.sendRequestForResult({ type: "NativeApp:IsDebuggable" }).then((response) => {
let that = this;
let name = this._getAppName(aUrl);
if (response.isDebuggable) {
Notifications.create({
title: Strings.browser.formatStringFromName("remoteStartNotificationTitle", [name], 1),
message: Strings.browser.GetStringFromName("remoteStartNotificationMessage"),
icon: "drawable://warning_doorhanger",
onClick: function(aId, aCookie) {
that._enableRemoteDebugger(aUrl);
},
});
}
});
this.findManifestUrlFor(aUrl, (function(aLaunchUrl) {
if (aStatus == "new") {
if (BrowserApp.manifest && BrowserApp.manifest.orientation) {
let orientation = BrowserApp.manifest.orientation;
if (Array.isArray(orientation)) {
orientation = orientation.join(",");
}
this.addPref(pref("app.orientation.default", orientation));
}
}
aCallback(aLaunchUrl);
}).bind(this));
},
getManifestFor: function (aUrl, aCallback) {
let request = navigator.mozApps.mgmt.getAll();
request.onsuccess = function() {
let apps = request.result;
for (let i = 0; i < apps.length; i++) {
let app = apps[i];
let manifest = new ManifestHelper(app.manifest, app.origin, app.manifestURL);
// if this is a path to the manifest, or the launch path, then we have a hit.
if (app.manifestURL == aUrl || manifest.fullLaunchPath() == aUrl) {
aCallback(manifest, app);
return;
}
}
// Otherwise, once we loop through all of them, we have a miss.
aCallback(undefined);
};
request.onerror = function() {
// Treat an error like a miss. We can't find the manifest.
aCallback(undefined);
};
},
findManifestUrlFor: function(aUrl, aCallback) {
this.getManifestFor(aUrl, function(aManifest, aApp) {
if (!aManifest) {
// we can't find the manifest, so open it like a web page
aCallback(aUrl);
return;
}
BrowserApp.manifest = aManifest;
BrowserApp.manifestUrl = aApp.manifestURL;
aCallback(aManifest.fullLaunchPath());
});
},
addPref: function(aPref) {
switch (typeof aPref.value) {
case "string":
Services.prefs.setCharPref(aPref.name, aPref.value);
break;
case "boolean":
Services.prefs.setBoolPref(aPref.name, aPref.value);
break;
case "number":
Services.prefs.setIntPref(aPref.name, aPref.value);
break;
}
},
_getAppName: function(aUrl) {
let name = Strings.browser.GetStringFromName("remoteNotificationGenericName");
let app = DOMApplicationRegistry.getAppByManifestURL(aUrl);
if (app) {
name = app.name;
}
return name;
},
_enableRemoteDebugger: function(aUrl) {
// Skip the connection prompt in favor of notifying the user below.
Services.prefs.setBoolPref("devtools.debugger.prompt-connection", false);
// Automagically find a free port and configure the debugger to use it.
let serv = Cc['@mozilla.org/network/server-socket;1'].createInstance(Ci.nsIServerSocket);
serv.init(-1, true, -1);
let port = serv.port;
serv.close();
Services.prefs.setIntPref("devtools.debugger.remote-port", port);
// Clear the UNIX domain socket path to ensure a TCP socket will be used
// instead.
Services.prefs.setCharPref("devtools.debugger.unix-domain-socket", "");
Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
// Notify the user that we enabled the debugger and which port it's using
// so they can use the DevTools Connect… dialog to connect the client to it.
DOMApplicationRegistry.registryReady.then(() => {
let name = this._getAppName(aUrl);
Notifications.create({
title: Strings.browser.formatStringFromName("remoteNotificationTitle", [name], 1),
message: Strings.browser.formatStringFromName("remoteNotificationMessage", [port], 1),
icon: "drawable://warning_doorhanger",
});
});
},
handleEvent: function(event) {
let target = event.target;
// walk up the tree to find the nearest link tag
while (target && !(target instanceof HTMLAnchorElement)) {
target = target.parentNode;
}
if (!target || target.getAttribute("target") != "_blank") {
return;
}
let uri = Services.io.newURI(target.href, target.ownerDocument.characterSet, null);
// Direct the URL to the browser.
Cc["@mozilla.org/uriloader/external-protocol-service;1"].
getService(Ci.nsIExternalProtocolService).
getProtocolHandlerInfo(uri.scheme).
launchWithURI(uri);
// Prevent the runtime from loading the URL. We do this after directing it
// to the browser to give the runtime a shot at handling the URL if we fail
// to direct it to the browser for some reason.
event.preventDefault();
}
}

View File

@ -93,9 +93,6 @@ XPCOMUtils.defineLazyServiceGetter(this, "Profiler",
XPCOMUtils.defineLazyModuleGetter(this, "SimpleServiceDiscovery",
"resource://gre/modules/SimpleServiceDiscovery.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebappManager",
"resource://gre/modules/WebappManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
"resource://gre/modules/CharsetMenu.jsm");
@ -443,15 +440,6 @@ var BrowserApp = {
Services.obs.addObserver(this, "android-set-pref", false);
Services.obs.addObserver(this, "gather-telemetry", false);
Services.obs.addObserver(this, "keyword-search", false);
Services.obs.addObserver(this, "webapps-runtime-install", false);
Services.obs.addObserver(this, "webapps-runtime-install-package", false);
Services.obs.addObserver(this, "webapps-ask-install", false);
Services.obs.addObserver(this, "webapps-ask-uninstall", false);
Services.obs.addObserver(this, "webapps-launch", false);
Services.obs.addObserver(this, "webapps-runtime-uninstall", false);
Services.obs.addObserver(this, "Webapps:AutoInstall", false);
Services.obs.addObserver(this, "Webapps:Load", false);
Services.obs.addObserver(this, "Webapps:AutoUninstall", false);
Services.obs.addObserver(this, "sessionstore-state-purge-complete", false);
Services.obs.addObserver(this, "Fonts:Reload", false);
@ -652,13 +640,6 @@ var BrowserApp = {
Messaging.sendRequest({ type: "Locale:Set", locale: locale });
},
_initRuntime: function(status, url, callback) {
let sandbox = {};
Services.scriptloader.loadSubScript("chrome://browser/content/WebappRT.js", sandbox);
window.WebappRT = sandbox.WebappRT;
WebappRT.init(status, url, callback);
},
initContextMenu: function () {
// We pass a thunk in place of a raw label string. This allows the
// context menu to automatically accommodate locale changes without
@ -1283,15 +1264,6 @@ var BrowserApp = {
Messaging.sendRequest(message);
},
_loadWebapp: function(aMessage) {
// Entry point for WebApps. This is the point in which we know
// the code is being used as a WebApp runtime.
this._initRuntime(this._startupStatus, aMessage.url, aUrl => {
this.manifestUrl = aMessage.url;
this.addTab(aUrl, { title: aMessage.name });
});
},
// Calling this will update the state in BrowserApp after a tab has been
// closed in the Java UI.
_handleTabClosed: function _handleTabClosed(aTab, aShowUndoSnackbar) {
@ -1938,44 +1910,6 @@ var BrowserApp = {
Messaging.sendRequest({ type: "Telemetry:Gather" });
break;
case "webapps-runtime-install":
WebappManager.install(JSON.parse(aData), aSubject);
break;
case "webapps-runtime-install-package":
WebappManager.installPackage(JSON.parse(aData), aSubject);
break;
case "webapps-ask-install":
WebappManager.askInstall(JSON.parse(aData));
break;
case "webapps-ask-uninstall":
WebappManager.askUninstall(JSON.parse(aData));
break;
case "webapps-launch": {
WebappManager.launch(JSON.parse(aData));
break;
}
case "webapps-runtime-uninstall": {
WebappManager.uninstall(JSON.parse(aData), aSubject);
break;
}
case "Webapps:AutoInstall":
WebappManager.autoInstall(JSON.parse(aData));
break;
case "Webapps:Load":
this._loadWebapp(JSON.parse(aData));
break;
case "Webapps:AutoUninstall":
WebappManager.autoUninstall(JSON.parse(aData));
break;
case "Locale:OS":
// We know the system locale. We use this for generating Accept-Language headers.
console.log("Locale:OS: " + aData);

View File

@ -34,7 +34,6 @@ chrome.jar:
content/SelectHelper.js (content/SelectHelper.js)
content/SelectionHandler.js (content/SelectionHandler.js)
content/ActionBarHandler.js (content/ActionBarHandler.js)
content/WebappRT.js (content/WebappRT.js)
content/EmbedRT.js (content/EmbedRT.js)
content/InputWidgetHelper.js (content/InputWidgetHelper.js)
content/WebrtcUI.js (content/WebrtcUI.js)

View File

@ -41,11 +41,6 @@ category app-startup SessionStore service,@mozilla.org/browser/sessionstore;1
component {C6E8C44D-9F39-4AF7-BCC0-76E38A8310F5} ContentPermissionPrompt.js
contract @mozilla.org/content-permission/prompt;1 {C6E8C44D-9F39-4AF7-BCC0-76E38A8310F5}
# WebappsUpdateTimer.js
component {8f7002cb-e959-4f0a-a2e8-563232564385} WebappsUpdateTimer.js
contract @mozilla.org/webapps-update-timer;1 {8f7002cb-e959-4f0a-a2e8-563232564385}
category update-timer WebappsUpdateTimer @mozilla.org/webapps-update-timer;1,getService,webapp-background-update-timer,browser.webapps.updateInterval,86400
# PromptService.js
component {9a61149b-2276-4a0a-b79c-be994ad106cf} PromptService.js
contract @mozilla.org/prompter;1 {9a61149b-2276-4a0a-b79c-be994ad106cf}

View File

@ -1,78 +0,0 @@
/* 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/. */
/**
* This component triggers a periodic webapp update check.
*/
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/JNI.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/WebappManager.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const Log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.bind("WebappsUpdateTimer");
function isWebAppProcess() {
let jenv = null;
try {
jenv = JNI.GetForThread();
let GeckoAppShell = JNI.LoadClass(jenv, "org.mozilla.gecko.GeckoAppShell", {
static_methods: [
{ name: "isWebAppProcess", sig: "()Z" }
],
});
return GeckoAppShell.isWebAppProcess();
} finally {
if (jenv) {
JNI.UnloadClasses(jenv);
}
}
}
function WebappsUpdateTimer() {}
WebappsUpdateTimer.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback,
Ci.nsISupportsWeakReference]),
classID: Components.ID("{8f7002cb-e959-4f0a-a2e8-563232564385}"),
notify: function(aTimer) {
// Ignore the timer if automatic update checks are disabled or if this
// is a webapp process (since we only want to bug people about updates
// from the browser process).
if (Services.prefs.getIntPref("browser.webapps.checkForUpdates") == 0 || !isWebAppProcess()) {
return;
}
// If we are offline, wait to be online to start the update check.
if (Services.io.offline) {
Log.i("network offline for webapp update check; waiting");
Services.obs.addObserver(this, "network:offline-status-changed", true);
return;
}
Log.i("periodic check for webapp updates");
WebappManager.checkForUpdates();
},
observe: function(aSubject, aTopic, aData) {
if (aTopic !== "network:offline-status-changed" || aData !== "online") {
return;
}
Log.i("network back online for webapp update check; commencing");
Services.obs.removeObserver(this, "network:offline-status-changed");
WebappManager.checkForUpdates();
}
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WebappsUpdateTimer]);

View File

@ -35,7 +35,6 @@ if not CONFIG['MOZ_B2GDROID']:
'PromptService.js',
'SessionStore.js',
'SiteSpecificUserAgent.js',
'WebappsUpdateTimer.js',
]
# Keep it this way if at all possible. If you need preprocessing,

View File

@ -557,7 +557,6 @@
@BINPATH@/components/marionettecomponent.js
#endif
@BINPATH@/components/WebappsUpdateTimer.js
@BINPATH@/components/DataStore.manifest
@BINPATH@/components/DataStoreImpl.js
@BINPATH@/components/dom_datastore.xpt

View File

@ -1,59 +0,0 @@
# 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/.
checkingForUpdatesTitle=Checking for updates…
checkingForUpdatesMessage=Checking for updates to your apps
noUpdatesTitle=No updates available
noUpdatesMessage=There are no updates to your apps
# LOCALIZATION NOTE (retrieveUpdateTitle): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 is the number of updates.
# example: 3 new updates available
retrieveUpdateTitle=#1 new update available;#1 new updates available
# LOCALIZATION NOTE (retrieveUpdateMessage): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# %1$S is a comma-separated list of apps for which to retrieve an update.
# example: Touch to retrieve updates for Foo, Bar, Baz
retrieveUpdateMessage=Touch to retrieve update for %1$S;Touch to retrieve updates for %1$S
# LOCALIZATION NOTE (retrievingUpdateTitle): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 is the number of updates.
# example: Retrieving 3 updates…
retrievingUpdateTitle=Retrieving #1 update…;Retrieving #1 updates…
# LOCALIZATION NOTE (retrievingUpdateMessage): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# %1$S is a comma-separated list of apps for which we're retrieving updates.
# example: Retrieving updates for Foo, Bar, Baz
retrievingUpdateMessage=Retrieving update for %1$S; Retrieving updates for %1$S
# LOCALIZATION NOTE (installUpdateTitle): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 is the number of updates.
# example: 3 updates downloaded
installUpdateTitle=#1 update downloaded;#1 updates downloaded
# LOCALIZATION NOTE (installUpdateMessage2): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# %1$S is a comma-separated list of apps for which to install an update.
# example: Touch to install updates for Foo, Bar, Baz
installUpdateMessage2=Touch to install update for %1$S;Touch to install updates for %1$S
# LOCALIZATION NOTE (retrievalFailedTitle): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 is the number of updates.
# example: 3 updates failed
retrievalFailedTitle=#1 update failed;#1 updates failed
# LOCALIZATION NOTE (retrievalFailedMessage): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# %1$S is a comma-separated list of apps for which retrieval failed.
# example: Failed to retrieve updates for Foo, Bar, Baz
retrievalFailedMessage=Failed to retrieve update for %1$S;Failed to retrieve updates for %1$S
webappsDisabled=Installing apps is disabled

View File

@ -37,7 +37,6 @@
locale/@AB_CD@/browser/feedback.dtd (%chrome/feedback.dtd)
locale/@AB_CD@/browser/phishing.dtd (%chrome/phishing.dtd)
locale/@AB_CD@/browser/handling.properties (%chrome/handling.properties)
locale/@AB_CD@/browser/webapp.properties (%chrome/webapp.properties)
locale/@AB_CD@/browser/aboutLogins.dtd (%chrome/aboutLogins.dtd)
locale/@AB_CD@/browser/aboutLogins.properties (%chrome/aboutLogins.properties)
#ifdef NIGHTLY_BUILD

View File

@ -1,659 +0,0 @@
/* 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/. */
"use strict";
this.EXPORTED_SYMBOLS = ["WebappManager"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
const UPDATE_URL_PREF = "browser.webapps.updateCheckUrl";
Cu.import("resource://gre/modules/AppsUtils.jsm");
Cu.import("resource://gre/modules/Downloads.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
Cu.import("resource://gre/modules/Webapps.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Notifications", "resource://gre/modules/Notifications.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Messaging", "resource://gre/modules/Messaging.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm");
// Import AppsServiceChild.DOMApplicationRegistry for its getAll method.
var AppsServiceChild = {};
XPCOMUtils.defineLazyModuleGetter(AppsServiceChild, "DOMApplicationRegistry",
"resource://gre/modules/AppsServiceChild.jsm");
XPCOMUtils.defineLazyGetter(this, "Strings", function() {
return Services.strings.createBundle("chrome://browser/locale/webapp.properties");
});
XPCOMUtils.defineLazyServiceGetter(this, "ParentalControls",
"@mozilla.org/parental-controls-service;1", "nsIParentalControlsService");
/**
* Get the formatted plural form of a string. Escapes semicolons in arguments
* to provide to the formatter before formatting the string, then unescapes them
* after getting its plural form, to avoid tripping up the plural form getter
* with a semicolon in one of the formatter's arguments, since the plural forms
* of localized strings are delimited by semicolons.
*
* Ideally, we'd get the plural form first and then format the string,
* so we wouldn't have to escape/unescape the semicolons; but that would require
* changes to nsIStringBundle and PluralForm.jsm.
*
* @param stringName {String} the string to get the formatted plural form of
* @param formatterArgs {Array} of {String} args to provide to the formatter
* @param pluralNum {Number} the number that determines the plural form
* @returns {String} the formatted plural form of the string
*/
function getFormattedPluralForm(stringName, formatterArgs, pluralNum) {
// Escape semicolons by replacing them with ESC characters.
let escapedArgs = formatterArgs.map((arg) => arg.replace(/;/g, String.fromCharCode(0x1B)));
let formattedString = Strings.formatStringFromName(stringName, escapedArgs, escapedArgs.length);
let pluralForm = PluralForm.get(pluralNum, formattedString);
let unescapedString = pluralForm.replace(String.fromCharCode(0x1B), ";", "g");
return unescapedString;
}
var Log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog;
var debug = Log.d.bind(null, "WebappManager");
this.WebappManager = {
__proto__: DOMRequestIpcHelper.prototype,
get _testing() {
try {
return Services.prefs.getBoolPref("browser.webapps.testing");
} catch(ex) {
return false;
}
},
install: function(aMessage, aMessageManager) {
if (this._testing) {
// Go directly to DOM. Do not download/install APK, do not collect $200.
DOMApplicationRegistry.doInstall(aMessage, aMessageManager);
return;
}
this._installApk(aMessage, aMessageManager);
},
installPackage: function(aMessage, aMessageManager) {
if (this._testing) {
// Go directly to DOM. Do not download/install APK, do not collect $200.
DOMApplicationRegistry.doInstallPackage(aMessage, aMessageManager);
return;
}
this._installApk(aMessage, aMessageManager);
},
_installApk: function(aMessage, aMessageManager) { return Task.spawn((function*() {
if (!ParentalControls.isAllowed(ParentalControls.INSTALL_APPS)) {
aMessage.error = Strings.GetStringFromName("webappsDisabled"),
aMessageManager.sendAsyncMessage("Webapps:Install:Return:KO", aMessage);
return;
}
let filePath;
try {
filePath = yield this._downloadApk(aMessage.app.manifestURL);
} catch(ex) {
aMessage.error = ex;
aMessageManager.sendAsyncMessage("Webapps:Install:Return:KO", aMessage);
debug("error downloading APK: " + ex);
return;
}
Messaging.sendRequestForResult({
type: "Webapps:InstallApk",
filePath: filePath,
data: aMessage,
}).catch(function (error) {
aMessage.error = error;
aMessageManager.sendAsyncMessage("Webapps:Install:Return:KO", aMessage);
debug("error downloading APK: " + error);
});
}).bind(this)); },
_downloadApk: function(aManifestUrl) {
debug("_downloadApk for " + aManifestUrl);
let deferred = Promise.defer();
// Get the endpoint URL and convert it to an nsIURI/nsIURL object.
const GENERATOR_URL_PREF = "browser.webapps.apkFactoryUrl";
const GENERATOR_URL_BASE = Services.prefs.getCharPref(GENERATOR_URL_PREF);
let generatorUrl = NetUtil.newURI(GENERATOR_URL_BASE).QueryInterface(Ci.nsIURL);
// Populate the query part of the URL with the manifest URL parameter.
let params = {
manifestUrl: aManifestUrl,
};
generatorUrl.query =
Object.keys(params).map((p) => p + "=" + encodeURIComponent(params[p])).join("&");
debug("downloading APK from " + generatorUrl.spec);
Downloads.getSystemDownloadsDirectory().then(function(downloadsDir) {
let file = new FileUtils.File(downloadsDir);
file.append(aManifestUrl.replace(/[^a-zA-Z0-9]/gi, "") + ".apk");
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
debug("downloading APK to " + file.path);
let worker = new ChromeWorker("resource://gre/modules/WebappManagerWorker.js");
worker.onmessage = function(event) {
let { type, message } = event.data;
worker.terminate();
if (type == "success") {
deferred.resolve(file.path);
} else { // type == "failure"
debug("error downloading APK: " + message);
deferred.reject(message);
}
}
// Trigger the download.
worker.postMessage({ url: generatorUrl.spec, path: file.path });
});
return deferred.promise;
},
_deleteAppcachePath: function(aManifest) {
// We don't yet support pre-installing an appcache because it isn't clear
// how to do it without degrading the user experience (since users expect
// apps to be available after the system tells them they've been installed,
// which has already happened) and because nsCacheService shuts down
// when we trigger the native install dialog and doesn't re-init itself
// afterward (TODO: file bug about this behavior).
if ("appcache_path" in aManifest) {
debug("deleting appcache_path from manifest: " + aManifest.appcache_path);
delete aManifest.appcache_path;
}
},
askInstall: function(aData) {
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
file.initWithPath(aData.profilePath);
this._deleteAppcachePath(aData.app.manifest);
DOMApplicationRegistry.registryReady.then(() => {
DOMApplicationRegistry.confirmInstall(aData, file, (function(aApp, aManifest) {
this._postInstall(aData.profilePath, aManifest, aData.app.origin,
aData.app.apkPackageName, aData.app.manifestURL);
}).bind(this));
});
},
_postInstall: function(aProfilePath, aNewManifest, aOrigin, aApkPackageName, aManifestURL) {
// aOrigin may now point to the app: url that hosts this app.
Messaging.sendRequest({
type: "Webapps:Postinstall",
apkPackageName: aApkPackageName,
origin: aOrigin,
});
},
askUninstall: function(aData) {
// Android does not currently support automatic uninstalling of apps.
// See bug 1019054.
DOMApplicationRegistry.denyUninstall(aData, "NOT_SUPPORTED");
},
launch: function({ apkPackageName }) {
debug("launch: " + apkPackageName);
Messaging.sendRequest({
type: "Webapps:Launch",
packageName: apkPackageName,
});
},
uninstall: Task.async(function*(aData, aMessageManager) {
debug("uninstall: " + aData.manifestURL);
yield DOMApplicationRegistry.registryReady;
if (this._testing) {
// Go directly to DOM. Do not uninstall APK, do not collect $200.
DOMApplicationRegistry.doUninstall(aData, aMessageManager);
return;
}
let app = DOMApplicationRegistry.getAppByManifestURL(aData.manifestURL);
if (!app) {
throw new Error("app not found in registry");
}
// If the APK is installed, then _getAPKVersions will return a version
// for it, so we can use that function to determine its install status.
if (app.apkPackageName && app.apkPackageName in (yield this._getAPKVersions([ app.apkPackageName ]))) {
debug("APK is installed; requesting uninstallation");
Messaging.sendRequest({
type: "Webapps:UninstallApk",
apkPackageName: app.apkPackageName,
});
// We don't need to call DOMApplicationRegistry.doUninstall at this point,
// because the APK uninstall listener will call autoUninstall once the APK
// is uninstalled; and if the user cancels the APK uninstallation, then we
// shouldn't remove the app from the registry anyway.
// But we should tell the requesting document the result of their request.
// TODO: tell the requesting document if uninstallation succeeds or fails
// by storing weak references to the message/manager pair here and then
// using them in autoUninstall if they're still defined when it's called;
// and make EventListener.uninstallApk return an error when APK uninstall
// fails (which it should be able to detect reliably on Android 4+),
// which we observe here and use to notify the requester of failure.
} else {
// The APK isn't installed, but remove the app from the registry anyway,
// to ensure the user can always remove an app from the registry (and thus
// about:apps) even if it's out of sync with installed APKs.
debug("APK not installed; proceeding directly to removal from registry");
DOMApplicationRegistry.uninstall(aData.manifestURL);
}
}),
autoInstall: function(aData) {
debug("autoInstall " + aData.manifestURL);
let mm = {
sendAsyncMessage: function (aMessageName, aData) {
// TODO hook this back to Java to report errors.
debug("sendAsyncMessage " + aMessageName + ": " + JSON.stringify(aData));
}
};
let origin = Services.io.newURI(aData.manifestURL, null, null).prePath;
let message = aData.request || {
app: {
origin: origin,
receipts: [],
}
};
if (aData.updateManifest) {
if (aData.zipFilePath) {
aData.updateManifest.package_path = aData.zipFilePath;
}
message.app.updateManifest = aData.updateManifest;
}
// The manifest url may be subtly different between the
// time the APK was built and the APK being installed.
// Thus, we should take the APK as the source of truth.
message.app.manifestURL = aData.manifestURL;
message.app.manifest = aData.manifest;
message.app.apkPackageName = aData.apkPackageName;
message.profilePath = aData.profilePath;
message.mm = mm;
message.apkInstall = true;
DOMApplicationRegistry.registryReady.then(() => {
// If the app is already installed, update the existing installation.
// We should be able to use DOMApplicationRegistry.getAppByManifestURL,
// but it returns a mozIApplication, while _autoUpdate needs the original
// object from DOMApplicationRegistry.webapps in order to modify it.
for (let [ , app] in Iterator(DOMApplicationRegistry.webapps)) {
if (app.manifestURL == aData.manifestURL) {
return this._autoUpdate(aData, app);
}
}
switch (aData.type) { // can be hosted or packaged.
case "hosted":
DOMApplicationRegistry.doInstall(message, mm);
break;
case "packaged":
message.isPackage = true;
DOMApplicationRegistry.doInstallPackage(message, mm);
break;
}
});
},
_autoUpdate: function(aData, aOldApp) { return Task.spawn((function*() {
debug("_autoUpdate app of type " + aData.type);
if (aOldApp.apkPackageName != aData.apkPackageName) {
// This happens when the app was installed as a shortcut via the old
// runtime and is now being updated to an APK.
debug("update apkPackageName from " + aOldApp.apkPackageName + " to " + aData.apkPackageName);
aOldApp.apkPackageName = aData.apkPackageName;
}
if (aData.type == "hosted") {
this._deleteAppcachePath(aData.manifest);
let oldManifest = yield DOMApplicationRegistry.getManifestFor(aData.manifestURL);
yield DOMApplicationRegistry.updateHostedApp(aData, aOldApp.id, aOldApp, oldManifest, aData.manifest);
} else {
yield this._autoUpdatePackagedApp(aData, aOldApp);
}
this._postInstall(aData.profilePath, aData.manifest, aOldApp.origin, aOldApp.apkPackageName, aOldApp.manifestURL);
}).bind(this)); },
_autoUpdatePackagedApp: Task.async(function*(aData, aOldApp) {
debug("_autoUpdatePackagedApp: " + aData.manifestURL);
if (aData.updateManifest && aData.zipFilePath) {
aData.updateManifest.package_path = aData.zipFilePath;
}
// updatePackagedApp just prepares the update, after which we must
// download the package via the misnamed startDownload and then apply it
// via applyDownload.
yield DOMApplicationRegistry.updatePackagedApp(aData, aOldApp.id, aOldApp, aData.updateManifest);
try {
yield DOMApplicationRegistry.startDownload(aData.manifestURL);
} catch (ex if ex == "PACKAGE_UNCHANGED") {
debug("package unchanged");
// If the package is unchanged, then there's nothing more to do.
return;
}
yield DOMApplicationRegistry.applyDownload(aData.manifestURL);
}),
_checkingForUpdates: false,
checkForUpdates: function(userInitiated) { return Task.spawn((function*() {
debug("checkForUpdates");
// Don't start checking for updates if we're already doing so.
// TODO: Consider cancelling the old one and starting a new one anyway
// if the user requested this one.
if (this._checkingForUpdates) {
debug("already checking for updates");
return;
}
this._checkingForUpdates = true;
try {
let installedApps = yield this._getInstalledApps();
if (installedApps.length === 0) {
return;
}
// Map APK names to APK versions.
let apkNameToVersion = yield this._getAPKVersions(installedApps.map(app =>
app.apkPackageName).filter(apkPackageName => !!apkPackageName)
);
// Map manifest URLs to APK versions, which is what the service needs
// in order to tell us which apps are outdated; and also map them to app
// objects, which the downloader/installer uses to download/install APKs.
// XXX Will this cause us to update apps without packages, and if so,
// does that satisfy the legacy migration story?
let manifestUrlToApkVersion = {};
let manifestUrlToApp = {};
for (let app of installedApps) {
manifestUrlToApkVersion[app.manifestURL] = apkNameToVersion[app.apkPackageName] || 0;
manifestUrlToApp[app.manifestURL] = app;
}
let outdatedApps = yield this._getOutdatedApps(manifestUrlToApkVersion, userInitiated);
if (outdatedApps.length === 0) {
// If the user asked us to check for updates, tell 'em we came up empty.
if (userInitiated) {
this._notify({
title: Strings.GetStringFromName("noUpdatesTitle"),
message: Strings.GetStringFromName("noUpdatesMessage"),
icon: "drawable://alert_app",
});
}
return;
}
let usingLan = function() {
let network = Cc["@mozilla.org/network/network-link-service;1"].getService(Ci.nsINetworkLinkService);
return (network.linkType == network.LINK_TYPE_WIFI || network.linkType == network.LINK_TYPE_ETHERNET);
};
let updateAllowed = function() {
let autoUpdatePref = Services.prefs.getCharPref("app.update.autodownload");
return (autoUpdatePref == "enabled") || (autoUpdatePref == "wifi" && usingLan());
};
if (updateAllowed()) {
yield this._updateApks(outdatedApps.map((url) => manifestUrlToApp[url]));
} else {
let names = outdatedApps.map((url) => manifestUrlToApp[url].name).join(", ");
let accepted = yield this._notify({
title: PluralForm.get(outdatedApps.length, Strings.GetStringFromName("retrieveUpdateTitle")).
replace("#1", outdatedApps.length),
message: getFormattedPluralForm("retrieveUpdateMessage", [names], outdatedApps.length),
icon: "drawable://alert_app",
}).dismissed;
if (accepted) {
yield this._updateApks(outdatedApps.map((url) => manifestUrlToApp[url]));
}
}
}
// There isn't a catch block because we want the error to propagate through
// the promise chain, so callers can receive it and choose to respond to it.
finally {
// Ensure we update the _checkingForUpdates flag even if there's an error;
// otherwise the process will get stuck and never check for updates again.
this._checkingForUpdates = false;
}
}).bind(this)); },
_getAPKVersions: function(packageNames) {
return Messaging.sendRequestForResult({
type: "Webapps:GetApkVersions",
packageNames: packageNames
}).then(data => data.versions);
},
_getInstalledApps: function() {
let deferred = Promise.defer();
AppsServiceChild.DOMApplicationRegistry.getAll(apps => deferred.resolve(apps));
return deferred.promise;
},
_getOutdatedApps: function(installedApps, userInitiated) {
let deferred = Promise.defer();
let data = JSON.stringify({ installed: installedApps });
let notification;
if (userInitiated) {
notification = this._notify({
title: Strings.GetStringFromName("checkingForUpdatesTitle"),
message: Strings.GetStringFromName("checkingForUpdatesMessage"),
icon: "drawable://alert_app_animation",
progress: NaN,
});
}
let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
createInstance(Ci.nsIXMLHttpRequest).
QueryInterface(Ci.nsIXMLHttpRequestEventTarget);
request.mozBackgroundRequest = true;
request.open("POST", Services.prefs.getCharPref(UPDATE_URL_PREF), true);
request.channel.loadFlags = Ci.nsIChannel.LOAD_ANONYMOUS |
Ci.nsIChannel.LOAD_BYPASS_CACHE |
Ci.nsIChannel.INHIBIT_CACHING;
request.onload = function() {
if (userInitiated) {
notification.cancel();
}
deferred.resolve(JSON.parse(this.response).outdated);
};
request.onerror = function() {
if (userInitiated) {
notification.cancel();
}
deferred.reject(this.status || this.statusText);
};
request.setRequestHeader("Content-Type", "application/json");
request.setRequestHeader("Content-Length", data.length);
request.send(data);
return deferred.promise;
},
_updateApks: function(aApps) { return Task.spawn((function*() {
// Notify the user that we're in the progress of downloading updates.
let downloadingNames = aApps.map((app) => app.name).join(", ");
let notification = this._notify({
title: PluralForm.get(aApps.length, Strings.GetStringFromName("retrievingUpdateTitle")).
replace("#1", aApps.length),
message: getFormattedPluralForm("retrievingUpdateMessage", [downloadingNames], aApps.length),
icon: "drawable://alert_download_animation",
// TODO: make this a determinate progress indicator once we can determine
// the sizes of the APKs and observe their progress.
progress: NaN,
});
// Download the APKs for the given apps. We do this serially to avoid
// saturating the user's network connection.
// TODO: download APKs in parallel (or at least more than one at a time)
// if it seems reasonable.
let downloadedApks = [];
let downloadFailedApps = [];
for (let app of aApps) {
try {
let filePath = yield this._downloadApk(app.manifestURL);
downloadedApks.push({ app: app, filePath: filePath });
} catch(ex) {
downloadFailedApps.push(app);
}
}
notification.cancel();
// Notify the user if any downloads failed, but don't do anything
// when the user accepts/cancels the notification.
// In the future, we might prompt the user to retry the download.
if (downloadFailedApps.length > 0) {
let downloadFailedNames = downloadFailedApps.map((app) => app.name).join(", ");
this._notify({
title: PluralForm.get(downloadFailedApps.length, Strings.GetStringFromName("retrievalFailedTitle")).
replace("#1", downloadFailedApps.length),
message: getFormattedPluralForm("retrievalFailedMessage", [downloadFailedNames], downloadFailedApps.length),
icon: "drawable://alert_app",
});
}
// If we weren't able to download any APKs, then there's nothing more to do.
if (downloadedApks.length === 0) {
return;
}
// Prompt the user to update the apps for which we downloaded APKs, and wait
// until they accept/cancel the notification.
let downloadedNames = downloadedApks.map((apk) => apk.app.name).join(", ");
let accepted = yield this._notify({
title: PluralForm.get(downloadedApks.length, Strings.GetStringFromName("installUpdateTitle")).
replace("#1", downloadedApks.length),
message: getFormattedPluralForm("installUpdateMessage2", [downloadedNames], downloadedApks.length),
icon: "drawable://alert_app",
}).dismissed;
if (accepted) {
// The user accepted the notification, so install the downloaded APKs.
for (let apk of downloadedApks) {
let msg = {
app: apk.app,
// TODO: figure out why Webapps:InstallApk needs the "from" property.
from: apk.app.installOrigin,
};
Messaging.sendRequestForResult({
type: "Webapps:InstallApk",
filePath: apk.filePath,
data: msg,
}).catch((error) => {
// There's no page to report back to so drop the error.
// TODO: we should notify the user about this failure.
debug("APK install failed : " + error);
});
}
} else {
// The user cancelled the notification, so remove the downloaded APKs.
for (let apk of downloadedApks) {
try {
yield OS.file.remove(apk.filePath);
} catch(ex) {
debug("error removing " + apk.filePath + " for cancelled update: " + ex);
}
}
}
}).bind(this)); },
_notify: function(aOptions) {
dump("_notify: " + aOptions.title);
// Resolves to true if the notification is "clicked" (i.e. touched)
// and false if the notification is "cancelled" by swiping it away.
let dismissed = Promise.defer();
// TODO: make notifications expandable so users can expand them to read text
// that gets cut off in standard notifications.
let id = Notifications.create({
title: aOptions.title,
message: aOptions.message,
icon: aOptions.icon,
progress: aOptions.progress,
onClick: function(aId, aCookie) {
dismissed.resolve(true);
},
onCancel: function(aId, aCookie) {
dismissed.resolve(false);
},
});
// Return an object with a promise that resolves when the notification
// is dismissed by the user along with a method for cancelling it,
// so callers who want to wait for user action can do so, while those
// who want to control the notification's lifecycle can do that instead.
return {
dismissed: dismissed.promise,
cancel: function() {
Notifications.cancel(id);
},
};
},
autoUninstall: function(aData) {
DOMApplicationRegistry.registryReady.then(() => {
for (let id in DOMApplicationRegistry.webapps) {
let app = DOMApplicationRegistry.webapps[id];
if (aData.apkPackageNames.indexOf(app.apkPackageName) > -1) {
debug("attempting to uninstall " + app.name);
DOMApplicationRegistry.uninstall(app.manifestURL).then(
function() {
debug("success uninstalling " + app.name);
},
function(error) {
debug("error uninstalling " + app.name + ": " + error);
}
);
}
}
});
},
};

View File

@ -1,52 +0,0 @@
/* 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/. */
importScripts("resource://gre/modules/osfile.jsm");
importScripts("resource://gre/modules/workers/require.js");
var Log = require("resource://gre/modules/AndroidLog.jsm");
// Define the "log" function as a binding of the Log.d function so it specifies
// the "debug" priority and a log tag.
var log = Log.d.bind(null, "WebappManagerWorker");
// (eslint-disable: see bug 1177901)
onmessage = function(event) { // eslint-disable-line no-undef
let { url, path } = event.data;
let file = OS.File.open(path, { truncate: true });
let request = new XMLHttpRequest({ mozSystem: true });
request.open("GET", url, true);
request.responseType = "moz-chunked-arraybuffer";
request.onprogress = function(event) {
log("onprogress: received " + request.response.byteLength + " bytes");
let bytesWritten = file.write(new Uint8Array(request.response));
log("onprogress: wrote " + bytesWritten + " bytes");
};
request.onreadystatechange = function(event) {
log("onreadystatechange: " + request.readyState);
if (request.readyState !== 4) {
return;
}
file.close();
if (request.status === 200) {
postMessage({ type: "success" });
} else {
try {
OS.File.remove(path);
} catch(ex) {
log("error removing " + path + ": " + ex);
}
let statusMessage = request.status + " - " + request.statusText;
postMessage({ type: "failure", message: statusMessage });
}
};
request.send(null);
}

View File

@ -30,8 +30,6 @@ EXTRA_JS_MODULES += [
'Snackbars.jsm',
'SSLExceptions.jsm',
'TabMirror.jsm',
'WebappManager.jsm',
'WebappManagerWorker.js',
]
if not CONFIG['MOZ_B2GDROID']: