-
-
Notifications
You must be signed in to change notification settings - Fork 271
feat(details): Implement open, uninstall, and downgrade functionality #253
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
Changes from all commits
26967f1
7c49347
1106543
17f0346
b08bf7e
387c745
bc34ded
04a57db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,18 +1,16 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android"> | ||
| <manifest xmlns:tools="http://schemas.android.com/tools" | ||
| xmlns:android="http://schemas.android.com/apk/res/android"> | ||
|
|
||
| <uses-permission android:name="android.permission.INTERNET" /> | ||
| <!-- Needed to prompt install of APKs from our app --> | ||
| <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> | ||
| <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" /> | ||
| <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> | ||
|
|
||
| <queries> | ||
| <package android:name="zed.rainxch.githubstore" /> | ||
| <intent> | ||
| <action android:name="android.intent.action.MAIN" /> | ||
| </intent> | ||
| </queries> | ||
|
|
||
| <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" | ||
| tools:ignore="PackageVisibilityPolicy,QueryAllPackagesPermission" /> | ||
|
|
||
| <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" | ||
| tools:ignore="RequestInstallPackagesPolicy" /> | ||
| <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" /> | ||
|
|
||
| <application | ||
| android:name=".app.GithubStoreApp" | ||
| android:allowBackup="true" | ||
|
|
@@ -24,7 +22,6 @@ | |
| android:theme="@android:style/Theme.Material.Light.NoActionBar" | ||
| android:usesCleartextTraffic="false"> | ||
|
|
||
| <!-- Expose cache files via FileProvider for APK install --> | ||
| <activity | ||
| android:name=".MainActivity" | ||
| android:exported="true" | ||
|
|
@@ -35,7 +32,6 @@ | |
| <category android:name="android.intent.category.LAUNCHER" /> | ||
| </intent-filter> | ||
|
|
||
| <!-- Auth callback (existing) --> | ||
| <intent-filter> | ||
| <action android:name="android.intent.action.VIEW" /> | ||
|
|
||
|
|
@@ -47,7 +43,6 @@ | |
| android:scheme="githubstore" /> | ||
| </intent-filter> | ||
|
|
||
| <!-- Custom scheme: githubstore://repo/{owner}/{repo} --> | ||
| <intent-filter> | ||
| <action android:name="android.intent.action.VIEW" /> | ||
|
|
||
|
|
@@ -72,8 +67,7 @@ | |
| android:scheme="https" /> | ||
| </intent-filter> | ||
|
|
||
| <!-- App website links: https://github-store.org/app/?repo={owner}/{repo} --> | ||
| <intent-filter> | ||
| <intent-filter android:autoVerify="true"> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: What
|
||
| <action android:name="android.intent.action.VIEW" /> | ||
|
|
||
| <category android:name="android.intent.category.DEFAULT" /> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| package zed.rainxch.core.data.services | ||
|
|
||
| import android.content.BroadcastReceiver | ||
| import android.content.Context | ||
| import android.content.Intent | ||
| import android.content.IntentFilter | ||
| import co.touchlab.kermit.Logger | ||
| import kotlinx.coroutines.CoroutineScope | ||
| import kotlinx.coroutines.Dispatchers | ||
| import kotlinx.coroutines.SupervisorJob | ||
| import kotlinx.coroutines.launch | ||
| import zed.rainxch.core.domain.repository.InstalledAppsRepository | ||
| import zed.rainxch.core.domain.system.PackageMonitor | ||
|
|
||
| /** | ||
| * Listens to system package install/uninstall/replace broadcasts. | ||
| * When a tracked package is installed or updated, it resolves the pending | ||
| * install flag and updates version info from the system PackageManager. | ||
| * When a tracked package is removed, it deletes the record from the database. | ||
| */ | ||
| class PackageEventReceiver( | ||
| private val installedAppsRepository: InstalledAppsRepository, | ||
| private val packageMonitor: PackageMonitor | ||
| ) : BroadcastReceiver() { | ||
|
|
||
| private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) | ||
|
|
||
| override fun onReceive(context: Context?, intent: Intent?) { | ||
| val packageName = intent?.data?.schemeSpecificPart ?: return | ||
|
|
||
| Logger.d { "PackageEventReceiver: ${intent.action} for $packageName" } | ||
|
|
||
| when (intent.action) { | ||
| Intent.ACTION_PACKAGE_ADDED, | ||
| Intent.ACTION_PACKAGE_REPLACED -> { | ||
| scope.launch { onPackageInstalled(packageName) } | ||
| } | ||
|
|
||
| Intent.ACTION_PACKAGE_FULLY_REMOVED -> { | ||
| scope.launch { onPackageRemoved(packageName) } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private suspend fun onPackageInstalled(packageName: String) { | ||
| try { | ||
| val app = installedAppsRepository.getAppByPackage(packageName) ?: return | ||
|
|
||
| if (app.isPendingInstall) { | ||
| val systemInfo = packageMonitor.getInstalledPackageInfo(packageName) | ||
| if (systemInfo != null) { | ||
| installedAppsRepository.updateApp( | ||
| app.copy( | ||
| isPendingInstall = false, | ||
| isUpdateAvailable = false, | ||
| installedVersionName = systemInfo.versionName, | ||
| installedVersionCode = systemInfo.versionCode, | ||
| latestVersionName = systemInfo.versionName, | ||
| latestVersionCode = systemInfo.versionCode | ||
| ) | ||
| ) | ||
| Logger.i { "Resolved pending install via broadcast: $packageName (v${systemInfo.versionName})" } | ||
| } else { | ||
| installedAppsRepository.updatePendingStatus(packageName, false) | ||
| Logger.i { "Resolved pending install via broadcast (no system info): $packageName" } | ||
| } | ||
| } else { | ||
| val systemInfo = packageMonitor.getInstalledPackageInfo(packageName) | ||
| if (systemInfo != null) { | ||
| installedAppsRepository.updateApp( | ||
| app.copy( | ||
| installedVersionName = systemInfo.versionName, | ||
| installedVersionCode = systemInfo.versionCode | ||
| ) | ||
| ) | ||
| Logger.d { "Updated version info via broadcast: $packageName (v${systemInfo.versionName})" } | ||
| } | ||
| } | ||
| } catch (e: Exception) { | ||
| Logger.e { "PackageEventReceiver error for $packageName: ${e.message}" } | ||
| } | ||
| } | ||
|
|
||
| private suspend fun onPackageRemoved(packageName: String) { | ||
| try { | ||
| val app = installedAppsRepository.getAppByPackage(packageName) ?: return | ||
| installedAppsRepository.deleteInstalledApp(packageName) | ||
| Logger.i { "Removed uninstalled app via broadcast: $packageName" } | ||
| } catch (e: Exception) { | ||
| Logger.e { "PackageEventReceiver remove error for $packageName: ${e.message}" } | ||
| } | ||
| } | ||
|
|
||
| companion object { | ||
| fun createIntentFilter(): IntentFilter { | ||
| return IntentFilter().apply { | ||
| addAction(Intent.ACTION_PACKAGE_ADDED) | ||
| addAction(Intent.ACTION_PACKAGE_REPLACED) | ||
| addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED) | ||
| addDataScheme("package") | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
REQUEST_INSTALL_PACKAGESalso requires a Google Play policy declaration —tools:ignoreis insufficient.REQUEST_INSTALL_PACKAGESis a restricted permission on Google Play. Similar toQUERY_ALL_PACKAGES, suppressing the lint warning withtools:ignore="RequestInstallPackagesPolicy"does not satisfy the Play Store policy requirement. Apps requesting this permission must qualify under an approved use case (e.g., acting as an app store) and must submit a Permissions Declaration Form. While this app's purpose justifies the use case, the declaration must still be submitted to avoid rejection.🤖 Prompt for AI Agents