diff --git a/changes/342.doc.rst b/changes/342.doc.rst new file mode 100644 index 000000000..689081469 --- /dev/null +++ b/changes/342.doc.rst @@ -0,0 +1 @@ +Documented the process of signing Android apps & publishing them to the Google Play store. diff --git a/changes/438.feature.rst b/changes/438.feature.rst new file mode 100644 index 000000000..71f60f2f0 --- /dev/null +++ b/changes/438.feature.rst @@ -0,0 +1 @@ +Android apps are now packaged in Android App Bundle format. This allows the Play Store to dynmically build the smallest APK appropriate to a device installing an app. diff --git a/docs/how-to/code-signing/android.rst b/docs/how-to/code-signing/android.rst new file mode 100644 index 000000000..79172adaf --- /dev/null +++ b/docs/how-to/code-signing/android.rst @@ -0,0 +1,78 @@ +======= +Android +======= + +Overview +-------- + +Android `requires that all apps be digitally signed with a certificate before +they are installed on a device or updated +`__. Android phones +enforce a policy that updates to an app must come from the same signing key to +validate. This allows the phone to be sure an update is fundamentally the same +app, i.e., has the same author. + +This documentation covers one way to sign your app where the Google Play Store +maintains the authoritative key for your app. This approach is called `App +Signing by Google Play +`__. + +You will need to generate a key on your development workstation to sign an app +package before sending it to the Google Play store. If you use app signing by +Google Play, the key on your workstation is called the upload key. + +Generate a key +-------------- + +You will need to decide where to store the upload key. A good default is to use +one keystore file per app you are creating and to store it in the ``.android`` +folder within your home folder. The folder is automatically created by the +Android tools; but if it doesn't exist, create it. + +We recommend using a separate keystore file per app. Below, we use the +**upload-key-helloworld.jks** filename. This assumes you are building an app +called "Hello World"; use the (lowercase, no spaces) app name, ``helloworld`` +in the filename for the keystore. + +Try not to lose this key; make backups if needed. If you do lose this key, you +can `contact Google Play support to reset it +`__. +If you choose not to use app signing by Google Play, it is absolutely essential +that you not lose this key. For this reason, we recommend using App Signing by +Google Play. + +.. tabs:: + + .. group-tab:: macOS + + .. code-block:: bash + + $ mkdir -p ~/.android + $ ~/.briefcase/tools/java/Contents/Home/bin/keytool -keyalg RSA -deststoretype pkcs12 -genkey -v -storepass android -keystore ~/.android/upload-key-helloworld.jks -keysize 2048 -dname "cn=Upload Key" -alias upload-key -validity 10000 + + .. group-tab:: Linux + + .. code-block:: bash + + $ mkdir -p ~/.android + $ ~/.briefcase/tools/java/bin/keytool -keyalg RSA -deststoretype pkcs12 -genkey -v -storepass android -keystore ~/.android/upload-key-helloworld.jks -keysize 2048 -dname "cn=Upload Key" -alias upload-key -validity 10000 + + .. group-tab:: Windows + + .. code-block:: doscon + + C:\...>IF not exist %HOMEPATH%\.android mkdir %HOMEPATH%\.android + C:\...>%HOMEPATH%\.briefcase\tools\java\bin\keytool.exe -keyalg RSA -deststoretype pkcs12 -genkey -v -storepass android -keystore %HOMEPATH%\.android\upload-key-helloworld.jks -keysize 2048 -dname "cn=Upload Key" -alias upload-key -validity 10000 + + +This creates a 2048-bit key and stores it in a Java keystore located in the +``.android`` folder within your home folder. Since the key's purpose is to be +used as an upload key for the Google Play store, we call the key "upload-key". + +We use a password of ``android``. This is the `default password for common +Android keystores `__. +You can change the password if you like. It is more important to limit who +has access to the keystore file than to change the password. + +See :doc:`Publishing your app <../publishing/android/>` for instructions +on using this key to upload an app to the Google Play store. diff --git a/docs/how-to/code-signing/index.rst b/docs/how-to/code-signing/index.rst index 24d8bd27c..1c3321369 100644 --- a/docs/how-to/code-signing/index.rst +++ b/docs/how-to/code-signing/index.rst @@ -14,4 +14,5 @@ supports: .. toctree:: :maxdepth: 1 + android macOS diff --git a/docs/how-to/index.rst b/docs/how-to/index.rst index 3a120059d..77d0723bb 100644 --- a/docs/how-to/index.rst +++ b/docs/how-to/index.rst @@ -20,3 +20,4 @@ stand alone. contribute-docs See errors on iOS internal/index + publishing/index diff --git a/docs/how-to/publishing/android.rst b/docs/how-to/publishing/android.rst new file mode 100644 index 000000000..e2ffd592d --- /dev/null +++ b/docs/how-to/publishing/android.rst @@ -0,0 +1,275 @@ +======= +Android +======= + +Overview +-------- + +The Google Play Store is the most widely-used Android app store. This guide +focuses on how to distribute a BeeWare app on the Google Play Store. + +Build the app in release mode +----------------------------- + +Use Briefcase to build a release bundle for your application: + +.. tabs:: + + .. group-tab:: macOS + + .. code-block:: bash + + (venv) $ briefcase package android + [hello-world] Building Android App Bundle and APK in release mode... + ... + [hello-world] Packaged android/Hello World/app/build/outputs/bundle/release/app-release.aab + + .. group-tab:: Linux + + .. code-block:: bash + + (venv) $ briefcase package android + [hello-world] Building Android App Bundle and APK in release mode... + ... + [hello-world] Packaged android/Hello World/app/build/outputs/bundle/release/app-release.aab + + .. group-tab:: Windows + + .. code-block:: bash + + (venv) C:\...>briefcase package android + [hello-world] Building Android App Bundle and APK in release mode... + ... + [hello-world] Packaged android\Hello World\app\build\outputs\bundle\release\app-release.aab + +This will result in an Android App Bundle file being generated. An `Android App +Bundle `__ is a publishing +format that includes all your app’s compiled code and resources. + +.. note:: AAB and APK + + You may have heard of the "Android Package", or APK format. The AAB format + is a newer format that simplifies the process of uploading your app to the + Play Store, allows Google to manage the signing process, and allows the app + bundle that is installed on your end-user's device to be smaller. + +Sign the Android App Bundle +--------------------------- + +.. admonition:: Create code signing identity + + Before you sign the APK files, you need to :doc:`create a code signing + identity. <../code-signing/android>` + +The Google Play Store requires that the Android App Bundle is signed +before it is uploaded, using the Java jarsigner tool. + +In this example below, we assume your code signing identity is stored +in **upload-key-helloworld.jks** under ``.android`` within your home +folder. We also assume that the app's formal name is Hello World. You +will need to change the path to the AAB file based on your app's formal +name. + +.. tabs:: + + .. group-tab:: macOS + + .. code-block:: + + $ ~/.briefcase/tools/java/Contents/Home/bin/jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ~/.android/upload-key-helloworld.jks "android/Hello World/app/build/outputs/bundle/release/app-release.aab" upload-key -storepass android + adding: META-INF/MANIFEST.MF + adding: META-INF/UPLOAD-K.SF + adding: META-INF/UPLOAD-K.RSA + signing: BundleConfig.pb + signing: BUNDLE-METADATA/com.android.tools.build.libraries/dependencies.pb + signing: base/assets/python/app/README + ... + signing: base/manifest/AndroidManifest.xml + signing: base/assets.pb + signing: base/native.pb + signing: base/resources.pb + >>> Signer + X.509, CN=Upload Key + [trusted certificate] + + jar signed. + + Warning: + The signer's certificate is self-signed. + + .. group-tab:: Linux + + .. code-block:: + + $ ~/.briefcase/tools/java/bin/jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ~/.android/upload-key-helloworld.jks "android/Hello World/app/build/outputs/bundle/release/app-release.aab" upload-key -storepass android + adding: META-INF/MANIFEST.MF + adding: META-INF/UPLOAD-K.SF + adding: META-INF/UPLOAD-K.RSA + signing: BundleConfig.pb + signing: BUNDLE-METADATA/com.android.tools.build.libraries/dependencies.pb + signing: base/assets/python/app/README + ... + signing: base/manifest/AndroidManifest.xml + signing: base/assets.pb + signing: base/native.pb + signing: base/resources.pb + >>> Signer + X.509, CN=Upload Key + [trusted certificate] + + jar signed. + + Warning: + The signer's certificate is self-signed. + + .. group-tab:: Windows + + .. code-block:: doscon + + C:\...> %HOMEPATH%\.briefcase\tools\java\bin\jarsigner.exe -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore %HOMEPATH%\.android\upload-key-helloworld.jks "android\Hello World\app\build\outputs\bundle\release\app-release.aab" upload-key -storepass android + adding: META-INF/MANIFEST.MF + adding: META-INF/UPLOAD-K.SF + adding: META-INF/UPLOAD-K.RSA + signing: BundleConfig.pb + signing: BUNDLE-METADATA/com.android.tools.build.libraries/dependencies.pb + signing: base/assets/python/app/README + ... + signing: base/manifest/AndroidManifest.xml + signing: base/assets.pb + signing: base/native.pb + signing: base/resources.pb + >>> Signer + X.509, CN=Upload Key + [trusted certificate] + + jar signed. + + Warning: + The signer's certificate is self-signed. + +You can safely ignore the warning about the signer's certificate being +self-signed. Google will manage the process of signing the app with a verified +certificate when you upload your app for distribution. + +Add the app to the Google Play store +------------------------------------ + +To publish to the Google Play store, you will need a Google Play Developer +account, which costs ~$25 USD per year. You will then need to provide +information for your app's store listing including an icon and screenshots, +upload the app to Google, and finally roll the app out to production. + +Register for a Google Play Developer account +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Registering for a Google Play Developer account requires a Google Account. You +will need to pay registration fee and accept an agreement in the process. + +To check if you already have a Google Play Developer account, you can visit the +`Google Play console. `__ If you see a +button to **Publish an Android App on Google Play** or a button to **Create +Application**, you can skip this step. + +To create your Google Play developer account, pay the fee, and review the +agreements, `follow Google's documentation. +`__ + + +Create a listing +~~~~~~~~~~~~~~~~ + +Visit the `Google Play console. `__ +You will see a button labeled **Publish an Android App on Google Play** or +a button to **Create Application**. Click it. + +Once you've done that, click **Create Application**. Choose a language and +write a brief app title, up to 50 characters. We suggest making this the +same as your app's Formal Name in its ``pyproject.toml``. + +This will take you to **Store Listing** section of your app. You will need +to provide a short app description (up to 80 characters) and a full +description (up to 4000 characters). Your app metadata may be helpful here. + +You will also need to provide a collection of assets that will be used to +promote your application: + + * **A 512x512px icon.** This will be the icon that appears in the Play Store. + It should match the icon you set on the application itself. + + * **At least 2 screen screenshots of the app.** Google recommends using a + screenshot `without framing. + `__ + One way to capture such a screenshot is with the Android emulator's + screenshot functionality (the camera icon on the simulator controls). This + allows your screenshot to contain just what appears on the screen rather + than a picture of the virtual device. This will store a file in your + Desktop folder. + + Screenshots must be at least 320px on their smallest dimension, no larger + than 3480px on their largest dimension, and can't have an spect ratio more + extreme than 2:1. A screenshot from the Android emulator typically fulfills + these requirements. + + * **A 1024x500px feature graphic.** A feature graphic visually represents the + purpose of the app or your logo and can optionally include a screenshot of + the app in use, typically including device framing. + +Google Play supports optional graphic assets including promo videos, TV banners, +and 360 degree stereoscopic images. See also `Google's advice on graphic assets. +`__ + +Once you've completed the store listing, you'll need to fill out a range of +other details about your app, including the category where it should appear in +the Play Store, pricing details, details about the app's content and it's +suitability for children, and contact details for you as a developer. The +navigation pane (typically on the left side of the screen) contains grayed out +check marks covering all the sections with required details. Visit each of +these sections in turn; when you have met the requirements of each section, the +checkmark will turn green. Once all the checkmarks are green, you're ready to +release your app. + +Create a release +~~~~~~~~~~~~~~~~ + +Click **App releases** in the navigation pane. To produce a production app +(i.e., an app in the public Play Store that anyone can download) click +**Manage** within the **Production track**, then select **Create Release.** +If prompted to enable App Signing by Google Play, click **Continue**. + +.. note:: Non-production releases + + The Play Store also supports releasing your app for internal, alpha and + beta testing. Google's documentation `contains more details about creating + test releases + `__. + +In an earlier section of this tutorial, we used ``briefcase publish`` and +``jarsigner`` to create a signed Android App Bundle file. It is stored at +``android/Hello World/app/build/outputs/bundle/release/app-release.aab`` +(subtituting the name of your own app as necessary). Upload this file to the +Google Play console within **Browse Files** under **Android App Bundles and +APKs to add.** + +You will need to write release notes for the app in the **What's new in this +release?** section. If this is your first upload of the app, you can use +something like "Initial application release." Review your application details, + +Once you have answered those questions, you can switch back to the +**App releases** tab. Click **Edit release**, save your changes, and +click **Start Rollout To Production.** + +The Google Play Store will now review your app. You will be emailed if any +updates are required; otherwise, after a day or two, your app will be rolled +out to the Play Store. + +Publish an update +----------------- + +At some point, you'll want to publish an updated version of your application. +Generate a fresh AAB file, signed with the *same* certificate as your original +release. Then log into the Play Store console, and select your application. +Select **Release Management** in the navigation bar, then **App Releases**. + +At this point, the release process is the same as it was for your initial +release; create a release, upload your AAB file, and submit the application +for rollout. diff --git a/docs/how-to/publishing/index.rst b/docs/how-to/publishing/index.rst new file mode 100644 index 000000000..25fdd00cc --- /dev/null +++ b/docs/how-to/publishing/index.rst @@ -0,0 +1,11 @@ +=================== +Publishing your app +=================== + +Some Briefcase platforms are linked to app distribution systems. This documentation +covers how to publish your app to the appropriate distribution system. + +.. toctree:: + :maxdepth: 1 + + android diff --git a/src/briefcase/platforms/android/gradle.py b/src/briefcase/platforms/android/gradle.py index 6a726463f..5491cc683 100644 --- a/src/briefcase/platforms/android/gradle.py +++ b/src/briefcase/platforms/android/gradle.py @@ -40,9 +40,9 @@ def distribution_path(self, app): / "app" / "build" / "outputs" - / "apk" + / "bundle" / "release" - / "app-release-unsigned.apk" + / "app-release.aab" ) def gradlew_path(self, app): @@ -88,11 +88,11 @@ def output_format_template_context(self, app: BaseConfig): class GradleUpdateCommand(GradleMixin, UpdateCommand): - description = "Update an existing Android APK." + description = "Update an existing Android debug APK." class GradleBuildCommand(GradleMixin, BuildCommand): - description = "Build an Android APK." + description = "Build an Android debug APK." def build_app(self, app: BaseConfig, **kwargs): """ @@ -119,7 +119,7 @@ def build_app(self, app: BaseConfig, **kwargs): class GradleRunCommand(GradleMixin, RunCommand): - description = "Run an Android APK." + description = "Run an Android debug APK on a device (physical or virtual)." def verify_tools(self): super().verify_tools() @@ -193,26 +193,26 @@ def run_app(self, app: BaseConfig, device_or_avd=None, **kwargs): class GradlePackageCommand(GradleMixin, PackageCommand): - description = "Package an Android APK." + description = "Create an Android App Bundle and APK in release mode." def package_app(self, app: BaseConfig, **kwargs): """ Package the app for distribution. - This involves building the release APK. + This involves building the release app bundle. :param app: The application to build """ - print("[{app.app_name}] Building Android release APK...".format(app=app)) + print("[{app.app_name}] Building Android App Bundle and APK in release mode...".format(app=app)) try: self.subprocess.run( # Windows needs the full path to `gradlew`; macOS & Linux can find it # via `./gradlew`. For simplicity of implementation, we always provide # the full path. - [str(self.gradlew_path(app)), "assembleRelease"], + [str(self.gradlew_path(app)), "bundleRelease"], env=self.android_sdk.env, # Set working directory so gradle can use the app bundle path as its - # project root, i.e., to avoid 'Task assembleRelease not found'. + # project root, i.e., to avoid 'Task bundleRelease not found'. cwd=str(self.bundle_path(app)), check=True ) diff --git a/tests/platforms/android/gradle/test_package.py b/tests/platforms/android/gradle/test_package.py index 08c9622ab..bcdc141fd 100644 --- a/tests/platforms/android/gradle/test_package.py +++ b/tests/platforms/android/gradle/test_package.py @@ -24,7 +24,7 @@ def package_command(tmp_path, first_app_config): "host_os,gradlew_name", [("Windows", "gradlew.bat"), ("NonWindows", "gradlew")], ) def test_execute_gradle(package_command, first_app_config, host_os, gradlew_name): - """Validate that package_app() will launch `gradlew assembleRelease` with the + """Validate that package_app() will launch `gradlew bundleRelease` with the appropriate environment & cwd, and that it will use `gradlew.bat` on Windows but `gradlew` elsewhere.""" # Mock out `host_os` so we can validate which name is used for gradlew. @@ -36,7 +36,7 @@ def test_execute_gradle(package_command, first_app_config, host_os, gradlew_name package_command.subprocess.run.assert_called_once_with( [ str(package_command.bundle_path(first_app_config) / gradlew_name), - "assembleRelease", + "bundleRelease", ], cwd=str(package_command.bundle_path(first_app_config)), env=package_command.android_sdk.env,