Switched to synchronous install plugin

This commit is contained in:
Imran Remtulla
2023-04-30 02:23:53 -04:00
parent 08aa04f812
commit 8b123acdcd
8 changed files with 81 additions and 89 deletions

View File

@@ -34,7 +34,6 @@ Currently supported App sources:
height="80">](https://apt.izzysoft.de/fdroid/index/apk/dev.imranr.obtainium)
## Limitations
- App installs happen asynchronously and the success/failure of an install cannot be determined directly. This results in install statuses and versions sometimes being out of sync with the OS until the next launch or until the problem is manually corrected.
- Auto (unattended) updates are unsupported due to a lack of any capable Flutter plugin.
- For some sources, data is gathered using Web scraping and can easily break due to changes in website design. In such cases, more reliable methods may be unavailable.

View File

@@ -25,6 +25,11 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action
android:name="com.android_package_installer.content.SESSION_API_PACKAGE_INSTALLED"
android:exported="false"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
@@ -46,9 +51,18 @@
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="dev.imranr.obtainium"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
</application>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>

View File

@@ -2,4 +2,5 @@
<paths>
<external-path path="Android/data/dev.imranr.obtainium/" name="files_root" />
<external-path path="." name="external_storage_root" />
<external-path name="external_files" path="."/>
</paths>

View File

@@ -1,3 +1,4 @@
import 'package:android_package_installer/android_package_installer.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:obtainium/providers/logs_provider.dart';
@@ -44,6 +45,11 @@ class DowngradeError extends ObtainiumError {
DowngradeError() : super(tr('cantInstallOlderVersion'));
}
class InstallError extends ObtainiumError {
InstallError(int code)
: super(PackageInstallerStatus.byCode(code).name.substring(7));
}
class IDChangedError extends ObtainiumError {
IDChangedError() : super(tr('appIdMismatch'));
}

View File

@@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart';
// ignore: implementation_imports
import 'package:easy_localization/src/localization.dart';
const String currentVersion = '0.12.0';
const String currentVersion = '0.12.1';
const String currentReleaseTag =
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES

View File

@@ -6,11 +6,11 @@ import 'dart:convert';
import 'dart:io';
import 'package:android_intent_plus/flag.dart';
import 'package:android_package_installer/android_package_installer.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:install_plugin_v2/install_plugin_v2.dart';
import 'package:installed_apps/app_info.dart';
import 'package:installed_apps/installed_apps.dart';
import 'package:obtainium/components/generated_form.dart';
@@ -268,7 +268,8 @@ class AppsProvider with ChangeNotifier {
// So we only know that the install prompt was shown, but the user could still cancel w/o us knowing
// If appropriate criteria are met, the update (never a fresh install) happens silently in the background
// But even then, we don't know if it actually succeeded
Future<void> installApk(DownloadedApk file) async {
Future<void> installApk(DownloadedApk file, {bool silent = false}) async {
// TODO: Use 'silent' when/if ever possible
var newInfo = await PackageArchiveInfo.fromPath(file.file.path);
AppInfo? appInfo;
try {
@@ -281,16 +282,15 @@ class AppsProvider with ChangeNotifier {
!(await canDowngradeApps())) {
throw DowngradeError();
}
await InstallPlugin.installApk(file.file.path, obtainiumId);
if (file.appId == obtainiumId) {
// Obtainium prompt should be lowest
await Future.delayed(const Duration(milliseconds: 500));
}
int? code =
await AndroidPackageInstaller.installApk(apkFilePath: file.file.path);
if (code != null && code != 0 && code != 3) {
throw InstallError(code);
} else if (code == 0) {
apps[file.appId]!.app.installedVersion =
apps[file.appId]!.app.latestVersion;
// Don't correct install status as installation may not be done yet
await saveApps([apps[file.appId]!.app],
attemptToCorrectInstallStatus: false);
}
await saveApps([apps[file.appId]!.app]);
}
void uninstallApp(String appId) async {
@@ -395,75 +395,43 @@ class AppsProvider with ChangeNotifier {
a.installedVersion = a.latestVersion;
return a;
}).toList());
// Download APKs for all Apps to be installed
// Prepare to download+install Apps
MultiAppMultiError errors = MultiAppMultiError();
List<DownloadedApk?> downloadedFiles =
await Future.wait(appsToInstall.map((id) async {
try {
return await downloadApp(apps[id]!.app, context);
} catch (e) {
errors.add(id, e.toString());
}
return null;
}));
downloadedFiles =
downloadedFiles.where((element) => element != null).toList();
// Separate the Apps to install into silent and regular lists
List<DownloadedApk> silentUpdates = [];
List<DownloadedApk> regularInstalls = [];
for (var f in downloadedFiles) {
bool willBeSilent = await canInstallSilently(apps[f!.appId]!.app);
if (willBeSilent) {
silentUpdates.add(f);
} else {
regularInstalls.add(f);
}
}
List<String> installedIds = [];
// Move everything to the regular install list (since silent updates don't currently work)
// TODO: Remove this when silent updates work
regularInstalls.addAll(silentUpdates);
// If Obtainium is being installed, it should be the last one
List<DownloadedApk> moveObtainiumToStart(List<DownloadedApk> items) {
DownloadedApk? temp;
items.removeWhere((element) {
bool res =
element.appId == obtainiumId || element.appId == obtainiumTempId;
// Move Obtainium to the end of the line (let all other apps update first)
String? temp;
appsToInstall.removeWhere((element) {
bool res = element == obtainiumId || element == obtainiumTempId;
if (res) {
temp = element;
}
return res;
});
if (temp != null) {
items = [temp!, ...items];
}
return items;
appsToInstall = [...appsToInstall, temp!];
}
silentUpdates = moveObtainiumToStart(silentUpdates);
regularInstalls = moveObtainiumToStart(regularInstalls);
for (var id in appsToInstall) {
try {
// ignore: use_build_context_synchronously
var downloadedFile = await downloadApp(apps[id]!.app, context);
bool willBeSilent =
await canInstallSilently(apps[downloadedFile.appId]!.app);
willBeSilent = false; // TODO: Remove this when silent updates work
if (!(await settingsProvider?.getInstallPermission(enforce: false) ??
true)) {
throw ObtainiumError(tr('cancelled'));
}
// // Install silent updates (uncomment when it works - TODO)
// for (var u in silentUpdates) {
// await installApk(u, silent: true); // Would need to add silent option
// }
// Do regular installs
if (regularInstalls.isNotEmpty && context != null) {
if (!willBeSilent && context != null) {
// ignore: use_build_context_synchronously
await waitForUserToReturnToForeground(context);
for (var i in regularInstalls) {
try {
await installApk(i);
} catch (e) {
errors.add(i.appId, e.toString());
}
await installApk(downloadedFile, silent: willBeSilent);
installedIds.add(id);
} catch (e) {
errors.add(id, e.toString());
}
}
@@ -473,7 +441,7 @@ class AppsProvider with ChangeNotifier {
NotificationsProvider().cancel(UpdateNotification([]).id);
return downloadedFiles.map((e) => e!.appId).toList();
return installedIds;
}
Future<Directory> getAppsDir() async {

View File

@@ -17,6 +17,15 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.9"
android_package_installer:
dependency: "direct main"
description:
path: "."
ref: main
resolved-ref: f09c79eee5be3c60b04760143eb954a13fdd07f1
url: "https://github.com/ImranR98/android_package_installer"
source: git
version: "0.0.1"
animations:
dependency: "direct main"
description:
@@ -293,14 +302,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.2"
install_plugin_v2:
dependency: "direct main"
description:
name: install_plugin_v2
sha256: d6b014637e7a53839e9c5a254f9fd9bb8866392c6db1f16184ce17818cc2d979
url: "https://pub.dev"
source: hosted
version: "1.0.0"
installed_apps:
dependency: "direct main"
description:

View File

@@ -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.12.0+160 # When changing this, update the tag in main() accordingly
version: 0.12.1+161 # When changing this, update the tag in main() accordingly
environment:
sdk: '>=2.18.2 <3.0.0'
@@ -51,7 +51,10 @@ dependencies:
device_info_plus: ^8.0.0
file_picker: ^5.2.10
animations: ^2.0.4
install_plugin_v2: ^1.0.0
android_package_installer:
git:
url: https://github.com/ImranR98/android_package_installer
ref: main
share_plus: ^6.0.1
installed_apps: ^1.3.1
package_archive_info: ^0.1.0