Merge branch 'unarchive' into 'master'

Experimental support for un-archiving apps on Android 15

See merge request fdroid/fdroidclient!1450
This commit is contained in:
Torsten Grote
2024-10-17 19:41:29 +00:00
4 changed files with 136 additions and 1 deletions

View File

@@ -496,6 +496,13 @@
<action android:name="org.fdroid.action.UPDATE_REPOS" />
</intent-filter>
</receiver>
<receiver
android:name=".receiver.UnarchivePackageReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.UNARCHIVE_PACKAGE" />
</intent-filter>
</receiver>
<service
android:name=".net.DownloaderService"

View File

@@ -0,0 +1,31 @@
package org.fdroid.fdroid.receiver
import android.annotation.TargetApi
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_UNARCHIVE_PACKAGE
import android.content.pm.PackageInstaller.EXTRA_UNARCHIVE_ALL_USERS
import android.content.pm.PackageInstaller.EXTRA_UNARCHIVE_ID
import android.content.pm.PackageInstaller.EXTRA_UNARCHIVE_PACKAGE_NAME
import android.util.Log
import org.fdroid.fdroid.work.UnarchiveWorker
private val TAG = UnarchivePackageReceiver::class.java.simpleName
@TargetApi(35)
class UnarchivePackageReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action != ACTION_UNARCHIVE_PACKAGE) {
Log.w(TAG, "Unknown action: ${intent.action}")
return
}
val packageName = intent.getStringExtra(EXTRA_UNARCHIVE_PACKAGE_NAME) ?: error("")
val unarchiveId = intent.getIntExtra(EXTRA_UNARCHIVE_ID, -1)
val allUsers = intent.getBooleanExtra(EXTRA_UNARCHIVE_ALL_USERS, false)
Log.i(TAG, "Intent received, un-archiving $packageName...")
UnarchiveWorker.updateNow(context, packageName, unarchiveId, allUsers)
}
}

View File

@@ -0,0 +1,97 @@
package org.fdroid.fdroid.work
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.content.Context
import android.content.pm.PackageInstaller.EXTRA_UNARCHIVE_ALL_USERS
import android.content.pm.PackageInstaller.EXTRA_UNARCHIVE_ID
import android.content.pm.PackageInstaller.EXTRA_UNARCHIVE_PACKAGE_NAME
import android.content.pm.PackageManager.MATCH_ARCHIVED_PACKAGES
import android.content.pm.PackageManager.PackageInfoFlags
import android.util.Log
import androidx.annotation.UiThread
import androidx.lifecycle.asFlow
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import kotlinx.coroutines.flow.first
import org.fdroid.database.DbUpdateChecker
import org.fdroid.fdroid.FDroidApp
import org.fdroid.fdroid.data.Apk
import org.fdroid.fdroid.data.App
import org.fdroid.fdroid.data.DBHelper
import org.fdroid.fdroid.installer.InstallManagerService
private val TAG = UnarchiveWorker::class.java.simpleName
@TargetApi(35)
class UnarchiveWorker(
appContext: Context,
workerParams: WorkerParameters,
) : CoroutineWorker(appContext, workerParams) {
companion object {
@UiThread
fun updateNow(context: Context, packageName: String, unarchiveId: Int, allUsers: Boolean) {
val data = Data.Builder()
.putString(EXTRA_UNARCHIVE_PACKAGE_NAME, packageName)
.putInt(EXTRA_UNARCHIVE_ID, unarchiveId)
.putBoolean(EXTRA_UNARCHIVE_ALL_USERS, allUsers)
.build()
val request = OneTimeWorkRequestBuilder<UnarchiveWorker>()
.setExpedited(RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.setInputData(data)
.build()
WorkManager.getInstance(context)
.enqueue(request)
}
}
override suspend fun doWork(): Result {
val packageName = inputData.getString(EXTRA_UNARCHIVE_PACKAGE_NAME)
?: error("No packageName")
val unarchiveId = inputData.getInt(EXTRA_UNARCHIVE_ID, -1)
val allUsers = inputData.getBoolean(EXTRA_UNARCHIVE_ALL_USERS, false)
Log.i(TAG, "$packageName $unarchiveId $allUsers")
// get archived PackageInfo
val pm = applicationContext.packageManager
@SuppressLint("WrongConstant") // not sure why MATCH_ARCHIVED_PACKAGES is considered wrong
val packageInfo =
pm.getPackageInfo(packageName, PackageInfoFlags.of(MATCH_ARCHIVED_PACKAGES))
// find suggested version for that app
val db = DBHelper.getDb(applicationContext)
val updateChecker = DbUpdateChecker(db, pm)
val appPrefs = db.getAppPrefsDao().getAppPrefs(packageName).asFlow().first()
val version = updateChecker.getSuggestedVersion(
packageName = packageName,
// TODO we could try to get the old signer (if still available) and search for the same
preferredSigner = null,
releaseChannels = appPrefs.releaseChannels,
onlyFromPreferredRepo = true,
)
// install version, if available
return if (version == null) {
Log.e(TAG, "Could not find a version to unarchive for $packageName")
Result.failure()
} else {
// get all the objects our InstallManagerService requires
val repoManager = FDroidApp.getRepoManager(applicationContext)
// repos may not have loaded yet, so we use the flow and wait for repos to be ready
val repo = repoManager.repositoriesState.first().find { it.repoId == version.repoId }
val dbApp = db.getAppDao().getApp(version.repoId, packageName)
val app = App(dbApp, packageInfo)
val apk = Apk(version, repo)
// fire off installation, should happen automatically from here on
InstallManagerService.queue(applicationContext, app, apk)
Result.success()
}
}
}

View File

@@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xms1g -Xmx2g
org.gradle.jvmargs=-Xms2g -Xmx4g
android.enableJetifier=false
android.useAndroidX=true