зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1235869 - Remove web runtime from android. r=myk
This commit is contained in:
Родитель
a3ad28794e
Коммит
2ece6215a9
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -27,7 +27,6 @@ globals:
|
|||
TelemetryStopwatch: false
|
||||
UITelemetry: false
|
||||
UserAgentOverrides: 0
|
||||
WebappManager: false
|
||||
XPCOMUtils: false
|
||||
ctypes: false
|
||||
dump: false
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
|
@ -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"
|
||||
/>
|
|
@ -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",
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
|
@ -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 -->
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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]);
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
|
@ -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);
|
||||
}
|
|
@ -30,8 +30,6 @@ EXTRA_JS_MODULES += [
|
|||
'Snackbars.jsm',
|
||||
'SSLExceptions.jsm',
|
||||
'TabMirror.jsm',
|
||||
'WebappManager.jsm',
|
||||
'WebappManagerWorker.js',
|
||||
]
|
||||
|
||||
if not CONFIG['MOZ_B2GDROID']:
|
||||
|
|
Загрузка…
Ссылка в новой задаче