SimpleInstaller
Easy to use Android package installer wrapper leveraging Kotlin coroutines (API 16+). Newer alternative: https://github.com/solrudev/Ackpine
Install / Use
/learn @solrudev/SimpleInstallerREADME
SimpleInstaller
Deprecated!
SimpleInstaller is deprecated in favor of Ackpine, a robust package installer library with more rich API. SimpleInstaller won't receive updates anymore.
Contents
Overview
SimpleInstaller is an Android library which provides wrapper over Android packages install and uninstall functionality leveraging Kotlin coroutines.
It supports Android versions starting from API 16. Split packages installation is also supported (note that this is only available on Android versions starting from API 21).
SimpleInstaller was developed with deferred execution in mind. You can launch an install or uninstall session when user is not interacting with your app directly, for example, while foreground service is running and your application was removed from recents. The way it works is that the user is shown a high-priority notification which launches a standard Android confirmation by clicking on it.
Note: SimpleInstaller does not support process death scenario.
Gradle
All versions are available here.
implementation("io.github.solrudev:simpleinstaller:5.0.0")
Usage
Permissions
If your application relies on WRITE_EXTERNAL_STORAGE permission, change your permission's
declaration in application's AndroidManifest.xml to this:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:remove="android:maxSdkVersion" />
Here we are saying to manifest merger that we don't want to include maxSdkVersion attribute which
is declared in SimpleInstaller's manifest. SimpleInstaller needs this permission only on API levels
< 21.
If you're targeting API level 33, you also need to request POST_NOTIFICATIONS runtime permission,
so SimpleInstaller can work with DEFERRED confirmation strategy.
Install permission
On Oreo and higher PackageInstaller sets an install reason PackageManager.INSTALL_REASON_USER,
so on first install there should be a prompt from Android to allow installation. But relying on this
is not recommended, because your process may be restarted if user chooses Always allow, so the
result and progress won't be received anymore. There's InstallPermissionContract in
activityresult package which you should use to request user to turn on install from unknown
sources for your app.
In your Activity:
val requestInstallPermissionLauncher = registerForActivityResult(InstallPermissionContract()) { isGranted ->
if (isGranted) { /* do something */ }
}
...
requestInstallPermissionLauncher.launch(Unit)
// or using `launch()` extension from androidx.activity
requestInstallPermissionLauncher.launch()
Installation
Installation functionality is provided by PackageInstaller interface.
SimpleInstaller has out-of-the-box support for Uri (URIs must have file: or content: scheme),
AssetFileDescriptor and File. It is possible to subclass ApkSource and pass it to
PackageInstaller.
// for split packages: packageInstaller.installSplitPackage(apk1, apk2, apk3) { ... }
val result = packageInstaller.installPackage(apk) {
confirmationStrategy = ConfirmationStrategy.DEFERRED
notification {
title = "Notification title"
contentText = "Notification text"
icon = R.drawable.icon
}
}
when (result) {
InstallResult.Success -> println("Install succeeded.")
is InstallResult.Failure -> println(result.cause)
}
Here DSL block is an extension on SessionOptions.Builder.
To obtain PackageInstaller instance in Kotlin one can just treat it as a singleton object
because its companion object implements PackageInstaller interface. For example:
val result = PackageInstaller.installPackage(apk)
val packageInstallerInstance = PackageInstaller
You can get if PackageInstaller has an active session through a property:
val hasActiveSession: Boolean
Progress updates can be collected from progress SharedFlow property:
val progress: SharedFlow<ProgressData>
</details>
<details>
<summary>Java</summary>
// for split packages: packageInstaller.installSplitPackage(apkSourceArray, sessionOptions, callback)
UriApkSource apkSource = new UriApkSource(apkUri);
// you can provide your own SessionOptions instead of SessionOptions.DEFAULT
packageInstaller.installPackage(apkSource, SessionOptions.DEFAULT, new PackageInstaller.Callback() {
@Override
public void onSuccess() {}
@Override
public void onFailure(@Nullable InstallFailureCause cause) {}
@Override
public void onException(@NonNull Throwable exception) {}
@Override
public void onCanceled() {}
@Override
public void onProgressChanged(@NonNull ProgressData progress) {}
});
Callback interface methods are empty default methods, so you are not forced to always implement all of them.
As you may notice, callback instance is held on until session is completed, so don't reference any short-lived objects such as Views or Activities in callback implementation to prevent possible memory leaks. Usage directly from UI layer is hereby discouraged.
To obtain an instance of PackageInstaller use static getInstance() method:
PackageInstaller packageInstaller = PackageInstaller.getInstance();
You can get if PackageInstaller has an active session through a getter method:
public boolean getHasActiveSession();
Also it's possible to cancel current install session:
public void cancel();
</details>
ApkSource
SimpleInstaller provides an abstract ApkSource class with the following public interface:
val progress: SharedFlow<ProgressData>
open val file: File
abstract suspend fun getUri(): Uri
open fun openAssetFileDescriptor(signal: CancellationSignal): AssetFileDescriptor?
open fun clearTempFiles()
You can provide your own implementation and pass it to PackageInstaller's installPackage() or
installSplitPackage().
Uninstallation
Uninstallation functionality is provided by PackageUninstaller interface.
val result = packageUninstaller.uninstallPackage(packageName) {
confirmationStrategy = ConfirmationStrategy.DEFERRED
notification {
title = "Notification title"
contentText = "Notification text"
icon = R.drawable.icon
}
}
if (result) {
println("Uninstall succeeded.")
}
Here DSL block is an extension on SessionOptions.Builder.
To obtain PackageUninstaller instance in Kotlin one can just treat it as a singleton object
because its companion object implements PackageUninstaller interface. For example:
val result = PackageUninstaller.uninstallPackage(packageName)
val packageUninstallerInstance = PackageUninstaller
You can get if PackageUninstaller has an active session through a property:
val hasActiveSession: Boolean
</details>
<details>
<summary>Java</summary>
// you can provide your own SessionOptions instead of SessionOptions.DEFAULT
packageUninstaller.uninstallPackage(packageName, SessionOptions.DEFAULT, new PackageUninstaller.Callback() {
@Override
public void onFinished(boolean success) {}
@Override
public void onException(@NonNull Throwable exception) {}
@Override
public void onCanceled() {}
});
Callback interface methods are empty default methods, so you are not forced to always implement all of them.
As you may notice, callback instance is held on until session is completed, so don't reference any short-lived objects such as Views or Activities in callback implementation to prevent possible memory leaks. Usage directly from UI layer is hereby discouraged.
To obtain an instance of PackageUninstaller use static getInstance() method:
PackageUninstaller packageUninstaller = PackageUninstaller.getInstance();
You can get if PackageUninstaller has an active session through a getter method:
public boolean getHasActiveSession();
Also it's possible to cancel current uninstall session:
public void cancel();
</details>
Customization options
Install or uninstall session may be customized with SessionOptions. It allows to set notification
data and different strategies for handling user's confirmation.
Default value can be retrieved from SessionOptions.DEFAULT static field.
A new instance may be constructed in a following way:
<details open> <summary>Kotlin</summary>val sessionOptions = SessionOptions {
confirmationStrategy = ConfirmationStrategy.DEFERRED
notificationData = notificationDataInstance
// It's also possible to use `notification` DSL function.
}
</details>
<details>
<summary>Java</summary>
SessionOptions sessionOptions = new SessionOpt
