mirror of
https://github.com/Aviortheking/Neo-Store.git
synced 2025-04-23 19:32:16 +00:00
Rewrite DefaultInstaller to use PackageInstaller
This commit is contained in:
parent
7bd4785d80
commit
87f19ddc21
@ -9,6 +9,7 @@
|
|||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||||
|
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="com.looker.droidify.MainApplication"
|
android:name="com.looker.droidify.MainApplication"
|
||||||
@ -89,6 +90,10 @@
|
|||||||
|
|
||||||
<receiver android:name="com.looker.droidify.service.DownloadService$Receiver" />
|
<receiver android:name="com.looker.droidify.service.DownloadService$Receiver" />
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="com.looker.droidify.installer.InstallerService"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="com.looker.droidify.content.Cache$Provider"
|
android:name="com.looker.droidify.content.Cache$Provider"
|
||||||
android:authorities="${applicationId}.provider.cache"
|
android:authorities="${applicationId}.provider.cache"
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
package com.looker.droidify.installer
|
package com.looker.droidify.installer
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.content.pm.PackageInstaller.SessionParams
|
||||||
import com.looker.droidify.content.Cache
|
import com.looker.droidify.content.Cache
|
||||||
import com.looker.droidify.utility.extension.android.Android
|
import com.looker.droidify.utility.extension.android.Android
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -25,38 +26,48 @@ class DefaultInstaller(context: Context) : BaseInstaller(context) {
|
|||||||
|
|
||||||
override suspend fun uninstall(packageName: String) = mDefaultUninstaller(packageName)
|
override suspend fun uninstall(packageName: String) = mDefaultUninstaller(packageName)
|
||||||
|
|
||||||
private suspend fun mDefaultInstaller(cacheFile: File) {
|
private fun mDefaultInstaller(cacheFile: File) {
|
||||||
val (uri, flags) = if (Android.sdk(24)) {
|
val sessionInstaller = context.packageManager.packageInstaller
|
||||||
Pair(
|
val sessionParams =
|
||||||
Cache.getReleaseUri(context, cacheFile.name),
|
SessionParams(SessionParams.MODE_FULL_INSTALL)
|
||||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
||||||
)
|
if (Android.sdk(31)) {
|
||||||
} else {
|
sessionParams.setRequireUserAction(SessionParams.USER_ACTION_NOT_REQUIRED)
|
||||||
Pair(Uri.fromFile(cacheFile), 0)
|
|
||||||
}
|
}
|
||||||
// TODO Handle deprecation
|
|
||||||
@Suppress("DEPRECATION")
|
val id = sessionInstaller.createSession(sessionParams)
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
context.startActivity(
|
val session = sessionInstaller.openSession(id)
|
||||||
Intent(Intent.ACTION_INSTALL_PACKAGE)
|
|
||||||
.setDataAndType(uri, "application/vnd.android.package-archive")
|
session.use { activeSession ->
|
||||||
.setFlags(flags)
|
activeSession.openWrite("package", 0, cacheFile.length()).use { packageStream ->
|
||||||
)
|
cacheFile.inputStream().use { fileStream ->
|
||||||
|
fileStream.copyTo(packageStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val intent = Intent(context, InstallerService::class.java)
|
||||||
|
|
||||||
|
val flags = if (Android.sdk(31)) PendingIntent.FLAG_MUTABLE else 0
|
||||||
|
|
||||||
|
val pendingIntent = PendingIntent.getService(context, id, intent, flags)
|
||||||
|
|
||||||
|
session.commit(pendingIntent.intentSender)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun mDefaultUninstaller(packageName: String) {
|
private suspend fun mDefaultUninstaller(packageName: String) {
|
||||||
val uri = Uri.fromParts("package", packageName, null)
|
val sessionInstaller = context.packageManager.packageInstaller
|
||||||
val intent = Intent()
|
|
||||||
intent.data = uri
|
val intent = Intent(context, InstallerService::class.java)
|
||||||
@Suppress("DEPRECATION")
|
intent.putExtra(InstallerService.KEY_ACTION, InstallerService.ACTION_UNINSTALL)
|
||||||
if (Android.sdk(28)) {
|
|
||||||
intent.action = Intent.ACTION_DELETE
|
val flags = if (Android.sdk(31)) PendingIntent.FLAG_MUTABLE else 0
|
||||||
} else {
|
|
||||||
intent.action = Intent.ACTION_UNINSTALL_PACKAGE
|
val pendingIntent = PendingIntent.getService(context, -1, intent, flags)
|
||||||
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true)
|
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
sessionInstaller.uninstall(packageName, pendingIntent.intentSender)
|
||||||
}
|
}
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
||||||
withContext(Dispatchers.IO) { context.startActivity(intent) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,122 @@
|
|||||||
|
package com.looker.droidify.installer
|
||||||
|
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageInstaller
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.view.ContextThemeWrapper
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import com.looker.droidify.Common
|
||||||
|
import com.looker.droidify.R
|
||||||
|
import com.looker.droidify.utility.extension.android.notificationManager
|
||||||
|
import com.looker.droidify.utility.extension.resources.getColorFromAttr
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs during or after a PackageInstaller session in order to handle completion, failure, or
|
||||||
|
* interruptions requiring user intervention (e.g. "Install Unknown Apps" permission requests).
|
||||||
|
*/
|
||||||
|
class InstallerService : Service() {
|
||||||
|
companion object {
|
||||||
|
const val KEY_ACTION = "installerAction"
|
||||||
|
const val ACTION_UNINSTALL = "uninstall"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||||
|
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)
|
||||||
|
|
||||||
|
if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) {
|
||||||
|
// prompts user to enable unknown source
|
||||||
|
val promptIntent: Intent? = intent.getParcelableExtra(Intent.EXTRA_INTENT)
|
||||||
|
|
||||||
|
promptIntent?.let {
|
||||||
|
it.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
|
||||||
|
it.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, "com.android.vending")
|
||||||
|
it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
|
||||||
|
startActivity(it)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
notifyStatus(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
stopSelf()
|
||||||
|
return START_NOT_STICKY
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies user of installer outcome.
|
||||||
|
*/
|
||||||
|
private fun notifyStatus(intent: Intent) {
|
||||||
|
// unpack from intent
|
||||||
|
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)
|
||||||
|
val name = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)
|
||||||
|
val message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
|
||||||
|
val installerAction = intent.getStringExtra(KEY_ACTION)
|
||||||
|
|
||||||
|
// get application name for notifications
|
||||||
|
val appLabel = try {
|
||||||
|
if (name != null) packageManager.getApplicationLabel(
|
||||||
|
packageManager.getApplicationInfo(
|
||||||
|
name,
|
||||||
|
PackageManager.GET_META_DATA
|
||||||
|
)
|
||||||
|
) else null
|
||||||
|
} catch (_: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val notificationTag = "download-$name"
|
||||||
|
|
||||||
|
// start building
|
||||||
|
val builder = NotificationCompat
|
||||||
|
.Builder(this, Common.NOTIFICATION_CHANNEL_DOWNLOADING)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setColor(
|
||||||
|
ContextThemeWrapper(this, R.style.Theme_Main_Light)
|
||||||
|
.getColorFromAttr(android.R.attr.colorAccent).defaultColor
|
||||||
|
)
|
||||||
|
|
||||||
|
when (status) {
|
||||||
|
PackageInstaller.STATUS_SUCCESS -> {
|
||||||
|
if (installerAction == ACTION_UNINSTALL)
|
||||||
|
// remove any notification for this app
|
||||||
|
notificationManager.cancel(notificationTag, Common.NOTIFICATION_ID_DOWNLOADING)
|
||||||
|
else {
|
||||||
|
val notification = builder
|
||||||
|
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||||
|
.setContentTitle(getString(R.string.installed))
|
||||||
|
.setContentText(appLabel)
|
||||||
|
.build()
|
||||||
|
notificationManager.notify(
|
||||||
|
notificationTag,
|
||||||
|
Common.NOTIFICATION_ID_DOWNLOADING,
|
||||||
|
notification
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PackageInstaller.STATUS_FAILURE_ABORTED -> {
|
||||||
|
// do nothing if user cancels
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// problem occurred when installing/uninstalling package
|
||||||
|
val notification = builder
|
||||||
|
.setSmallIcon(android.R.drawable.stat_notify_error)
|
||||||
|
.setContentTitle(getString(R.string.unknown_error_DESC))
|
||||||
|
.setContentText(message)
|
||||||
|
.build()
|
||||||
|
notificationManager.notify(
|
||||||
|
notificationTag,
|
||||||
|
Common.NOTIFICATION_ID_DOWNLOADING,
|
||||||
|
notification
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent?): IBinder? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user