Bug 1234629 - Part 1: Create bouncer APK for OTA distribution installs. r=margaret,gps
☠☠ backed out by 1836c1f9ef19 ☠ ☠
authorNick Alexander <nalexander@mozilla.com>
Wed, 27 Jan 2016 15:28:31 -0800
changeset 283147 aaa420ed66d754ecc17b19f5a12297d24371f1ca
parent 283146 7a62e97f07c2c90d9f34c2c7de40f3cb193f6312
child 283148 50bcadca213183aaa64e39632892b8f00957dcfc
push id29975
push userphilringnalda@gmail.com
push dateSat, 06 Feb 2016 02:27:56 +0000
treeherdermozilla-central@6148b5848349 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmargaret, gps
bugs1234629
milestone47.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1234629 - Part 1: Create bouncer APK for OTA distribution installs. r=margaret,gps This commit produces an "install bouncer" APK which is a "hollow shell" that looks like the main Fennec APK. In particular, both APKs have: * the same Android package name (application id); and * the same set of <permission>, <uses-permission>, and <uses-feature> blocks in their manifests. The bouncer APK must always have an android:versionCode smaller than the main Fennec APK; for now, we will just bump that manually mobile/android/bouncer/moz.build.
configure.in
mobile/android/base/moz.build
mobile/android/bouncer/AndroidManifest.xml.in
mobile/android/bouncer/Makefile.in
mobile/android/bouncer/assets/example_asset.txt
mobile/android/bouncer/java/org/mozilla/bouncer/BouncerService.java
mobile/android/bouncer/java/org/mozilla/gecko/BrowserApp.java
mobile/android/bouncer/moz.build
mobile/android/bouncer/res/drawable-v21/logo.xml
mobile/android/bouncer/res/drawable/logo.xml
mobile/android/confvars.sh
mobile/android/moz.build
toolkit/mozapps/installer/upload-files.mk
--- a/configure.in
+++ b/configure.in
@@ -8535,16 +8535,17 @@ AC_SUBST(MOZ_D3DCOMPILER_XP_CAB)
 AC_SUBST(MOZ_ANDROID_HISTORY)
 AC_SUBST(MOZ_WEBSMS_BACKEND)
 AC_SUBST(MOZ_ANDROID_BEAM)
 AC_SUBST(MOZ_LOCALE_SWITCHER)
 AC_SUBST(MOZ_DISABLE_GECKOVIEW)
 AC_SUBST(MOZ_ANDROID_GCM)
 AC_SUBST(MOZ_ANDROID_GECKOLIBS_AAR)
 AC_SUBST(MOZ_ANDROID_SEARCH_ACTIVITY)
+AC_SUBST(MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER)
 AC_SUBST(MOZ_ANDROID_MLS_STUMBLER)
 AC_SUBST(MOZ_ANDROID_DOWNLOADS_INTEGRATION)
 AC_SUBST(MOZ_ANDROID_APPLICATION_CLASS)
 AC_SUBST(MOZ_ANDROID_BROWSER_INTENT_CLASS)
 AC_SUBST(MOZ_ANDROID_SEARCH_INTENT_CLASS)
 AC_SUBST(MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE)
 AC_SUBST(MOZ_EXCLUDE_HYPHENATION_DICTIONARIES)
 AC_SUBST(MOZ_INSTALL_TRACKING)
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -881,19 +881,23 @@ ANDROID_GENERATED_RESFILES += [
     'res/values/strings.xml',
 ]
 
 ANDROID_ASSETS_DIRS += [
     '/mobile/android/app/assets',
 ]
 
 if CONFIG['MOZ_ANDROID_DISTRIBUTION_DIRECTORY']:
