Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Improve installation robustness #1528

Merged
merged 24 commits into from
Dec 23, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5556a0d
feat: Improve installation robustness
TheAabedKhan Nov 27, 2023
55cf84a
Merge branch 'dev' into implement-packageinstaller-api
TheAabedKhan Dec 2, 2023
aae748e
refactor: apply changes from suggestions
TheAabedKhan Dec 2, 2023
7fdd536
Merge branch 'dev' into implement-packageinstaller-api
TheAabedKhan Dec 2, 2023
0b853c2
Update assets/i18n/en_US.json [skip ci]
oSumAtrIX Dec 2, 2023
36d2e48
feat: append patch version to the APK name while sharing/exporting (#…
dhruvanbhalara Dec 5, 2023
6a8d10e
Merge branch 'dev' into implement-packageinstaller-api
TheAabedKhan Dec 5, 2023
9e003ed
refactor: code formatting
TheAabedKhan Dec 5, 2023
c4bf940
Merge branch 'dev' into implement-packageinstaller-api
TheAabedKhan Dec 5, 2023
e03a67b
refactor: code formatting
TheAabedKhan Dec 5, 2023
fdb3a1d
refactor: default string
TheAabedKhan Dec 5, 2023
c388aab
refactor: modify strings
TheAabedKhan Dec 5, 2023
2e69aa5
refactor: apply changes from suggestion
TheAabedKhan Dec 5, 2023
be1a5da
Update assets/i18n/en_US.json
TheAabedKhan Dec 6, 2023
a812a58
refactor: apply changes from suggestion
TheAabedKhan Dec 6, 2023
63ddc1c
Update assets/i18n/en_US.json
TheAabedKhan Dec 7, 2023
11e3e89
Update lib/services/root_api.dart
TheAabedKhan Dec 9, 2023
0819376
feat: more string consistency
Ushie Dec 9, 2023
1494f17
Merge branch 'implement-packageinstaller-api' of https://github.com/t…
Ushie Dec 9, 2023
d7f9d6f
Merge branch 'dev' into implement-packageinstaller-api
validcube Dec 11, 2023
68db4a1
Merge branch 'dev' into implement-packageinstaller-api
validcube Dec 22, 2023
a9dfb84
Update assets/i18n/en_US.json
oSumAtrIX Dec 22, 2023
abd43da
Update button
validcube Dec 23, 2023
5c39cba
Update style
validcube Dec 23, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"/>
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ExportSettingsActivity"
android:exported="true">
</activity>
<activity
android:name=".ExportSettingsActivity"
android:exported="true">
</activity>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
Expand All @@ -55,5 +55,22 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>

<receiver
android:name=".utils.packageInstaller.InstallerReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="APP_INSTALL_ACTION" />
</intent-filter>
</receiver>
<receiver
android:name=".utils.packageInstaller.UninstallerReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="APP_UNINSTALL_ACTION" />
</intent-filter>
</receiver>
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package app.revanced.manager.flutter

import android.app.PendingIntent
import android.app.SearchManager
import android.content.Intent
import android.content.pm.PackageInstaller
import android.os.Build
import android.os.Handler
import android.os.Looper
import app.revanced.manager.flutter.utils.Aapt
import app.revanced.manager.flutter.utils.aligning.ZipAligner
import app.revanced.manager.flutter.utils.packageInstaller.InstallerReceiver
import app.revanced.manager.flutter.utils.packageInstaller.UninstallerReceiver
import app.revanced.manager.flutter.utils.signing.Signer
import app.revanced.manager.flutter.utils.zip.ZipFile
import app.revanced.manager.flutter.utils.zip.structures.ZipEntry
Expand Down Expand Up @@ -184,12 +189,24 @@ class MainActivity : FlutterActivity() {
}.toString().let(result::success)
}

"installApk" -> {
val apkPath = call.argument<String>("apkPath")!!
PackageInstallerManager.result = result
installApk(apkPath)
}

"uninstallApp" -> {
val packageName = call.argument<String>("packageName")!!
uninstallApp(packageName)
PackageInstallerManager.result = result
}

else -> result.notImplemented()
}
}
}

