diff --git a/examples/bundle/.bazelrc b/examples/bundle/.bazelrc new file mode 100644 index 000000000..a28203e1e --- /dev/null +++ b/examples/bundle/.bazelrc @@ -0,0 +1,10 @@ +# Flags needed while the Android rules are being migrated to Starlark. +common --experimental_google_legacy_api +common --experimental_enable_android_migration_apis +common --android_sdk=@androidsdk//:sdk +common:core_library_desugaring --desugar_java8_libs + +# Flags to enable mobile-install v3 +mobile-install --mode=skylark --mobile_install_aspect=@rules_android//mobile_install:mi.bzl --mobile_install_supported_rules=android_binary +# Required to invoke the Studio deployer jar +mobile-install --tool_java_runtime_version=17 diff --git a/examples/bundle/.gitignore b/examples/bundle/.gitignore new file mode 100644 index 000000000..63f1fef0e --- /dev/null +++ b/examples/bundle/.gitignore @@ -0,0 +1 @@ +*.lock diff --git a/examples/bundle/BUILD b/examples/bundle/BUILD new file mode 100644 index 000000000..a09fce916 --- /dev/null +++ b/examples/bundle/BUILD @@ -0,0 +1 @@ +# Empty build file to satisfy gazelle for rules_go. \ No newline at end of file diff --git a/examples/bundle/MODULE.bazel b/examples/bundle/MODULE.bazel new file mode 100644 index 000000000..0d85b63a4 --- /dev/null +++ b/examples/bundle/MODULE.bazel @@ -0,0 +1,55 @@ +module( + name = "bundle", +) + +bazel_dep(name = "rules_java", version = "7.4.0") +bazel_dep(name = "bazel_skylib", version = "1.3.0") + +bazel_dep( + name = "rules_android", + version = "0.5.1", +) + +local_path_override( + module_name = "rules_android", + path = "../../", +) + +remote_android_extensions = use_extension( + "@rules_android//bzlmod_extensions:android_extensions.bzl", + "remote_android_tools_extensions") +use_repo(remote_android_extensions, "android_tools") + +register_toolchains( + "@rules_android//toolchains/android:android_default_toolchain", + "@rules_android//toolchains/android_sdk:android_sdk_tools", +) + +android_sdk_repository_extension = use_extension("@rules_android//rules/android_sdk_repository:rule.bzl", "android_sdk_repository_extension") +use_repo(android_sdk_repository_extension, "androidsdk") + +register_toolchains("@androidsdk//:sdk-toolchain", "@androidsdk//:all") + +bazel_dep(name = "rules_jvm_external", version = "5.3") + +# Load the maven extension from rules_jvm_external +maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") + +maven.install( + name = "maven", + aar_import_bzl_label = "@rules_android//rules:rules.bzl", + artifacts = [ + "com.google.guava:guava:32.1.2-android", + "com.google.android.play:core:1.10.3", + ], + repositories = [ + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], + use_starlark_android_rules = True, +) +use_repo(maven, "maven") + + + + diff --git a/examples/bundle/README.md b/examples/bundle/README.md new file mode 100644 index 000000000..0d923e32d --- /dev/null +++ b/examples/bundle/README.md @@ -0,0 +1,12 @@ +To build, ensure that the `ANDROID_HOME` environment variable is set to the path +to an Android SDK, and run: + +``` +bazel build app:assets +``` + +This will build application bundle containing a dynamic feature containing assets (named assets.txt). Verify with : + +``` +jar -tf bazel-bin/app/assets_unsigned.aab | grep assets.txt +``` diff --git a/examples/bundle/WORKSPACE b/examples/bundle/WORKSPACE new file mode 100644 index 000000000..a23815374 --- /dev/null +++ b/examples/bundle/WORKSPACE @@ -0,0 +1,58 @@ +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") + +maybe( + http_archive, + name = "rules_jvm_external", + strip_prefix = "rules_jvm_external-fa73b1a8e4846cee88240d0019b8f80d39feb1c3", + sha256 = "7e13e48b50f9505e8a99cc5a16c557cbe826e9b68d733050cd1e318d69f94bb5", + url = "https://github.com/bazelbuild/rules_jvm_external/archive/fa73b1a8e4846cee88240d0019b8f80d39feb1c3.zip", +) + +maybe( + http_archive, + name = "bazel_skylib", + urls = [ + "https://github.com/bazelbuild/bazel-skylib/releases/download/1.0.3/bazel-skylib-1.0.3.tar.gz", + "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.0.3/bazel-skylib-1.0.3.tar.gz", + ], + sha256 = "1c531376ac7e5a180e0237938a2536de0c54d93f5c278634818e0efc952dd56c", +) +load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") +bazel_skylib_workspace() + +local_repository( + name = "rules_android", + path = "../..", # rules_android's WORKSPACE relative to this inner workspace +) + +load("@rules_android//:prereqs.bzl", "rules_android_prereqs") +rules_android_prereqs() +load("@rules_android//:defs.bzl", "rules_android_workspace") +rules_android_workspace() + +load("@rules_android//rules:rules.bzl", "android_sdk_repository") +android_sdk_repository( + name = "androidsdk", +) + +register_toolchains( + "@rules_android//toolchains/android:android_default_toolchain", + "@rules_android//toolchains/android_sdk:android_sdk_tools", +) + +load("@rules_jvm_external//:defs.bzl", "maven_install") + +maven_install( + name = "maven", + aar_import_bzl_label = "@rules_android//rules:rules.bzl", + artifacts = [ + "com.google.guava:guava:32.1.2-android", + "com.arthenica:ffmpeg-kit-https:4.4.LTS", + ], + repositories = [ + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], + use_starlark_android_rules = True, +) diff --git a/examples/bundle/WORKSPACE.bzlmod b/examples/bundle/WORKSPACE.bzlmod new file mode 100644 index 000000000..df9ed0ec9 --- /dev/null +++ b/examples/bundle/WORKSPACE.bzlmod @@ -0,0 +1 @@ +workspace(name = "bundle") diff --git a/examples/bundle/app/AndroidManifest.xml b/examples/bundle/app/AndroidManifest.xml new file mode 100644 index 000000000..d56513140 --- /dev/null +++ b/examples/bundle/app/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + diff --git a/examples/bundle/app/BUILD b/examples/bundle/app/BUILD new file mode 100644 index 000000000..dab595436 --- /dev/null +++ b/examples/bundle/app/BUILD @@ -0,0 +1,24 @@ +load("@rules_android//android:rules.bzl", "android_application", "android_library") + +android_application( + name = "assets", + manifest_values = { + "applicationId" : "com.examples.bundle.app", + "versionCode": "0", + }, + feature_modules = ["//features/assets:feature_module"], + manifest = "AndroidManifest.xml", + proguard_specs = ["proguard.cfg"], + deps = [":lib"], +) + +android_library( + name = "lib", + srcs = ["BasicActivity.java", "BundleApplication.java"], + manifest = "AndroidManifest.xml", + resource_files = glob(["res/**"]), + deps = [ + "@maven//:com_google_guava_guava", + "@maven//:com_google_android_play_core", + ] +) diff --git a/examples/bundle/app/BasicActivity.java b/examples/bundle/app/BasicActivity.java new file mode 100644 index 000000000..2d4274cf8 --- /dev/null +++ b/examples/bundle/app/BasicActivity.java @@ -0,0 +1,144 @@ +// Copyright 2022 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.examples.bundle.app; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.Menu; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +import com.google.android.play.core.splitinstall.SplitInstallManager; +import com.google.android.play.core.splitinstall.SplitInstallManagerFactory; +import com.google.android.play.core.splitinstall.SplitInstallRequest; +import com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener; +import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus; + +/** + * The main activity of the Basic Sample App. + */ +public class BasicActivity extends Activity { + + private static final String FEATURE_MODULE_NAME = "asset_feature"; + private static final String FEATURE_ACTIVITY_CLASS = + "com.example.bundle.features.assets.FeatureActivity"; + + private SplitInstallManager splitInstallManager; + private TextView statusTextView; + + private final SplitInstallStateUpdatedListener listener = state -> { + switch (state.status()) { + case SplitInstallSessionStatus.DOWNLOADING: + statusTextView.setText("Downloading feature module..."); + break; + case SplitInstallSessionStatus.INSTALLING: + statusTextView.setText("Installing feature module..."); + break; + case SplitInstallSessionStatus.INSTALLED: + statusTextView.setText("Feature module installed!"); + launchFeatureActivity(); + break; + case SplitInstallSessionStatus.FAILED: + statusTextView.setText("Installation failed: " + state.errorCode()); + break; + case SplitInstallSessionStatus.CANCELED: + statusTextView.setText("Installation canceled"); + break; + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.basic_activity); + + splitInstallManager = SplitInstallManagerFactory.create(this); + statusTextView = findViewById(R.id.text_hello); + + final Button buttons[] = { + findViewById(R.id.button_id_fizz), findViewById(R.id.button_id_buzz), + }; + + for (Button b : buttons) { + b.setOnClickListener( + new View.OnClickListener() { + public void onClick(View v) { + TextView tv = findViewById(R.id.text_hello); + if (v.getId() == R.id.button_id_fizz) { + tv.setText("fizz"); + } else if (v.getId() == R.id.button_id_buzz) { + tv.setText("buzz "); + } + } + }); + } + + Button loadFeatureButton = findViewById(R.id.button_load_feature); + loadFeatureButton.setOnClickListener(v -> loadFeatureModule()); + } + + @Override + protected void onResume() { + super.onResume(); + splitInstallManager.registerListener(listener); + } + + @Override + protected void onPause() { + super.onPause(); + splitInstallManager.unregisterListener(listener); + } + + private void loadFeatureModule() { + if (splitInstallManager.getInstalledModules().contains(FEATURE_MODULE_NAME)) { + statusTextView.setText("Feature already installed!"); + launchFeatureActivity(); + return; + } + + statusTextView.setText("Requesting feature module..."); + + SplitInstallRequest request = SplitInstallRequest.newBuilder() + .addModule(FEATURE_MODULE_NAME) + .build(); + + splitInstallManager.startInstall(request) + .addOnSuccessListener(sessionId -> { + statusTextView.setText("Installation started (session " + sessionId + ")"); + }) + .addOnFailureListener(e -> { + statusTextView.setText("Failed to start install: " + e.getMessage()); + }); + } + + private void launchFeatureActivity() { + try { + Intent intent = new Intent(); + intent.setClassName(getPackageName(), FEATURE_ACTIVITY_CLASS); + startActivity(intent); + } catch (Exception e) { + statusTextView.setText("Failed to launch: " + e.getMessage()); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu, menu); + return true; + } +} diff --git a/examples/bundle/app/BundleApplication.java b/examples/bundle/app/BundleApplication.java new file mode 100644 index 000000000..bdc0e0728 --- /dev/null +++ b/examples/bundle/app/BundleApplication.java @@ -0,0 +1,31 @@ +// Copyright 2022 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.examples.bundle.app; + +import android.app.Application; +import android.content.Context; + +import com.google.android.play.core.splitcompat.SplitCompat; + +/** + * Application class that enables SplitCompat for dynamic feature modules. + */ +public class BundleApplication extends Application { + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + SplitCompat.install(this); + } +} diff --git a/examples/bundle/app/proguard.cfg b/examples/bundle/app/proguard.cfg new file mode 100644 index 000000000..f5ee7318a --- /dev/null +++ b/examples/bundle/app/proguard.cfg @@ -0,0 +1,10 @@ +# Keep application classes +-keep class com.examples.bundle.app.** { *; } +-keep class com.example.bundle.features.** { *; } + +# Keep Android components +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application + +# Don't warn about missing classes +-dontwarn ** diff --git a/examples/bundle/app/res/drawable-hdpi/ic_launcher.png b/examples/bundle/app/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 000000000..6ab2adde2 Binary files /dev/null and b/examples/bundle/app/res/drawable-hdpi/ic_launcher.png differ diff --git a/examples/bundle/app/res/drawable-mdpi/ic_launcher.png b/examples/bundle/app/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 000000000..c0a73c33d Binary files /dev/null and b/examples/bundle/app/res/drawable-mdpi/ic_launcher.png differ diff --git a/examples/bundle/app/res/drawable-xhdpi/ic_launcher.png b/examples/bundle/app/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 000000000..014b0f106 Binary files /dev/null and b/examples/bundle/app/res/drawable-xhdpi/ic_launcher.png differ diff --git a/examples/bundle/app/res/drawable-xxhdpi/ic_launcher.png b/examples/bundle/app/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..20703a15c Binary files /dev/null and b/examples/bundle/app/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/examples/bundle/app/res/layout/basic_activity.xml b/examples/bundle/app/res/layout/basic_activity.xml new file mode 100644 index 000000000..2e08425a5 --- /dev/null +++ b/examples/bundle/app/res/layout/basic_activity.xml @@ -0,0 +1,28 @@ + + + + +