diff --git a/android/app/build.gradle b/android/app/build.gradle index 09eb008..764d501 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -49,7 +49,6 @@ android { } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "dev.imranr.obtainium" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. diff --git a/lib/main.dart b/lib/main.dart index a9f0caa..efcce43 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -22,7 +22,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart'; // ignore: implementation_imports import 'package:easy_localization/src/localization.dart'; -const String currentVersion = '0.13.24'; +const String currentVersion = '0.13.25'; const String currentReleaseTag = 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index 894967e..f8ee0e4 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -643,15 +643,15 @@ class AppsPageState extends State { label: tr('installX', args: [ plural('apps', newInstallIdsAllOrSelected.length) ]), - defaultValue: existingUpdateIdsAllOrSelected.isNotEmpty)); + defaultValue: existingUpdateIdsAllOrSelected.isEmpty)); } if (trackOnlyUpdateIdsAllOrSelected.isNotEmpty) { formItems.add(GeneratedFormSwitch('trackonlies', label: tr('markXTrackOnlyAsUpdated', args: [ plural('apps', trackOnlyUpdateIdsAllOrSelected.length) ]), - defaultValue: existingUpdateIdsAllOrSelected.isNotEmpty || - newInstallIdsAllOrSelected.isNotEmpty)); + defaultValue: existingUpdateIdsAllOrSelected.isEmpty && + newInstallIdsAllOrSelected.isEmpty)); } showDialog?>( context: context, diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index ec9c104..bc03997 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -7,6 +7,7 @@ import 'dart:io'; import 'package:android_intent_plus/flag.dart'; import 'package:android_package_installer/android_package_installer.dart'; +import 'package:android_package_manager/android_package_manager.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -29,6 +30,8 @@ import 'package:http/http.dart'; import 'package:android_intent_plus/android_intent.dart'; import 'package:flutter_archive/flutter_archive.dart'; +final pm = AndroidPackageManager(); + class AppInMemory { late App app; double? downloadProgress; @@ -322,16 +325,39 @@ class AppsProvider with ChangeNotifier { .isNotEmpty; Future canInstallSilently(App app) async { - return false; - // TODO: Uncomment the below if silent updates are ever figured out - // // NOTE: This is unreliable - try to get from OS in the future - // if (app.apkUrls.length > 1) { - // return false; - // } - // var osInfo = await DeviceInfoPlugin().androidInfo; - // return app.installedVersion != null && - // osInfo.version.sdkInt >= 30 && - // osInfo.version.release.compareTo('12') >= 0; + if (app.apkUrls.length > 1) { + // Manual API selection means silent install is not possible + return false; + } + + var osInfo = await DeviceInfoPlugin().androidInfo; + String? installerPackageName; + try { + installerPackageName = osInfo.version.sdkInt >= 30 + ? (await pm.getInstallSourceInfo(packageName: app.id)) + ?.installingPackageName + : (await pm.getInstallerPackageName(packageName: app.id)); + } catch (e) { + // Probably not installed - ignore + } + if (installerPackageName != obtainiumId) { + // If we did not install the app (or it isn't installed), silent install is not possible + return false; + } + var targetSDK; + try { + targetSDK = (await pm.getPackageInfo(packageName: app.id)) + ?.applicationInfo + ?.targetSdkVersion; + } catch (e) { + // Weird if you get here - ignore + } + + // The OS must also be new enough and the APK should target a new enough API + return osInfo.version.sdkInt >= 30 && + targetSDK != null && + targetSDK >= // https://developer.android.com/reference/android/content/pm/PackageInstaller.SessionParams#setRequireUserAction(int) + (osInfo.version.sdkInt - 3); } Future waitForUserToReturnToForeground(BuildContext context) async { @@ -359,8 +385,7 @@ class AppsProvider with ChangeNotifier { zipFile: File(filePath), destinationDir: Directory(destinationPath)); } - Future installXApkDir(DownloadedXApkDir dir, - {bool silent = false}) async { + Future installXApkDir(DownloadedXApkDir dir) async { // We don't know which APKs in an XAPK are supported by the user's device // So we try installing all of them and assume success if at least one installed // If 0 APKs installed, throw the first install error encountered @@ -373,8 +398,7 @@ class AppsProvider with ChangeNotifier { if (file.path.toLowerCase().endsWith('.apk')) { try { somethingInstalled = somethingInstalled || - await installApk(DownloadedApk(dir.appId, file), - silent: silent); + await installApk(DownloadedApk(dir.appId, file)); } catch (e) { logs.add( 'Could not install APK from XAPK \'${file.path}\': ${e.toString()}'); @@ -394,8 +418,7 @@ class AppsProvider with ChangeNotifier { } } - Future installApk(DownloadedApk file, {bool silent = false}) async { - // TODO: Use 'silent' when/if ever possible + Future installApk(DownloadedApk file) async { var newInfo = await PackageArchiveInfo.fromPath(file.file.path); AppInfo? appInfo; try { @@ -571,7 +594,6 @@ class AppsProvider with ChangeNotifier { } bool willBeSilent = await canInstallSilently( apps[downloadedFile?.appId ?? downloadedDir!.appId]!.app); - willBeSilent = false; // TODO: Remove this when silent updates work if (!(await settingsProvider?.getInstallPermission(enforce: false) ?? true)) { throw ObtainiumError(tr('cancelled')); @@ -584,9 +606,9 @@ class AppsProvider with ChangeNotifier { notifyListeners(); try { if (downloadedFile != null) { - await installApk(downloadedFile, silent: willBeSilent); + await installApk(downloadedFile); } else { - await installXApkDir(downloadedDir!, silent: willBeSilent); + await installXApkDir(downloadedDir!); } } finally { apps[id]?.downloadProgress = null; diff --git a/pubspec.lock b/pubspec.lock index 41f8b1e..fc2d4e4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -26,6 +26,15 @@ packages: url: "https://github.com/ImranR98/android_package_installer" source: git version: "0.0.1" + android_package_manager: + dependency: "direct main" + description: + path: "." + ref: master + resolved-ref: "6e68991ef9c6232695abce2eef345d3cca2f52ac" + url: "https://github.com/ImranR98/android_package_manager" + source: git + version: "0.5.4" animations: dependency: "direct main" description: @@ -578,10 +587,10 @@ packages: dependency: transitive description: name: platform - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + sha256: "57c07bf82207aee366dfaa3867b3164e4f03a238a461a11b0e8a3a510d51203d" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" plugin_platform_interface: dependency: transitive description: @@ -871,10 +880,10 @@ packages: dependency: transitive description: name: webview_flutter_platform_interface - sha256: "564ef378cafc1a0e29f1d76ce175ef517a0a6115875dff7b43fccbef2b0aeb30" + sha256: "0ca3cfcc6781a7de701d580917af4a9efc4e3e129f8ead95a80587f0a749480a" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.5.0" webview_flutter_wkwebview: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b445ab3..e2e43e4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: obtainium -description: A new Flutter project. +description: Get Android App Updates Directly From the Source. # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 0.13.24+188 # When changing this, update the tag in main() accordingly +version: 0.13.25+189 # When changing this, update the tag in main() accordingly environment: sdk: '>=2.18.2 <3.0.0' @@ -55,9 +55,13 @@ dependencies: git: url: https://github.com/ImranR98/android_package_installer ref: main + android_package_manager: + git: + url: https://github.com/ImranR98/android_package_manager + ref: master share_plus: ^7.0.0 - installed_apps: ^1.3.1 - package_archive_info: ^0.1.0 + installed_apps: ^1.3.1 # TODO: Remove when android_package_manager supports getting icon as UInt8List and versionCode + package_archive_info: ^0.1.0 # TODO: Remove when android_package_manager supports getting versionCode android_alarm_manager_plus: ^3.0.0 sqflite: ^2.2.0+3 easy_localization: ^3.0.1