SimpleInstaller is deprecated in favor of Ackpine, a robust package installer library with more rich API. SimpleInstaller won't receive updates anymore.
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.
All versions are available here.
implementation("io.github.solrudev:simpleinstaller:5.0.0")
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.
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 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
.
Kotlin
// 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>
Java
// 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();
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 functionality is provided by PackageUninstaller
interface.
Kotlin
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
Java
// 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();
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:
Kotlin
val sessionOptions = SessionOptions {
confirmationStrategy = ConfirmationStrategy.DEFERRED
notificationData = notificationDataInstance
// It's also possible to use `notification` DSL function.
}
Java
SessionOptions sessionOptions = new SessionOptions.Builder()
.setConfirmationStrategy(ConfirmationStrategy.DEFERRED)
.setNotificationData(notificationDataInstance)
.build();
A strategy for handling user's confirmation of installation or uninstallation.
Can be DEFERRED
(used by default) or IMMEDIATE
.
DEFERRED
(default) — user will be shown a high-priority notification which will launch
confirmation activity.
IMMEDIATE
— user will be prompted to confirm installation or uninstallation right away. Suitable
for launching session directly from the UI when app is in foreground.
It is possible to provide notification title, text and icon.
Note: any options for notification will be ignored if ConfirmationStrategy
is set to IMMEDIATE
.
If title/text is empty (they are by default), default value is used when notification is displayed.
android.R.drawable.ic_dialog_alert
is used as a default icon.
Default value can be retrieved from NotificationData.DEFAULT
static field.
A new instance may be constructed in a following way:
Kotlin
val notificationData = NotificationData {
title = "Title"
contentText = "Text"
icon = R.drawable.icon
}
Java
NotificationData notificationData = new NotificationData.Builder()
.setTitle("Title")
.setContentText("Text")
.setIcon(R.drawable.icon)
.build();
PackageInstaller
and PackageUninstaller
are interfaces, so you can provide your own fake
implementation for tests. For example, you could create an implementation of PackageInstaller
which will always return InstallResult.Failure
with InstallFailureCause.Storage
cause:
class FailingPackageInstaller : PackageInstaller {
override val hasActiveSession = false
override val progress = MutableSharedFlow<ProgressData>()
private val result = InstallResult.Failure(
InstallFailureCause.Storage("Insufficient storage.")
)
override suspend fun installSplitPackage(vararg apkFiles: ApkSource, options: SessionOptions) = result
override fun installSplitPackage(vararg apkFiles: ApkSource, options: SessionOptions, callback: PackageInstaller.Callback) {
callback.onFailure(result.cause)
}
override suspend fun installPackage(apkFile: ApkSource, options: SessionOptions) = result
override fun installPackage(apkFile: ApkSource, options: SessionOptions, callback: PackageInstaller.Callback) {
callback.onFailure(result.cause)
}
override fun cancel() {}
}
There's a simple sample app available. It can install chosen APK file and uninstall an application selected from the installed apps list. Go here to see sources.
Copyright © 2021-2023 Ilya Fomichev
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.