-    ANDROID_ASSETS_DIRS += [
-        '%' + CONFIG['MOZ_ANDROID_DISTRIBUTION_DIRECTORY'] + '/assets',
-    ]
+    # If you change this, also change its equivalent in mobile/android/bouncer.
+    if not CONFIG['MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER']:
+        # If we are packaging the bouncer, it will have the distribution, so don't put
+        # it in the main APK as well.
+        ANDROID_ASSETS_DIRS += [
+            '%' + CONFIG['MOZ_ANDROID_DISTRIBUTION_DIRECTORY'] + '/assets',
+        ]
 
 # We do not expose MOZ_INSTALL_TRACKING_ADJUST_SDK_APP_TOKEN here because that
 # would leak the value to build logs.  Instead we expose the token quietly where
 # appropriate in Makefile.in.
 for var in ('MOZ_ANDROID_ANR_REPORTER', 'MOZ_LINKER_EXTRACT', 'MOZ_DEBUG',
             'MOZ_ANDROID_SEARCH_ACTIVITY', 'MOZ_NATIVE_DEVICES', 'MOZ_ANDROID_MLS_STUMBLER',
             'MOZ_ANDROID_DOWNLOADS_INTEGRATION', 'MOZ_INSTALL_TRACKING',
             'MOZ_ANDROID_GCM'):
new file mode 100644
--- /dev/null
+++ b/mobile/android/bouncer/AndroidManifest.xml.in
@@ -0,0 +1,54 @@
+#filter substitution
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="@ANDROID_PACKAGE_NAME@"
+      android:installLocation="auto"
+      android:versionCode="@ANDROID_VERSION_CODE@"
+      android:versionName="@MOZ_APP_VERSION@"
+#ifdef MOZ_ANDROID_SHARED_ID
+      android:sharedUserId="@MOZ_ANDROID_SHARED_ID@"
+#endif
+      >
+    <uses-sdk android:minSdkVersion="@MOZ_ANDROID_MIN_SDK_VERSION@"
+#ifdef MOZ_ANDROID_MAX_SDK_VERSION
+              android:maxSdkVersion="@MOZ_ANDROID_MAX_SDK_VERSION@"
+#endif
+              android:targetSdkVersion="23"/>
+
+<!-- The bouncer APK and the main APK should define the same set of
+     <permission>, <uses-permission>, and <uses-feature> elements.  This reduces
+     the likelihood of permission-related surprises when installing the main APK
+     on top of a pre-installed bouncer APK.  Add such shared elements in the
+     fileincluded here, so that they can be referenced by both APKs. -->
+#include ../base/FennecManifest_permissions.xml.in
+
+    <application android:label="@MOZ_APP_DISPLAYNAME@"
+                 android:icon="@drawable/icon"
+                 android:logo="@drawable/logo"
+                 android:hardwareAccelerated="true"
+                 android:allowBackup="false"
+# The preprocessor does not yet support arbitrary parentheses, so this cannot
+# be parenthesized thus to clarify that the logical AND operator has precedence:
+#   !defined(MOZILLA_OFFICIAL) || (defined(NIGHTLY_BUILD) && defined(MOZ_DEBUG))
+#if !defined(MOZILLA_OFFICIAL) || defined(NIGHTLY_BUILD) && defined(MOZ_DEBUG)
+                 android:debuggable="true">
+#else
+                 android:debuggable="false">
+#endif
+
+        <activity
+            android:name="@MOZ_ANDROID_BROWSER_INTENT_CLASS@"
+            android:label="@MOZ_APP_DISPLAYNAME@"
+            android:theme="@android:style/Theme.Translucent">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <service
+            android:name="org.mozilla.bouncer.BouncerService"
+            android:exported="false" />
+
+    </application>
+</manifest>
copy from mobile/android/javaaddons/Makefile.in
copy to mobile/android/bouncer/Makefile.in
--- a/mobile/android/javaaddons/Makefile.in
+++ b/mobile/android/bouncer/Makefile.in
@@ -1,9 +1,25 @@
 # 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/.
 
-include $(topsrcdir)/config/rules.mk
+include $(topsrcdir)/config/config.mk
+
+JAVAFILES := \
+	java/org/mozilla/bouncer/BouncerService.java \
+	java/org/mozilla/gecko/BrowserApp.java \
+  $(NULL)
+
+ANDROID_EXTRA_JARS := \
+  $(NULL)
 
