Unverified Commit e6fe1ed7 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Migrate android application_package to null safety (#84227)

parent 452be343
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// @dart = 2.8
import 'dart:collection'; import 'dart:collection';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
...@@ -25,10 +23,10 @@ import 'gradle.dart'; ...@@ -25,10 +23,10 @@ import 'gradle.dart';
/// An application package created from an already built Android APK. /// An application package created from an already built Android APK.
class AndroidApk extends ApplicationPackage { class AndroidApk extends ApplicationPackage {
AndroidApk({ AndroidApk({
String id, required String id,
@required this.file, required this.file,
@required this.versionCode, required this.versionCode,
@required this.launchActivity, required this.launchActivity,
}) : assert(file != null), }) : assert(file != null),
assert(launchActivity != null), assert(launchActivity != null),
super(id: id); super(id: id);
...@@ -36,14 +34,15 @@ class AndroidApk extends ApplicationPackage { ...@@ -36,14 +34,15 @@ class AndroidApk extends ApplicationPackage {
/// Creates a new AndroidApk from an existing APK. /// Creates a new AndroidApk from an existing APK.
/// ///
/// Returns `null` if the APK was invalid or any required tooling was missing. /// Returns `null` if the APK was invalid or any required tooling was missing.
factory AndroidApk.fromApk(File apk, { static AndroidApk? fromApk(
@required AndroidSdk androidSdk, File apk, {
@required ProcessManager processManager, required AndroidSdk androidSdk,
@required UserMessages userMessages, required ProcessManager processManager,
@required Logger logger, required UserMessages userMessages,
@required ProcessUtils processUtils, required Logger logger,
required ProcessUtils processUtils,
}) { }) {
final String aaptPath = androidSdk?.latestVersion?.aaptPath; final String? aaptPath = androidSdk.latestVersion?.aaptPath;
if (aaptPath == null || !processManager.canRun(aaptPath)) { if (aaptPath == null || !processManager.canRun(aaptPath)) {
logger.printError(userMessages.aaptNotFound); logger.printError(userMessages.aaptNotFound);
return null; return null;
...@@ -66,22 +65,23 @@ class AndroidApk extends ApplicationPackage { ...@@ -66,22 +65,23 @@ class AndroidApk extends ApplicationPackage {
return null; return null;
} }
final ApkManifestData data = ApkManifestData.parseFromXmlDump(apptStdout, logger); final ApkManifestData? data = ApkManifestData.parseFromXmlDump(apptStdout, logger);
if (data == null) { if (data == null) {
logger.printError('Unable to read manifest info from ${apk.path}.'); logger.printError('Unable to read manifest info from ${apk.path}.');
return null; return null;
} }
if (data.packageName == null || data.launchableActivityName == null) { final String? packageName = data.packageName;
if (packageName == null || data.launchableActivityName == null) {
logger.printError('Unable to read manifest info from ${apk.path}.'); logger.printError('Unable to read manifest info from ${apk.path}.');
return null; return null;
} }
return AndroidApk( return AndroidApk(
id: data.packageName, id: packageName,
file: apk, file: apk,
versionCode: int.tryParse(data.versionCode), versionCode: data.versionCode == null ? null : int.tryParse(data.versionCode!),
launchActivity: '${data.packageName}/${data.launchableActivityName}', launchActivity: '${data.packageName}/${data.launchableActivityName}',
); );
} }
...@@ -93,16 +93,17 @@ class AndroidApk extends ApplicationPackage { ...@@ -93,16 +93,17 @@ class AndroidApk extends ApplicationPackage {
final String launchActivity; final String launchActivity;
/// The version code of the APK. /// The version code of the APK.
final int versionCode; final int? versionCode;
/// Creates a new AndroidApk based on the information in the Android manifest. /// Creates a new AndroidApk based on the information in the Android manifest.
static Future<AndroidApk> fromAndroidProject(AndroidProject androidProject, { static Future<AndroidApk?> fromAndroidProject(
@required AndroidSdk androidSdk, AndroidProject androidProject, {
@required ProcessManager processManager, required AndroidSdk androidSdk,
@required UserMessages userMessages, required ProcessManager processManager,
@required ProcessUtils processUtils, required UserMessages userMessages,
@required Logger logger, required ProcessUtils processUtils,
@required FileSystem fileSystem, required Logger logger,
required FileSystem fileSystem,
}) async { }) async {
File apkFile; File apkFile;
...@@ -157,32 +158,31 @@ class AndroidApk extends ApplicationPackage { ...@@ -157,32 +158,31 @@ class AndroidApk extends ApplicationPackage {
logger.printError('Please check ${manifest.path} for errors.'); logger.printError('Please check ${manifest.path} for errors.');
return null; return null;
} }
final String packageId = manifests.first.getAttribute('package'); final String? packageId = manifests.first.getAttribute('package');
String launchActivity; String? launchActivity;
for (final XmlElement activity in document.findAllElements('activity')) { for (final XmlElement activity in document.findAllElements('activity')) {
final String enabled = activity.getAttribute('android:enabled'); final String? enabled = activity.getAttribute('android:enabled');
if (enabled != null && enabled == 'false') { if (enabled != null && enabled == 'false') {
continue; continue;
} }
for (final XmlElement element in activity.findElements('intent-filter')) { for (final XmlElement element in activity.findElements('intent-filter')) {
String actionName = ''; String? actionName = '';
String categoryName = ''; String? categoryName = '';
for (final XmlNode node in element.children) { for (final XmlNode node in element.children) {
if (node is! XmlElement) { if (node is! XmlElement) {
continue; continue;
} }
final XmlElement xmlElement = node as XmlElement; final String? name = node.getAttribute('android:name');
final String name = xmlElement.getAttribute('android:name');
if (name == 'android.intent.action.MAIN') { if (name == 'android.intent.action.MAIN') {
actionName = name; actionName = name;
} else if (name == 'android.intent.category.LAUNCHER') { } else if (name == 'android.intent.category.LAUNCHER') {
categoryName = name; categoryName = name;
} }
} }
if (actionName.isNotEmpty && categoryName.isNotEmpty) { if (actionName != null && categoryName != null && 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;
} }
...@@ -213,47 +213,51 @@ class AndroidApk extends ApplicationPackage { ...@@ -213,47 +213,51 @@ class AndroidApk extends ApplicationPackage {
abstract class _Entry { abstract class _Entry {
const _Entry(this.parent, this.level); const _Entry(this.parent, this.level);
final _Element parent; final _Element? parent;
final int level; final int level;
} }
class _Element extends _Entry { class _Element extends _Entry {
_Element._(this.name, _Element parent, int level) : super(parent, level); _Element._(this.name, _Element? parent, int level) : super(parent, level);
factory _Element.fromLine(String line, _Element parent) { factory _Element.fromLine(String line, _Element? parent) {
// E: application (line=29) // E: application (line=29)
final List<String> parts = line.trimLeft().split(' '); final List<String> parts = line.trimLeft().split(' ');
return _Element._(parts[1], parent, line.length - line.trimLeft().length); return _Element._(parts[1], parent, line.length - line.trimLeft().length);
} }
final List<_Entry> children = <_Entry>[]; final List<_Entry> children = <_Entry>[];
final String name; final String? name;
void addChild(_Entry child) { void addChild(_Entry child) {
children.add(child); children.add(child);
} }
_Attribute firstAttribute(String name) { _Attribute? firstAttribute(String name) {
return children.whereType<_Attribute>().firstWhere( for (final _Attribute child in children.whereType<_Attribute>()) {
(_Attribute e) => e.key.startsWith(name), if (child.key?.startsWith(name) == true) {
orElse: () => null, return child;
); }
}
return null;
} }
_Element firstElement(String name) { _Element? firstElement(String name) {
return children.whereType<_Element>().firstWhere( for (final _Element child in children.whereType<_Element>()) {
(_Element e) => e.name.startsWith(name), if (child.name?.startsWith(name) == true) {
orElse: () => null, return child;
); }
}
return null;
} }
Iterable<_Element> allElements(String name) { Iterable<_Element> allElements(String name) {
return children.whereType<_Element>().where((_Element e) => e.name.startsWith(name)); return children.whereType<_Element>().where((_Element e) => e.name?.startsWith(name) == true);
} }
} }
class _Attribute extends _Entry { class _Attribute extends _Entry {
const _Attribute._(this.key, this.value, _Element parent, int level) : super(parent, level); const _Attribute._(this.key, this.value, _Element? parent, int level) : super(parent, level);
factory _Attribute.fromLine(String line, _Element parent) { factory _Attribute.fromLine(String line, _Element parent) {
// A: android:label(0x01010001)="hello_world" (Raw: "hello_world") // A: android:label(0x01010001)="hello_world" (Raw: "hello_world")
...@@ -262,19 +266,19 @@ class _Attribute extends _Entry { ...@@ -262,19 +266,19 @@ class _Attribute extends _Entry {
return _Attribute._(keyVal[0], keyVal[1], parent, line.length - line.trimLeft().length); return _Attribute._(keyVal[0], keyVal[1], parent, line.length - line.trimLeft().length);
} }
final String key; final String? key;
final String value; final String? value;
} }
class ApkManifestData { class ApkManifestData {
ApkManifestData._(this._data); ApkManifestData._(this._data);
static bool _isAttributeWithValuePresent(_Element baseElement, static bool _isAttributeWithValuePresent(
String childElement, String attributeName, String attributeValue) { _Element baseElement, String childElement, String attributeName, String attributeValue) {
final Iterable<_Element> allElements = baseElement.allElements(childElement); final Iterable<_Element> allElements = baseElement.allElements(childElement);
for (final _Element oneElement in allElements) { for (final _Element oneElement in allElements) {
final String elementAttributeValue = oneElement final String? elementAttributeValue = oneElement
?.firstAttribute(attributeName) .firstAttribute(attributeName)
?.value; ?.value;
if (elementAttributeValue != null && if (elementAttributeValue != null &&
elementAttributeValue.startsWith(attributeValue)) { elementAttributeValue.startsWith(attributeValue)) {
...@@ -284,7 +288,7 @@ class ApkManifestData { ...@@ -284,7 +288,7 @@ class ApkManifestData {
return false; return false;
} }
static ApkManifestData parseFromXmlDump(String data, Logger logger) { static ApkManifestData? parseFromXmlDump(String data, Logger logger) {
if (data == null || data.trim().isEmpty) { if (data == null || data.trim().isEmpty) {
return null; return null;
} }
...@@ -302,7 +306,7 @@ class ApkManifestData { ...@@ -302,7 +306,7 @@ class ApkManifestData {
// Handle level out // Handle level out
while (currentElement.parent != null && level <= currentElement.level) { while (currentElement.parent != null && level <= currentElement.level) {
currentElement = currentElement.parent; currentElement = currentElement.parent!;
} }
if (level > currentElement.level) { if (level > currentElement.level) {
...@@ -319,19 +323,19 @@ class ApkManifestData { ...@@ -319,19 +323,19 @@ class ApkManifestData {
} }
} }
final _Element application = manifest.firstElement('application'); final _Element? application = manifest.firstElement('application');
if (application == null) { if (application == null) {
return null; return null;
} }
final Iterable<_Element> activities = application.allElements('activity'); final Iterable<_Element> activities = application.allElements('activity');
_Element launchActivity; _Element? launchActivity;
for (final _Element activity in activities) { for (final _Element activity in activities) {
final _Attribute enabled = activity.firstAttribute('android:enabled'); final _Attribute? enabled = activity.firstAttribute('android:enabled');
final Iterable<_Element> intentFilters = activity.allElements('intent-filter'); final Iterable<_Element> intentFilters = activity.allElements('intent-filter');
final bool isEnabledByDefault = enabled == null; final bool isEnabledByDefault = enabled == null;
final bool isExplicitlyEnabled = enabled != null && enabled.value.contains('0xffffffff'); final bool isExplicitlyEnabled = enabled != null && enabled.value?.contains('0xffffffff') == true;
if (!(isEnabledByDefault || isExplicitlyEnabled)) { if (!(isEnabledByDefault || isExplicitlyEnabled)) {
continue; continue;
} }
...@@ -356,31 +360,30 @@ class ApkManifestData { ...@@ -356,31 +360,30 @@ class ApkManifestData {
} }
} }
final _Attribute package = manifest.firstAttribute('package'); final _Attribute? package = manifest.firstAttribute('package');
// "io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world") // "io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world")
final String packageName = package.value.substring(1, package.value.indexOf('" ')); final String? packageName = package?.value?.substring(1, package.value?.indexOf('" '));
if (launchActivity == null) { if (launchActivity == null) {
logger.printError('Error running $packageName. Default activity not found'); logger.printError('Error running $packageName. Default activity not found');
return null; return null;
} }
final _Attribute nameAttribute = launchActivity.firstAttribute('android:name'); final _Attribute? nameAttribute = launchActivity.firstAttribute('android:name');
// "io.flutter.examples.hello_world.MainActivity" (Raw: "io.flutter.examples.hello_world.MainActivity") // "io.flutter.examples.hello_world.MainActivity" (Raw: "io.flutter.examples.hello_world.MainActivity")
final String activityName = nameAttribute final String? activityName = nameAttribute?.value?.substring(1, nameAttribute.value?.indexOf('" '));
.value.substring(1, nameAttribute.value.indexOf('" '));
// Example format: (type 0x10)0x1 // Example format: (type 0x10)0x1
final _Attribute versionCodeAttr = manifest.firstAttribute('android:versionCode'); final _Attribute? versionCodeAttr = manifest.firstAttribute('android:versionCode');
if (versionCodeAttr == null) { if (versionCodeAttr == null) {
logger.printError('Error running $packageName. Manifest versionCode not found'); logger.printError('Error running $packageName. Manifest versionCode not found');
return null; return null;
} }
if (!versionCodeAttr.value.startsWith('(type 0x10)')) { if (versionCodeAttr.value?.startsWith('(type 0x10)') != true) {
logger.printError('Error running $packageName. Manifest versionCode invalid'); logger.printError('Error running $packageName. Manifest versionCode invalid');
return null; return null;
} }
final int versionCode = int.tryParse(versionCodeAttr.value.substring(11)); final int? versionCode = versionCodeAttr.value == null ? null : int.tryParse(versionCodeAttr.value!.substring(11));
if (versionCode == null) { if (versionCode == null) {
logger.printError('Error running $packageName. Manifest versionCode invalid'); logger.printError('Error running $packageName. Manifest versionCode invalid');
return null; return null;
...@@ -403,12 +406,12 @@ class ApkManifestData { ...@@ -403,12 +406,12 @@ class ApkManifestData {
Map<String, Map<String, String>> get data => Map<String, Map<String, String>> get data =>
UnmodifiableMapView<String, Map<String, String>>(_data); UnmodifiableMapView<String, Map<String, String>>(_data);
String get packageName => _data['package'] == null ? null : _data['package']['name']; String? get packageName => _data['package'] == null ? null : _data['package']?['name'];
String get versionCode => _data['version-code'] == null ? null : _data['version-code']['name']; String? get versionCode => _data['version-code'] == null ? null : _data['version-code']?['name'];
String get launchableActivityName { String? get launchableActivityName {
return _data['launchable-activity'] == null ? null : _data['launchable-activity']['name']; return _data['launchable-activity'] == null ? null : _data['launchable-activity']?['name'];
} }
@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