Skip to content

Commit 35dd2cb

Browse files
committed
Create validatefeaturemanifest; do not rely on local xmlllint
1 parent b2cf3b1 commit 35dd2cb

File tree

16 files changed

+332
-69
lines changed

16 files changed

+332
-69
lines changed

examples/bundle/MODULE.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ maven.install(
4040
aar_import_bzl_label = "@rules_android//rules:rules.bzl",
4141
artifacts = [
4242
"com.google.guava:guava:32.1.2-android",
43+
"com.google.android.play:core:1.10.3",
4344
],
4445
repositories = [
4546
"https://maven.google.com",

examples/bundle/app/AndroidManifest.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
android:targetSdkVersion="30" />
88

99
<application
10+
android:name=".BundleApplication"
1011
android:allowBackup="true"
1112
android:icon="@drawable/ic_launcher"
1213
android:label="@string/app_name" >
@@ -18,5 +19,9 @@
1819
<category android:name="android.intent.category.LAUNCHER" />
1920
</intent-filter>
2021
</activity>
22+
<activity
23+
android:name="com.example.bundle.features.assets.FeatureActivity"
24+
android:exported="false"
25+
android:splitName="asset_feature" />
2126
</application>
2227
</manifest>

examples/bundle/app/BUILD

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ android_application(
1313

1414
android_library(
1515
name = "lib",
16-
srcs = ["BasicActivity.java"],
16+
srcs = ["BasicActivity.java", "BundleApplication.java"],
1717
manifest = "AndroidManifest.xml",
1818
resource_files = glob(["res/**"]),
19-
deps = ["@maven//:com_google_guava_guava",]
19+
deps = [
20+
"@maven//:com_google_guava_guava",
21+
"@maven//:com_google_android_play_core",
22+
]
2023
)

examples/bundle/app/BasicActivity.java

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,60 @@
1515
package com.examples.bundle.app;
1616

1717
import android.app.Activity;
18+
import android.content.Intent;
1819
import android.os.Bundle;
1920
import android.view.Menu;
2021
import android.view.View;
2122
import android.widget.Button;
2223
import android.widget.TextView;
2324

25+
import com.google.android.play.core.splitinstall.SplitInstallManager;
26+
import com.google.android.play.core.splitinstall.SplitInstallManagerFactory;
27+
import com.google.android.play.core.splitinstall.SplitInstallRequest;
28+
import com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener;
29+
import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus;
30+
2431
/**
2532
* The main activity of the Basic Sample App.
2633
*/
2734
public class BasicActivity extends Activity {
2835

36+
private static final String FEATURE_MODULE_NAME = "asset_feature";
37+
private static final String FEATURE_ACTIVITY_CLASS =
38+
"com.example.bundle.features.assets.FeatureActivity";
39+
40+
private SplitInstallManager splitInstallManager;
41+
private TextView statusTextView;
42+
43+
private final SplitInstallStateUpdatedListener listener = state -> {
44+
switch (state.status()) {
45+
case SplitInstallSessionStatus.DOWNLOADING:
46+
statusTextView.setText("Downloading feature module...");
47+
break;
48+
case SplitInstallSessionStatus.INSTALLING:
49+
statusTextView.setText("Installing feature module...");
50+
break;
51+
case SplitInstallSessionStatus.INSTALLED:
52+
statusTextView.setText("Feature module installed!");
53+
launchFeatureActivity();
54+
break;
55+
case SplitInstallSessionStatus.FAILED:
56+
statusTextView.setText("Installation failed: " + state.errorCode());
57+
break;
58+
case SplitInstallSessionStatus.CANCELED:
59+
statusTextView.setText("Installation canceled");
60+
break;
61+
}
62+
};
63+
2964
@Override
3065
protected void onCreate(Bundle savedInstanceState) {
3166
super.onCreate(savedInstanceState);
3267
setContentView(R.layout.basic_activity);
3368

69+
splitInstallManager = SplitInstallManagerFactory.create(this);
70+
statusTextView = findViewById(R.id.text_hello);
71+
3472
final Button buttons[] = {
3573
findViewById(R.id.button_id_fizz), findViewById(R.id.button_id_buzz),
3674
};
@@ -48,6 +86,53 @@ public void onClick(View v) {
4886
}
4987
});
5088
}
89+
90+
Button loadFeatureButton = findViewById(R.id.button_load_feature);
91+
loadFeatureButton.setOnClickListener(v -> loadFeatureModule());
92+
}
93+
94+
@Override
95+
protected void onResume() {
96+
super.onResume();
97+
splitInstallManager.registerListener(listener);
98+
}
99+
100+
@Override
101+
protected void onPause() {
102+
super.onPause();
103+
splitInstallManager.unregisterListener(listener);
104+
}
105+
106+
private void loadFeatureModule() {
107+
if (splitInstallManager.getInstalledModules().contains(FEATURE_MODULE_NAME)) {
108+
statusTextView.setText("Feature already installed!");
109+
launchFeatureActivity();
110+
return;
111+
}
112+
113+
statusTextView.setText("Requesting feature module...");
114+
115+
SplitInstallRequest request = SplitInstallRequest.newBuilder()
116+
.addModule(FEATURE_MODULE_NAME)
117+
.build();
118+
119+
splitInstallManager.startInstall(request)
120+
.addOnSuccessListener(sessionId -> {
121+
statusTextView.setText("Installation started (session " + sessionId + ")");
122+
})
123+
.addOnFailureListener(e -> {
124+
statusTextView.setText("Failed to start install: " + e.getMessage());
125+
});
126+
}
127+
128+
private void launchFeatureActivity() {
129+
try {
130+
Intent intent = new Intent();
131+
intent.setClassName(getPackageName(), FEATURE_ACTIVITY_CLASS);
132+
startActivity(intent);
133+
} catch (Exception e) {
134+
statusTextView.setText("Failed to launch: " + e.getMessage());
135+
}
51136
}
52137

53138
@Override
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2022 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.examples.bundle.app;
16+
17+
import android.app.Application;
18+
import android.content.Context;
19+
20+
import com.google.android.play.core.splitcompat.SplitCompat;
21+
22+
/**
23+
* Application class that enables SplitCompat for dynamic feature modules.
24+
*/
25+
public class BundleApplication extends Application {
26+
@Override
27+
protected void attachBaseContext(Context base) {
28+
super.attachBaseContext(base);
29+
SplitCompat.install(this);
30+
}
31+
}

examples/bundle/app/res/layout/basic_activity.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,10 @@
1919
android:layout_height="wrap_content"
2020
android:layout_width="wrap_content"
2121
android:text="buzz" />
22+
<Button
23+
android:id="@+id/button_load_feature"
24+
android:layout_height="wrap_content"
25+
android:layout_width="wrap_content"
26+
android:text="Load Feature" />
2227

2328
</LinearLayout>

examples/bundle/features/assets/src/AndroidManifest.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
33
xmlns:dist="http://schemas.android.com/apk/distribution"
4-
package="com.examples.bundle.features.assets">
4+
package="com.example.bundle.features.assets">
55

6-
<dist:module
6+
<dist:module
77
dist:onDemand="true"
88
dist:instant="false"
99
dist:title="${MODULE_TITLE}">

examples/bundle/features/assets/src/FeatureActivity.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@
22

33
import android.app.Activity;
44
import android.os.Bundle;
5+
import android.widget.TextView;
56

67
/** Activity provided by the dynamic feature module. */
78
public class FeatureActivity extends Activity {
89
@Override
910
protected void onCreate(Bundle savedInstanceState) {
1011
super.onCreate(savedInstanceState);
12+
TextView tv = new TextView(this);
13+
tv.setText("Feature Module Loaded: " + getFeatureName());
14+
tv.setTextSize(24);
15+
tv.setPadding(32, 32, 32, 32);
16+
setContentView(tv);
1117
}
1218

1319
public static String getFeatureName() {

rules/android_application/android_feature_module_rule.bzl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def _impl(ctx):
5050
args.add("")
5151
args.add(binary[ApkInfo].unsigned_apk.path)
5252
args.add(utils.dedupe_split_attr(ctx.split_attr.library).label)
53-
args.add(get_android_toolchain(ctx).xmllint_tool.files_to_run.executable)
53+
args.add(get_android_toolchain(ctx).android_kit.files_to_run.executable)
5454
args.add(get_android_toolchain(ctx).unzip_tool.files_to_run.executable)
5555
args.add(ctx.attr.is_asset_pack)
5656

@@ -60,7 +60,7 @@ def _impl(ctx):
6060
outputs = [validation],
6161
arguments = [args],
6262
tools = [
63-
get_android_toolchain(ctx).xmllint_tool.files_to_run.executable,
63+
get_android_toolchain(ctx).android_kit.files_to_run.executable,
6464
get_android_toolchain(ctx).unzip_tool.files_to_run.executable,
6565
],
6666
mnemonic = "ValidateFeatureModule",

rules/android_application/feature_module_validation.sh

Lines changed: 8 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -17,47 +17,16 @@ out="${1}"
1717
manifest="${2}"
1818
apk="${3}"
1919
lib_label="${4}"
20-
xmllint="${5}"
20+
android_kit="${5}"
2121
unzip="${6}"
2222
is_asset_pack="${7}"
2323

2424
if [[ -n "$manifest" ]]; then
25-
node_count=$("$xmllint" --xpath "count(//manifest/*)" "$manifest")
26-
module_count=$("$xmllint" --xpath "count(//manifest/*[local-name()='module'])" "$manifest")
27-
application_count=$("$xmllint" --xpath "count(//manifest/*[local-name()='application'])" "$manifest")
28-
application_attr_count=$("$xmllint" --xpath "count(//manifest/application/@*)" "$manifest")
29-
application_content_count=$("$xmllint" --xpath "count(//manifest/application/*)" "$manifest")
30-
module_title=$("$xmllint" --xpath "string(//manifest/*[local-name()='module'][1]/@*[local-name()='title'])" "$manifest")
31-
valid=0
32-
33-
# Valid manifest, containing a dist:module and an empty <application/>
34-
if [[ "$node_count" == "2" &&
35-
"$module_count" == "1" &&
36-
"$application_count" == "1" &&
37-
"$application_attr_count" == "0" &&
38-
"$application_content_count" == "0" ]]; then
39-
valid=1
40-
fi
41-
42-
# Valid manifest, containing a dist:module
43-
if [[ "$node_count" == "1" && "$module_count" == "1" ]]; then
44-
valid=1
45-
fi
46-
47-
if [[ "$valid" == "0" ]]; then
48-
echo ""
49-
echo "$manifest should only contain a single <dist:module /> element (and optional empty <application/>), nothing else"
50-
echo "Manifest contents: "
51-
cat "$manifest"
52-
exit 1
53-
fi
54-
55-
if [[ "$is_asset_pack" = false && "$module_title" != "\${MODULE_TITLE}" ]]; then
56-
echo ""
57-
echo "$manifest dist:title should be \${MODULE_TITLE} placeholder"
58-
echo ""
59-
exit 1
60-
fi
25+
"$android_kit" validatefeaturemanifest \
26+
-manifest "$manifest" \
27+
-output "$out" \
28+
-is_asset_pack="$is_asset_pack"
29+
exit $?
30+
else
31+
touch "$out"
6132
fi
62-
63-
touch "$out"

0 commit comments

Comments
 (0)