mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Migrate android application_package to null safety (#84227)
This commit is contained in:
parent
452be343fd
commit
e6fe1ed73a
@ -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';
|
|||||||
/// 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 {
|
|||||||
/// 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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
|
|
||||||
// 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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
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
|
||||||
|
Loading…
Reference in New Issue
Block a user