fun openBrowser(query: String?) {
private fun openBrowser(query: String?) {
val intent = Intent(Intent.ACTION_WEB_SEARCH).apply {
putExtra(SearchManager.QUERY, query)
}
Expand Down Expand Up @@ -407,4 +424,44 @@ class MainActivity : FlutterActivity() {
handler.post { result.success(null) }
}.start()
}

private fun installApk(apkPath: String) {
val packageInstaller: PackageInstaller = applicationContext.packageManager.packageInstaller
val sessionParams = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
val sessionId: Int = packageInstaller.createSession(sessionParams)
val session: PackageInstaller.Session = packageInstaller.openSession(sessionId)
session.use { activeSession ->
val sessionOutputStream = activeSession.openWrite(applicationContext.packageName, 0, -1)
sessionOutputStream.use { outputStream ->
val apkFile = File(apkPath)
apkFile.inputStream().use { inputStream ->
inputStream.copyTo(outputStream)
}
}
}
val receiverIntent = Intent(applicationContext, InstallerReceiver::class.java).apply {
action = "APP_INSTALL_ACTION"
}
val receiverPendingIntent = PendingIntent.getBroadcast(context, sessionId, receiverIntent, PackageInstallerManager.flags)
session.commit(receiverPendingIntent.intentSender)
session.close()
}

private fun uninstallApp(packageName: String) {
val packageInstaller: PackageInstaller = applicationContext.packageManager.packageInstaller
val receiverIntent = Intent(applicationContext, UninstallerReceiver::class.java).apply {
action = "APP_UNINSTALL_ACTION"
}
val receiverPendingIntent = PendingIntent.getBroadcast(context, 0, receiverIntent, PackageInstallerManager.flags)
packageInstaller.uninstall(packageName, receiverPendingIntent.intentSender)
}

object PackageInstallerManager {
var result: MethodChannel.Result? = null
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package app.revanced.manager.flutter.utils.packageInstaller

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import app.revanced.manager.flutter.MainActivity

class InstallerReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
val confirmationIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
if (confirmationIntent != null) {
context.startActivity(confirmationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
}
}

else -> {
val packageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)
val message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
val otherPackageName = intent.getStringExtra(PackageInstaller.EXTRA_OTHER_PACKAGE_NAME)
MainActivity.PackageInstallerManager.result!!.success(mapOf(
"status" to status,
"packageName" to packageName,
"message" to message,
"otherPackageName" to otherPackageName
))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package app.revanced.manager.flutter.utils.packageInstaller

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import app.revanced.manager.flutter.MainActivity

class UninstallerReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
val confirmationIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
if (confirmationIntent != null) {
context.startActivity(confirmationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
}
}

else -> {
MainActivity.PackageInstallerManager.result!!.success(status)
}
}
}
}
35 changes: 33 additions & 2 deletions assets/i18n/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,15 @@
},
"installerView": {
"widgetTitle": "Installer",
"installType": "Select install type",
"installTypeDescription": "Select the installation type to proceed with.",
"installType": "Choose installation method",
"installTypeDescription": "To install, you can either mount or install normally. Mounting will install the patched app on top of an existing installation.",
TheAabedKhan marked this conversation as resolved.
Show resolved Hide resolved

"installButton": "Install",
"installRootType": "Mount",
"installNonRootType": "Normal",

"warning": "Disable auto updates after installing the app to avoid unexpected issues.",
oSumAtrIX marked this conversation as resolved.
Show resolved Hide resolved

"pressBackAgain": "Press back again to cancel",
"openButton": "Open",
"shareButton": "Share file",
Expand Down Expand Up @@ -327,5 +329,34 @@
"integrationsContributors": "Integrations contributors",
"cliContributors": "CLI contributors",
"managerContributors": "Manager contributors"
},
"installErrorDialog": {
"mount_version_mismatch": "Version mismatch",
"mount_no_root": "No root access",
"mount_missing_installation": "No installation found",
TheAabedKhan marked this conversation as resolved.
Show resolved Hide resolved

"status_failure_blocked": "Installation blocked",
"install_failed_verification_failure": "Verification failed",
"status_failure_invalid": "Installation invalid",
"install_failed_version_downgrade": "Can't downgrade",
"status_failure_conflict": "Installation conflict",
"status_failure_storage": "Installation storage issue",
"status_failure_incompatible": "Installation incompatible",
"status_failure_timeout": "Installation timeout",
"status_unknown": "Installation failed",

"mount_version_mismatch_description": "To install by mounting the patched app, the version of the installed app must match the version of the patched app.\n\nInstall the version of the app you are mounting and try again.",
"mount_no_root_description": "To install by mounting the patched app, root access is required.\n\nGrant root access to ReVanced Manager and try again.",
TheAabedKhan marked this conversation as resolved.
Show resolved Hide resolved
"mount_missing_installation_description": "To install by mounting the patched app, the unpatched app must be installed on this device.\n\nInstall the app and try again.",

"status_failure_timeout_description": "Try again",
oSumAtrIX marked this conversation as resolved.
Show resolved Hide resolved
"status_failure_storage_description": "The app could not be installed due to insufficient storage.\n\nFree up some space and try again.",
"status_failure_invalid_description": "The app is invalid.\n\nUninstall the app and try again?",
"status_failure_incompatible_description": "The app is incompatible with this device.\n\nContact the developer of the app and ask for support.",
"status_failure_conflict_description": "An existing installation of the app prevents the installation.\n\nUninstall the app and try again?",
"status_failure_blocked_description": "The installation was blocked by {packageName}.\n\nAdjust your security settings and try again.",
"install_failed_verification_failure_description": "The app could not be installed due to a verification issue.\n\nAdjust your security settings and try again.",
"install_failed_version_downgrade_description": "The version of the installed app is newer than the version of the patched app.\n\nUninstall the current version and try again?",
oSumAtrIX marked this conversation as resolved.
Show resolved Hide resolved
"status_unknown_description": "The installation failed due to an unknown reason. Please try again."
}
}
8 changes: 8 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:revanced_manager/services/download_manager.dart';
import 'package:revanced_manager/services/github_api.dart';
import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/revanced_api.dart';
import 'package:revanced_manager/services/root_api.dart';
import 'package:revanced_manager/ui/theme/dynamic_theme_builder.dart';
import 'package:revanced_manager/ui/views/navigation/navigation_view.dart';
import 'package:shared_preferences/shared_preferences.dart';
Expand All @@ -24,6 +25,13 @@ Future main() async {
final String repoUrl = locator<ManagerAPI>().getRepoUrl();
locator<GithubAPI>().initialize(repoUrl);
tz.initializeTimeZones();

// TODO(aAbed): remove in the future, keep it for now during migration.
final rootAPI = RootAPI();
if (await rootAPI.hasRootPermissions()) {
await rootAPI.removeOrphanedFiles();
}

prefs = await SharedPreferences.getInstance();

runApp(const MyApp());
Expand Down
1 change: 0 additions & 1 deletion lib/services/download_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,3 @@ class DownloadManager {
);
}
}

Loading