Commit 99497dc2 authored by KyleWong's avatar KyleWong Committed by xster

Refactor android launchable activity extractor logic (#26819)

parent 07d015d4
......@@ -161,11 +161,28 @@ class AndroidApk extends ApplicationPackage {
final String packageId = manifests.first.getAttribute('package');
String launchActivity;
for (xml.XmlElement category in document.findAllElements('category')) {
if (category.getAttribute('android:name') == 'android.intent.category.LAUNCHER') {
final xml.XmlElement activity = category.parent.parent;
for (xml.XmlElement activity in document.findAllElements('activity')) {
final String enabled = activity.getAttribute('android:enabled');
if (enabled == null || enabled == 'true') {
if (enabled != null && enabled == 'false') {
continue;
}
for (xml.XmlElement element in activity.findElements('intent-filter')) {
String actionName = '';
String categoryName = '';
for (xml.XmlNode node in element.children) {
if (!(node is xml.XmlElement)) {
continue;
}
final xml.XmlElement xmlElement = node;
final String name = xmlElement.getAttribute('android:name');
if (name == 'android.intent.action.MAIN') {
actionName = name;
} else if (name == 'android.intent.category.LAUNCHER') {
categoryName = name;
}
}
if (actionName.isNotEmpty && categoryName.isNotEmpty) {
final String activityName = activity.getAttribute('android:name');
launchActivity = '$packageId/$activityName';
break;
......@@ -444,11 +461,36 @@ class ApkManifestData {
_Element launchActivity;
for (_Element activity in activities) {
final _Attribute enabled = activity.firstAttribute('android:enabled');
if (enabled == null || enabled.value.contains('0xffffffff')) {
final Iterable<_Element> intentFilters = activity
.allElements('intent-filter')
.cast<_Element>();
final bool isEnabledByDefault = enabled == null;
final bool isExplicitlyEnabled = enabled != null && enabled.value.contains('0xffffffff');
if (!(isEnabledByDefault || isExplicitlyEnabled)) {
continue;
}
for (_Element element in intentFilters) {
final _Element action = element.firstElement('action');
final _Element category = element.firstElement('category');
final String actionAttributeValue = action
?.firstAttribute('android:name')
?.value;
final String categoryAttributeValue =
category?.firstAttribute('android:name')?.value;
final bool isMainAction = actionAttributeValue != null &&
actionAttributeValue.startsWith('"android.intent.action.MAIN"');
final bool isLauncherCategory = categoryAttributeValue != null &&
categoryAttributeValue.startsWith('"android.intent.category.LAUNCHER"');
if (isMainAction && isLauncherCategory) {
launchActivity = activity;
break;
}
}
if (launchActivity != null) {
break;
}
}
final _Attribute package = manifest.firstAttribute('package');
// "io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world")
......
......@@ -26,25 +26,39 @@ final Map<Type, Generator> noColorTerminalOverride = <Type, Generator>{
void main() {
group('ApkManifestData', () {
test('Select explicity enabled activity', () {
final ApkManifestData data = ApkManifestData.parseFromXmlDump(_aaptDataWithExplicitEnabledActivity);
test('Parses manifest with an Activity that has enabled set to true, action set to android.intent.action.MAIN and category set to android.intent.category.LAUNCHER', () {
final ApkManifestData data = ApkManifestData.parseFromXmlDump(_aaptDataWithExplicitEnabledAndMainLauncherActivity);
expect(data, isNotNull);
expect(data.packageName, 'io.flutter.examples.hello_world');
expect(data.launchableActivityName, 'io.flutter.examples.hello_world.MainActivity2');
});
test('Select default enabled activity', () {
final ApkManifestData data = ApkManifestData.parseFromXmlDump(_aaptDataWithDefaultEnabledActivity);
test('Parses manifest with an Activity that has no value for its enabled field, action set to android.intent.action.MAIN and category set to android.intent.category.LAUNCHER', () {
final ApkManifestData data = ApkManifestData.parseFromXmlDump(_aaptDataWithDefaultEnabledAndMainLauncherActivity);
expect(data, isNotNull);
expect(data.packageName, 'io.flutter.examples.hello_world');
expect(data.launchableActivityName, 'io.flutter.examples.hello_world.MainActivity2');
});
testUsingContext('Error on no enabled activity', () {
testUsingContext('Error when parsing manifest with no Activity that has enabled set to true nor has no value for its enabled field', () {
final ApkManifestData data = ApkManifestData.parseFromXmlDump(_aaptDataWithNoEnabledActivity);
expect(data, isNull);
final BufferLogger logger = context[Logger];
expect(
logger.errorText, 'Error running io.flutter.examples.hello_world. Default activity not found\n');
}, overrides: noColorTerminalOverride);
testUsingContext('Error when parsing manifest with no Activity that has action set to android.intent.action.MAIN', () {
final ApkManifestData data = ApkManifestData.parseFromXmlDump(_aaptDataWithNoMainActivity);
expect(data, isNull);
final BufferLogger logger = context[Logger];
expect(
logger.errorText, 'Error running io.flutter.examples.hello_world. Default activity not found\n');
});
testUsingContext('Error when parsing manifest with no Activity that has category set to android.intent.category.LAUNCHER', () {
final ApkManifestData data = ApkManifestData.parseFromXmlDump(_aaptDataWithNoLauncherActivity);
expect(data, isNull);
final BufferLogger logger = context[Logger];
expect(
logger.errorText, 'Error running io.flutter.examples.hello_world. Default activity not found\n');
});
});
group('PrebuiltIOSApp', () {
......@@ -161,7 +175,7 @@ void main() {
});
}
const String _aaptDataWithExplicitEnabledActivity =
const String _aaptDataWithExplicitEnabledAndMainLauncherActivity =
'''N: android=http://schemas.android.com/apk/res/android
E: manifest (line=7)
A: android:versionCode(0x0101021b)=(type 0x10)0x1
......@@ -202,7 +216,7 @@ const String _aaptDataWithExplicitEnabledActivity =
A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")''';
const String _aaptDataWithDefaultEnabledActivity =
const String _aaptDataWithDefaultEnabledAndMainLauncherActivity =
'''N: android=http://schemas.android.com/apk/res/android
E: manifest (line=7)
A: android:versionCode(0x0101021b)=(type 0x10)0x1
......@@ -272,6 +286,62 @@ const String _aaptDataWithNoEnabledActivity =
E: category (line=45)
A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")''';
const String _aaptDataWithNoMainActivity =
'''N: android=http://schemas.android.com/apk/res/android
E: manifest (line=7)
A: android:versionCode(0x0101021b)=(type 0x10)0x1
A: android:versionName(0x0101021c)="0.0.1" (Raw: "0.0.1")
A: package="io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world")
E: uses-sdk (line=12)
A: android:minSdkVersion(0x0101020c)=(type 0x10)0x10
A: android:targetSdkVersion(0x01010270)=(type 0x10)0x1b
E: uses-permission (line=21)
A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET")
E: application (line=29)
A: android:label(0x01010001)="hello_world" (Raw: "hello_world")
A: android:icon(0x01010002)=@0x7f010000
A: android:name(0x01010003)="io.flutter.app.FlutterApplication" (Raw: "io.flutter.app.FlutterApplication")
A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff
E: activity (line=34)
A: android:theme(0x01010000)=@0x1030009
A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity" (Raw: "io.flutter.examples.hello_world.MainActivity")
A: android:enabled(0x0101000e)=(type 0x12)0xffffffff
A: android:launchMode(0x0101001d)=(type 0x10)0x1
A: android:configChanges(0x0101001f)=(type 0x11)0x400035b4
A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x10
A: android:hardwareAccelerated(0x010102d3)=(type 0x12)0xffffffff
E: intent-filter (line=42)
E: category (line=43)
A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")''';
const String _aaptDataWithNoLauncherActivity =
'''N: android=http://schemas.android.com/apk/res/android
E: manifest (line=7)
A: android:versionCode(0x0101021b)=(type 0x10)0x1
A: android:versionName(0x0101021c)="0.0.1" (Raw: "0.0.1")
A: package="io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world")
E: uses-sdk (line=12)
A: android:minSdkVersion(0x0101020c)=(type 0x10)0x10
A: android:targetSdkVersion(0x01010270)=(type 0x10)0x1b
E: uses-permission (line=21)
A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET")
E: application (line=29)
A: android:label(0x01010001)="hello_world" (Raw: "hello_world")
A: android:icon(0x01010002)=@0x7f010000
A: android:name(0x01010003)="io.flutter.app.FlutterApplication" (Raw: "io.flutter.app.FlutterApplication")
A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff
E: activity (line=34)
A: android:theme(0x01010000)=@0x1030009
A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity" (Raw: "io.flutter.examples.hello_world.MainActivity")
A: android:enabled(0x0101000e)=(type 0x12)0xffffffff
A: android:launchMode(0x0101001d)=(type 0x10)0x1
A: android:configChanges(0x0101001f)=(type 0x11)0x400035b4
A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x10
A: android:hardwareAccelerated(0x010102d3)=(type 0x12)0xffffffff
E: intent-filter (line=42)
E: action (line=43)
A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")''';
class MockIosWorkFlow extends Mock implements IOSWorkflow {
@override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment