Most of the infrastructure needed to install an APK on Android.
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index ae43934..80f5d6b 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -40,13 +40,13 @@
_Device._(this.id);
/// Install an app package on the current device
- bool installApp(String path);
-
- /// Check if the current device needs an installation
- bool needsInstall();
+ bool installApp(String appPath, String appPackageID, String appFileName);
/// Check if the device is currently connected
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 {
@@ -57,54 +57,44 @@
String _adbPath;
String get adbPath => _adbPath;
+ bool _hasAdb = false;
+ bool _hasValidAndroid = false;
factory AndroidDevice([String id = null]) {
return new _Device(className, id);
}
AndroidDevice._(id) : super._(id) {
- _updatePaths();
+ _adbPath = _getAdbPath();
+ _hasAdb = _checkForAdb();
// 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
// progress messages if the adb server is restarted.
- if (!_checkForAdb() || !_checkForLollipopOrLater()) {
+ _hasValidAndroid = _checkForLollipopOrLater();
+
+ if (!_hasAdb || !_hasValidAndroid) {
_logging.severe('Unable to run on Android.');
}
}
- @override
- bool installApp(String path) {
- return true;
- }
-
- @override
- bool needsInstall() {
- return true;
- }
-
- @override
- bool isConnected() {
- return true;
- }
-
- void _updatePaths() {
+ String _getAdbPath() {
if (Platform.environment.containsKey('ANDROID_HOME')) {
String androidHomeDir = Platform.environment['ANDROID_HOME'];
String adbPath1 =
path.join(androidHomeDir, 'sdk', 'platform-tools', 'adb');
String adbPath2 = path.join(androidHomeDir, 'platform-tools', 'adb');
if (FileSystemEntity.isFileSync(adbPath1)) {
- _adbPath = adbPath1;
+ return adbPath1;
} else if (FileSystemEntity.isFileSync(adbPath2)) {
- _adbPath = adbPath2;
+ return adbPath2;
} else {
_logging.info('"adb" not found at\n "$adbPath1" or\n "$adbPath2"\n' +
'using default path "$_ADB_PATH"');
- _adbPath = _ADB_PATH;
+ return _ADB_PATH;
}
} else {
- _adbPath = _ADB_PATH;
+ return _ADB_PATH;
}
}
@@ -184,4 +174,74 @@
}
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;
+ }
}
diff --git a/packages/flutter_tools/lib/src/install.dart b/packages/flutter_tools/lib/src/install.dart
index 1a89dee..d0b30d6 100644
--- a/packages/flutter_tools/lib/src/install.dart
+++ b/packages/flutter_tools/lib/src/install.dart
@@ -12,7 +12,8 @@
import 'device.dart';
class InstallCommandHandler extends CommandHandler {
- InstallCommandHandler()
+ AndroidDevice android = null;
+ InstallCommandHandler([this.android])
: super('install', 'Install your Sky app on attached devices.');
@override
@@ -32,9 +33,11 @@
bool installedSomewhere = false;
- AndroidDevice android = new AndroidDevice();
+ if (android == null) {
+ android = new AndroidDevice();
+ }
if (android.isConnected()) {
- installedSomewhere = installedSomewhere || android.installApp('');
+ installedSomewhere = installedSomewhere || android.installApp('', '', '');
}
if (installedSomewhere) {