mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Most of the infrastructure needed to install an APK on Android.
This commit is contained in:
parent
d8d87f1833
commit
c5ea40980a
@ -40,13 +40,13 @@ abstract class _Device {
|
|||||||
_Device._(this.id);
|
_Device._(this.id);
|
||||||
|
|
||||||
/// Install an app package on the current device
|
/// Install an app package on the current device
|
||||||
bool installApp(String path);
|
bool installApp(String appPath, String appPackageID, String appFileName);
|
||||||
|
|
||||||
/// Check if the current device needs an installation
|
|
||||||
bool needsInstall();
|
|
||||||
|
|
||||||
/// Check if the device is currently connected
|
/// Check if the device is currently connected
|
||||||
bool isConnected();
|
bool isConnected();
|
||||||
|
|
||||||
|
/// Check if the current version of the given app is already installed
|
||||||
|
bool isAppInstalled(String appPath, String appPackageID, String appFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
class AndroidDevice extends _Device {
|
class AndroidDevice extends _Device {
|
||||||
@ -57,54 +57,44 @@ class AndroidDevice extends _Device {
|
|||||||
|
|
||||||
String _adbPath;
|
String _adbPath;
|
||||||
String get adbPath => _adbPath;
|
String get adbPath => _adbPath;
|
||||||
|
bool _hasAdb = false;
|
||||||
|
bool _hasValidAndroid = false;
|
||||||
|
|
||||||
factory AndroidDevice([String id = null]) {
|
factory AndroidDevice([String id = null]) {
|
||||||
return new _Device(className, id);
|
return new _Device(className, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
AndroidDevice._(id) : super._(id) {
|
AndroidDevice._(id) : super._(id) {
|
||||||
_updatePaths();
|
_adbPath = _getAdbPath();
|
||||||
|
_hasAdb = _checkForAdb();
|
||||||
|
|
||||||
// Checking for lollipop only needs to be done if we are starting an
|
// Checking for lollipop only needs to be done if we are starting an
|
||||||
// app, but it has an important side effect, which is to discard any
|
// app, but it has an important side effect, which is to discard any
|
||||||
// progress messages if the adb server is restarted.
|
// progress messages if the adb server is restarted.
|
||||||
if (!_checkForAdb() || !_checkForLollipopOrLater()) {
|
_hasValidAndroid = _checkForLollipopOrLater();
|
||||||
|
|
||||||
|
if (!_hasAdb || !_hasValidAndroid) {
|
||||||
_logging.severe('Unable to run on Android.');
|
_logging.severe('Unable to run on Android.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
String _getAdbPath() {
|
||||||
bool installApp(String path) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool needsInstall() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool isConnected() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updatePaths() {
|
|
||||||
if (Platform.environment.containsKey('ANDROID_HOME')) {
|
if (Platform.environment.containsKey('ANDROID_HOME')) {
|
||||||
String androidHomeDir = Platform.environment['ANDROID_HOME'];
|
String androidHomeDir = Platform.environment['ANDROID_HOME'];
|
||||||
String adbPath1 =
|
String adbPath1 =
|
||||||
path.join(androidHomeDir, 'sdk', 'platform-tools', 'adb');
|
path.join(androidHomeDir, 'sdk', 'platform-tools', 'adb');
|
||||||
String adbPath2 = path.join(androidHomeDir, 'platform-tools', 'adb');
|
String adbPath2 = path.join(androidHomeDir, 'platform-tools', 'adb');
|
||||||
if (FileSystemEntity.isFileSync(adbPath1)) {
|
if (FileSystemEntity.isFileSync(adbPath1)) {
|
||||||
_adbPath = adbPath1;
|
return adbPath1;
|
||||||
} else if (FileSystemEntity.isFileSync(adbPath2)) {
|
} else if (FileSystemEntity.isFileSync(adbPath2)) {
|
||||||
_adbPath = adbPath2;
|
return adbPath2;
|
||||||
} else {
|
} else {
|
||||||
_logging.info('"adb" not found at\n "$adbPath1" or\n "$adbPath2"\n' +
|
_logging.info('"adb" not found at\n "$adbPath1" or\n "$adbPath2"\n' +
|
||||||
'using default path "$_ADB_PATH"');
|
'using default path "$_ADB_PATH"');
|
||||||
_adbPath = _ADB_PATH;
|
return _ADB_PATH;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_adbPath = _ADB_PATH;
|
return _ADB_PATH;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,4 +174,74 @@ class AndroidDevice extends _Device {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _getDeviceSha1Path(String appPackageID, String appFileName) {
|
||||||
|
return '/sdcard/$appPackageID/$appFileName.sha1';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getDeviceApkSha1(String appPackageID, String appFileName) {
|
||||||
|
return runCheckedSync([
|
||||||
|
adbPath,
|
||||||
|
'shell',
|
||||||
|
'cat',
|
||||||
|
_getDeviceSha1Path(appPackageID, appFileName)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getSourceSha1(String apkPath) {
|
||||||
|
String sha1 =
|
||||||
|
runCheckedSync(['shasum', '-a', '1', '-p', apkPath]).split(' ')[0];
|
||||||
|
return sha1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isAppInstalled(String appPath, String appPackageID, String appFileName) {
|
||||||
|
if (!isConnected()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (runCheckedSync([adbPath, 'shell', 'pm', 'path', appPackageID]) == '') {
|
||||||
|
_logging.info(
|
||||||
|
'TODO(iansf): move this log to the caller. $appFileName is not on the device. Installing now...');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (_getDeviceApkSha1(appPackageID, appFileName) !=
|
||||||
|
_getSourceSha1(appPath)) {
|
||||||
|
_logging.info(
|
||||||
|
'TODO(iansf): move this log to the caller. $appFileName is out of date. Installing now...');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool installApp(String appPath, String appPackageID, String appFileName) {
|
||||||
|
if (!isConnected()) {
|
||||||
|
_logging.info('Android device not connected. Not installing.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!FileSystemEntity.isFileSync(appPath)) {
|
||||||
|
_logging.severe('"$appPath" does not exist.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
runCheckedSync([adbPath, 'install', '-r', appPath]);
|
||||||
|
|
||||||
|
Directory tempDir = Directory.systemTemp;
|
||||||
|
String sha1Path = path.join(
|
||||||
|
tempDir.path, appPath.replaceAll(path.separator, '_'), '.sha1');
|
||||||
|
File sha1TempFile = new File(sha1Path);
|
||||||
|
sha1TempFile.writeAsStringSync(_getSourceSha1(appPath), flush: true);
|
||||||
|
runCheckedSync([
|
||||||
|
adbPath,
|
||||||
|
'push',
|
||||||
|
sha1Path,
|
||||||
|
_getDeviceSha1Path(appPackageID, appFileName)
|
||||||
|
]);
|
||||||
|
sha1TempFile.deleteSync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isConnected() => _hasValidAndroid;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,8 @@ import 'common.dart';
|
|||||||
import 'device.dart';
|
import 'device.dart';
|
||||||
|
|
||||||
class InstallCommandHandler extends CommandHandler {
|
class InstallCommandHandler extends CommandHandler {
|
||||||
InstallCommandHandler()
|
AndroidDevice android = null;
|
||||||
|
InstallCommandHandler([this.android])
|
||||||
: super('install', 'Install your Sky app on attached devices.');
|
: super('install', 'Install your Sky app on attached devices.');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -32,9 +33,11 @@ class InstallCommandHandler extends CommandHandler {
|
|||||||
|
|
||||||
bool installedSomewhere = false;
|
bool installedSomewhere = false;
|
||||||
|
|
||||||
AndroidDevice android = new AndroidDevice();
|
if (android == null) {
|
||||||
|
android = new AndroidDevice();
|
||||||
|
}
|
||||||
if (android.isConnected()) {
|
if (android.isConnected()) {
|
||||||
installedSomewhere = installedSomewhere || android.installApp('');
|
installedSomewhere = installedSomewhere || android.installApp('', '', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (installedSomewhere) {
|
if (installedSomewhere) {
|
||||||
|
@ -14,10 +14,14 @@ main() => defineTests();
|
|||||||
|
|
||||||
defineTests() {
|
defineTests() {
|
||||||
group('install', () {
|
group('install', () {
|
||||||
test('install returns 0', () {
|
test('returns 0 when Android is connected and ready for an install', () {
|
||||||
|
MockAndroidDevice android = new MockAndroidDevice();
|
||||||
|
when(android.isConnected()).thenReturn(true);
|
||||||
|
when(android.installApp(any, any, any)).thenReturn(true);
|
||||||
|
InstallCommandHandler handler = new InstallCommandHandler(android);
|
||||||
|
|
||||||
MockArgResults results = new MockArgResults();
|
MockArgResults results = new MockArgResults();
|
||||||
when(results['help']).thenReturn(false);
|
when(results['help']).thenReturn(false);
|
||||||
InstallCommandHandler handler = new InstallCommandHandler();
|
|
||||||
handler
|
handler
|
||||||
.processArgResults(results)
|
.processArgResults(results)
|
||||||
.then((int code) => expect(code, equals(0)));
|
.then((int code) => expect(code, equals(0)));
|
||||||
|
@ -4,9 +4,14 @@
|
|||||||
|
|
||||||
import 'package:args/args.dart';
|
import 'package:args/args.dart';
|
||||||
import 'package:mockito/mockito.dart';
|
import 'package:mockito/mockito.dart';
|
||||||
|
import 'package:sky_tools/src/device.dart';
|
||||||
|
|
||||||
@proxy
|
|
||||||
class MockArgResults extends Mock implements ArgResults {
|
class MockArgResults extends Mock implements ArgResults {
|
||||||
@override
|
@override
|
||||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MockAndroidDevice extends Mock implements AndroidDevice {
|
||||||
|
@override
|
||||||
|
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user