-include $(topsrcdir)/config/android-common.mk
+PP_TARGETS += manifest
+manifest := $(srcdir)/AndroidManifest.xml.in
+manifest_TARGET := export
+# Special 'cuz they are set in mobile/android/defs.mk.
+manifest_FLAGS += \
+  -DMOZ_ANDROID_SHARED_ID="$(MOZ_ANDROID_SHARED_ID)" \
+  -DMOZ_ANDROID_SHARED_ACCOUNT_TYPE="$(MOZ_ANDROID_SHARED_ACCOUNT_TYPE)" \
+  -DMOZ_ANDROID_SHARED_FXACCOUNT_TYPE="$(MOZ_ANDROID_SHARED_FXACCOUNT_TYPE)" \
+  $(NULL)
 
-libs:: javaaddons-1.0.jar
+libs:: $(ANDROID_APK_NAME).apk
copy from mobile/android/app/assets/example_asset.txt
copy to mobile/android/bouncer/assets/example_asset.txt
new file mode 100644
--- /dev/null
+++ b/mobile/android/bouncer/java/org/mozilla/bouncer/BouncerService.java
@@ -0,0 +1,129 @@
+/* -*- 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.bouncer;
+
+import android.app.IntentService;
+import android.content.Intent;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class BouncerService extends IntentService {
+
+    private static final String LOGTAG = "GeckoBouncerService";
+
+    public BouncerService() {
+        super("BouncerService");
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        final byte[] buffer = new byte[8192];
+
+        Log.d(LOGTAG, "Preparing to copy distribution files");
+
+        final List<String> files;
+        try {
+            files = getFiles("distribution");
+        } catch (IOException e) {
+            Log.e(LOGTAG, "Error getting distribution files from assets/distribution/**", e);
+            return;
+        }
+
+        InputStream in = null;
+        for (String path : files) {
+            try {
+                Log.d(LOGTAG, "Copying distribution file: " + path);
+
+                in = getAssets().open(path);
+
+                final File outFile = getDataFile(path);
+                writeStream(in, outFile, buffer);
+            } catch (IOException e) {
+                Log.e(LOGTAG, "Error opening distribution input stream from assets", e);
+            } finally {
+                if (in != null) {
+                    try {
+                        in.close();
+                    } catch (IOException e) {
+                        Log.e(LOGTAG, "Error closing distribution input stream", e);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Recursively traverse a directory to list paths to all files.
+     *
+     * @param path Directory to traverse.
+     * @return List of all files in given directory.
+     * @throws IOException
+     */
+    private List<String> getFiles(String path) throws IOException {
+        List<String> paths = new ArrayList<>();
+        getFiles(path, paths);
+        return paths;
+    }
+
+    /**
+     * Recursively traverse a directory to list paths to all files.
+     *
+     * @param path Directory to traverse.
+     * @param acc Accumulator of paths seen.
+     * @throws IOException
+     */
+    private void getFiles(String path, List<String> acc) throws IOException {
+        final String[] list = getAssets().list(path);
+        if (list.length > 0) {
+            // We're a directory -- recurse.
+            for (final String file : list) {
+                getFiles(path + "/" + file, acc);
+            }
+        } else {
+            // We're a file -- accumulate.
+            acc.add(path);
+        }
+    }
+
+    private String getDataDir() {
+        return getApplicationInfo().dataDir;
+    }
+
+    private File getDataFile(final String path) {
+        File outFile = new File(getDataDir(), path);
+        File dir = outFile.getParentFile();
+
+        if (dir != null && !dir.exists()) {
+            Log.d(LOGTAG, "Creating " + dir.getAbsolutePath());
+            if (!dir.mkdirs()) {
+                Log.e(LOGTAG, "Unable to create directories: " + dir.getAbsolutePath());
+                return null;
+            }
+        }
+
+        return outFile;
+    }
+
+    private void writeStream(InputStream fileStream, File outFile, byte[] buffer)
+            throws IOException {
+        final OutputStream outStream = new FileOutputStream(outFile);
+        try {
+            int count;
+            while ((count = fileStream.read(buffer)) > 0) {
+                outStream.write(buffer, 0, count);
+            }
+        } finally {
+            outStream.close();
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/bouncer/java/org/mozilla/gecko/BrowserApp.java
@@ -0,0 +1,46 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+import org.mozilla.bouncer.BouncerService;
+
+/**
+ * Bouncer activity version of BrowserApp.
+ *
+ * This class has the same name as org.mozilla.gecko.BrowserApp to preserve
+ * shortcuts created by the bouncer app.
+ */
+public class BrowserApp extends Activity {
+    private static final String LOGTAG = "GeckoBouncerActivity";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // This races distribution installation against the Play Store killing our process to
+        // install the update.  We'll live with it.  To do better, consider using an Intent to
+        // notify when the service has completed.
+        startService(new Intent(this, BouncerService.class));
+
+        final String appPackageName = Uri.encode(getPackageName());
+        final Uri uri = Uri.parse("market://details?id=" + appPackageName);
+        Log.i(LOGTAG, "Lanching activity with URL: " + uri.toString());
+
+        // It might be more correct to catch failure in case the Play Store isn't installed.  The
+        // fallback action is to open the Play Store website... but doing so may offer Firefox as
+        // browser (since even the bouncer offers to view URLs), which will be very confusing.
+        // Therefore, we don't try to be fancy here, and we just fail (silently).
+        startActivity(new Intent(Intent.ACTION_VIEW, uri));
+
+        finish();
+    }
+}
copy from mobile/android/javaaddons/moz.build
copy to mobile/android/bouncer/moz.build
--- a/mobile/android/javaaddons/moz.build
+++ b/mobile/android/bouncer/moz.build
@@ -1,11 +1,32 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
-jar = add_java_jar('javaaddons-1.0')
-jar.sources = [
-    'java/org/mozilla/javaaddons/JavaAddonInterfaceV1.java',
+DEFINES['ANDROID_VERSION_CODE'] = '1'
+
+for var in ('ANDROID_PACKAGE_NAME',
+            'MOZ_ANDROID_BROWSER_INTENT_CLASS',
+            'MOZ_APP_DISPLAYNAME',
+            'MOZ_APP_VERSION'):
+    DEFINES[var] = CONFIG[var]
+
+ANDROID_APK_NAME = 'bouncer'
+ANDROID_APK_PACKAGE = CONFIG['ANDROID_PACKAGE_NAME']
+
+# Putting branding earlier allows branders to override default resources.
+ANDROID_RES_DIRS += [
+    '/' + CONFIG['MOZ_BRANDING_DIRECTORY'] + '/res', # For the icon.
+    'res',
 ]
-jar.javac_flags += ['-Xlint:all']
+
+ANDROID_ASSETS_DIRS += [
+    'assets',
+]
+
+if CONFIG['MOZ_ANDROID_DISTRIBUTION_DIRECTORY']:
+    # If you change this, also change its equivalent in mobile/android/base.
+    ANDROID_ASSETS_DIRS += [
+        '%' + CONFIG['MOZ_ANDROID_DISTRIBUTION_DIRECTORY'] + '/assets',
+    ]
copy from mobile/android/base/resources/drawable-v21/logo.xml
copy to mobile/android/bouncer/res/drawable-v21/logo.xml
copy from mobile/android/base/resources/drawable/logo.xml
copy to mobile/android/bouncer/res/drawable/logo.xml
--- a/mobile/android/confvars.sh
+++ b/mobile/android/confvars.sh
@@ -88,16 +88,19 @@ MOZ_WEBGL_CONFORMANT=1
 MOZ_ANDROID_SEARCH_ACTIVITY=1
 
 # Enable the Mozilla Location Service stumbler.
 MOZ_ANDROID_MLS_STUMBLER=1
 
 # Enable adding to the system downloads list.
 MOZ_ANDROID_DOWNLOADS_INTEGRATION=1
 
+# Build and package the install bouncer APK by default.
+MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER=1
+
 # Use the low-memory GC tuning.
 export JS_GC_SMALL_CHUNK_SIZE=1
 
 # Enable GCM registration on Nightly builds only.
 if test "$NIGHTLY_BUILD"; then
   MOZ_ANDROID_GCM=1
 fi
 
--- a/mobile/android/moz.build
+++ b/mobile/android/moz.build
@@ -21,15 +21,18 @@ DIRS += [
     'components',
     'modules',
     'themes/core',
     'app',
     'fonts',
     'geckoview_library',
 ]
 
+if CONFIG['MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER']:
+    DIRS += ['bouncer'] # No ordering implied with respect to base.
+
 DIRS += ['../../xulrunner/tools/redit']
 
 TEST_DIRS += [
     'tests',
 ]
 
 SPHINX_TREES['fennec'] = 'docs'
--- a/toolkit/mozapps/installer/upload-files.mk
+++ b/toolkit/mozapps/installer/upload-files.mk
@@ -332,16 +332,26 @@ ROBOCOP_PATH = $(topobjdir)/mobile/andro
 INNER_ROBOCOP_PACKAGE= \
   cp $(GECKO_APP_AP_PATH)/fennec_ids.txt $(ABS_DIST) && \
   $(call RELEASE_SIGN_ANDROID_APK,$(ROBOCOP_PATH)/robocop-debug-unsigned-unaligned.apk,$(ABS_DIST)/robocop.apk)
 endif
 else
 INNER_ROBOCOP_PACKAGE=echo 'Testing is disabled - No Android Robocop for you'
 endif
 
+ifdef MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER
+UPLOAD_EXTRA_FILES += bouncer.apk
+
+# Package and release sign the install bouncer APK.
+INNER_INSTALL_BOUNCER_PACKAGE=\
+  $(call RELEASE_SIGN_ANDROID_APK,$(topobjdir)/mobile/android/bouncer/bouncer-unsigned-unaligned.apk,$(ABS_DIST)/bouncer.apk)
+else
+INNER_INSTALL_BOUNCER_PACKAGE=echo 'Install bouncer is disabled - No trampolines for you'
+endif # MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER
+
 # Create geckoview_library/geckoview_{assets,library}.zip for third-party GeckoView consumers.
 ifdef NIGHTLY_BUILD
 ifndef MOZ_DISABLE_GECKOVIEW
 INNER_MAKE_GECKOVIEW_LIBRARY= \
   $(MAKE) -C ../mobile/android/geckoview_library package
 else
 INNER_MAKE_GECKOVIEW_LIBRARY=echo 'GeckoView library packaging is disabled'
 endif
@@ -473,16 +483,17 @@ INNER_MAKE_PACKAGE	= \
   $(INNER_SZIP_LIBRARIES) && \
   make -C $(GECKO_APP_AP_PATH) gecko-nodeps.ap_ && \
   cp $(GECKO_APP_AP_PATH)/gecko-nodeps.ap_ $(ABS_DIST)/gecko.ap_ && \
   ( (test ! -f $(GECKO_APP_AP_PATH)/R.txt && echo "*** Warning: The R.txt that is being packaged might not agree with the R.txt that was built. This is normal during l10n repacks.") || \
     diff $(GECKO_APP_AP_PATH)/R.txt $(GECKO_APP_AP_PATH)/gecko-nodeps/R.txt >/dev/null || \
     (echo "*** Error: The R.txt that was built and the R.txt that is being packaged are not the same. Rebuild mobile/android/base and re-package." && exit 1)) && \
   $(INNER_MAKE_APK) && \
   $(INNER_ROBOCOP_PACKAGE) && \
+  $(INNER_INSTALL_BOUNCER_PACKAGE) && \
   $(INNER_MAKE_GECKOLIBS_AAR) && \
   $(INNER_MAKE_GECKOVIEW_LIBRARY)
 endif
 
 ifeq ($(MOZ_BUILD_APP),mobile/android/b2gdroid)
 INNER_MAKE_PACKAGE	= \
   $(INNER_SZIP_LIBRARIES) && \
   cp $(topobjdir)/mobile/android/b2gdroid/app/classes.dex $(ABS_DIST)/classes.dex && \