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 { ...@@ -161,11 +161,28 @@ class AndroidApk extends ApplicationPackage {
final String packageId = manifests.first.getAttribute('package'); final String packageId = manifests.first.getAttribute('package');
String launchActivity; String launchActivity;
for (xml.XmlElement category in document.findAllElements('category')) { for (xml.XmlElement activity in document.findAllElements('activity')) {
if (category.getAttribute('android:name') == 'android.intent.category.LAUNCHER') { final String enabled = activity.getAttribute('android:enabled');
final xml.XmlElement activity = category.parent.parent; if (enabled != null && enabled == 'false') {
final String enabled = activity.getAttribute('android:enabled'); continue;
if (enabled == null || enabled == 'true') { }
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'); final String activityName = activity.getAttribute('android:name');
launchActivity = '$packageId/$activityName'; launchActivity = '$packageId/$activityName';
break; break;
...@@ -444,8 +461,33 @@ class ApkManifestData { ...@@ -444,8 +461,33 @@ class ApkManifestData {
_Element launchActivity; _Element launchActivity;
for (_Element activity in activities) { for (_Element activity in activities) {
final _Attribute enabled = activity.firstAttribute('android:enabled'); final _Attribute enabled = activity.firstAttribute('android:enabled');
if (enabled == null || enabled.value.contains('0xffffffff')) { final Iterable<_Element> intentFilters = activity
launchActivity = 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; break;
} }
} }
......
...@@ -26,25 +26,39 @@ final Map<Type, Generator> noColorTerminalOverride = <Type, Generator>{ ...@@ -26,25 +26,39 @@ final Map<Type, Generator> noColorTerminalOverride = <Type, Generator>{
void main() { void main() {
group('ApkManifestData', () { group('ApkManifestData', () {
test('Select explicity enabled activity', () { 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(_aaptDataWithExplicitEnabledActivity); final ApkManifestData data = ApkManifestData.parseFromXmlDump(_aaptDataWithExplicitEnabledAndMainLauncherActivity);
expect(data, isNotNull); expect(data, isNotNull);
expect(data.packageName, 'io.flutter.examples.hello_world'); expect(data.packageName, 'io.flutter.examples.hello_world');
expect(data.launchableActivityName, 'io.flutter.examples.hello_world.MainActivity2'); expect(data.launchableActivityName, 'io.flutter.examples.hello_world.MainActivity2');
}); });
test('Select default enabled activity', () { 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(_aaptDataWithDefaultEnabledActivity); final ApkManifestData data = ApkManifestData.parseFromXmlDump(_aaptDataWithDefaultEnabledAndMainLauncherActivity);
expect(data, isNotNull); expect(data, isNotNull);
expect(data.packageName, 'io.flutter.examples.hello_world'); expect(data.packageName, 'io.flutter.examples.hello_world');
expect(data.launchableActivityName, 'io.flutter.examples.hello_world.MainActivity2'); 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); final ApkManifestData data = ApkManifestData.parseFromXmlDump(_aaptDataWithNoEnabledActivity);
expect(data, isNull); expect(data, isNull);
final BufferLogger logger = context[Logger]; final BufferLogger logger = context[Logger];
expect( expect(
logger.errorText, 'Error running io.flutter.examples.hello_world. Default activity not found\n'); logger.errorText, 'Error running io.flutter.examples.hello_world. Default activity not found\n');
}, overrides: noColorTerminalOverride); }, 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', () { group('PrebuiltIOSApp', () {
...@@ -161,7 +175,7 @@ void main() { ...@@ -161,7 +175,7 @@ void main() {
}); });
} }
const String _aaptDataWithExplicitEnabledActivity = const String _aaptDataWithExplicitEnabledAndMainLauncherActivity =
'''N: android=http://schemas.android.com/apk/res/android '''N: android=http://schemas.android.com/apk/res/android
E: manifest (line=7) E: manifest (line=7)
A: android:versionCode(0x0101021b)=(type 0x10)0x1 A: android:versionCode(0x0101021b)=(type 0x10)0x1
...@@ -202,7 +216,7 @@ const String _aaptDataWithExplicitEnabledActivity = ...@@ -202,7 +216,7 @@ const String _aaptDataWithExplicitEnabledActivity =
A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")'''; 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 '''N: android=http://schemas.android.com/apk/res/android
E: manifest (line=7) E: manifest (line=7)
A: android:versionCode(0x0101021b)=(type 0x10)0x1 A: android:versionCode(0x0101021b)=(type 0x10)0x1
...@@ -272,6 +286,62 @@ const String _aaptDataWithNoEnabledActivity = ...@@ -272,6 +286,62 @@ const String _aaptDataWithNoEnabledActivity =
E: category (line=45) E: category (line=45)
A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")'''; 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 { class MockIosWorkFlow extends Mock implements IOSWorkflow {
@override @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