Recently Written · git

ink-update

Ink Update: tells you when Rebind, Ink Recents or Ink Dim has a new version. F-Droid, then Google Play, then GitHub.

git clone https://github.com/equwal/ink-update

Log | Files | Refs


app/src/main/java/dev/equwal/inkupdate/Checker.kt (5645 bytes)

1 // SPDX-License-Identifier: GPL-3.0-or-later
2 // Copyright (c) 2026 equwal
3 package dev.equwal.inkupdate
4 
5 import android.content.Context
6 import android.content.Intent
7 import android.content.pm.PackageManager
8 import android.net.Uri
9 
10 /**
11  * The check itself: what is installed, what the two hosts say, and what that
12  * means.
13  *
14  * Every function here makes network requests. Call them from a background
15  * thread only.
16  */
17 object Checker {
18 
19     private const val GITHUB_ACCEPT = "application/vnd.github+json"
20 
21     /** A watched app that is on the device. */
22     data class Installed(
23         val watched: Watched,
24         val versionName: String,
25         val versionCode: Long,
26         val installer: String?,
27         val byFdroid: Boolean = Decide.fromFdroidClient(installer)
28     )
29 
30     /** A row of the screen: an installed app and what the last check found. */
31     data class Row(val app: Installed, val result: Result?)
32 
33     /** The watched apps that are on the device, in the order of the list. */
34     fun installed(context: Context): List<Installed> {
35         val pm = context.packageManager
36         val out = ArrayList<Installed>()
37         for (w in Watch.ALL) {
38             val info = try {
39                 pm.getPackageInfo(w.pkg, 0)
40             } catch (e: PackageManager.NameNotFoundException) {
41                 null
42             } catch (e: Exception) {
43                 null
44             } ?: continue
45             val installer = installerOf(pm, w.pkg)
46             out.add(
47                 Installed(
48                     watched = w,
49                     versionName = info.versionName ?: "",
50                     versionCode = info.longVersionCode,
51                     installer = installer,
52                     byFdroid = fdroidClient(pm, installer)
53                 )
54             )
55         }
56         return out
57     }
58 
59     /** The rows to show, from what the store holds. This makes no request. */
60     fun rows(context: Context, store: Store): List<Row> =
61         installed(context).map { Row(it, store.result(it.watched.pkg)) }
62 
63     /**
64      * Checks every installed watched app, keeps the results, and tells the
65      * person about each new update.
66      *
67      * @return how many apps have an update.
68      */
69     fun checkAll(context: Context, store: Store): Int {
70         val apps = installed(context)
71         var updates = 0
72         var ok = true
73         for (app in apps) {
74             val r = check(app)
75             if (r is Result.Unknown) ok = false
76             store.put(app.watched.pkg, r)
77             if (r is Result.Update) {
78                 updates++
79                 // The version is marked only after the notification went out.
80                 // A person who turns notifications on later still hears about
81                 // the update at the next check.
82                 if (store.notified(app.watched.pkg) != r.versionName &&
83                     Notify.update(context, app.watched, r)
84                 ) {
85                     store.setNotified(app.watched.pkg, r.versionName)
86                 }
87             }
88         }
89         store.checked(System.currentTimeMillis(), ok)
90         return updates
91     }
92 
93     /** The check of one app: F-Droid, then the Play store, then GitHub. */
94     fun check(app: Installed): Result {
95         val w = app.watched
96         val fdroid = askFdroid(w)
97         val fdroidWins = fdroid is FdroidAnswer.Found && fdroid.versionCode > app.versionCode
98 
99         // Two cases make the GitHub request waste: F-Droid already has a newer
100         // build, and the Play store updates the app by itself.
101         val github =
102             if (fdroidWins || app.installer == Decide.PLAY_PACKAGE || app.byFdroid) GithubAnswer.None
103             else askGithub(w)
104 
105         return Decide.source(
106             app.installer, fdroid, github, app.versionCode, app.versionName, app.byFdroid
107         )
108     }
109 
110     private fun askFdroid(w: Watched): FdroidAnswer {
111         val url = w.fdroidApi() ?: return FdroidAnswer.NotThere
112         val page = w.fdroidPage() ?: return FdroidAnswer.NotThere
113         val a = Net.get(url)
114         return when {
115             a.code == 0 -> FdroidAnswer.Failed("No answer")
116             a.code == 404 -> FdroidAnswer.NotThere
117             a.code !in 200..299 -> FdroidAnswer.Failed("F-Droid said " + a.code)
118             else -> Decide.parseFdroid(a.body, page)
119         }
120     }
121 
122     private fun askGithub(w: Watched): GithubAnswer {
123         val a = Net.get(w.githubApi(), GITHUB_ACCEPT)
124         return when {
125             a.code == 0 -> GithubAnswer.Failed("No answer")
126             a.code == 403 || a.code == 429 -> GithubAnswer.Failed("Too many requests")
127             a.code !in 200..299 -> GithubAnswer.Failed("GitHub said " + a.code)
128             else -> Decide.parseGithub(a.body, w.preferAsset)
129         }
130     }
131 
132     /**
133      * True when the installer is a F-Droid client. There are many clients, and
134      * each one opens fdroidrepos:// links, so the system can tell.
135      */
136     private fun fdroidClient(pm: PackageManager, installer: String?): Boolean {
137         if (installer == null) return false
138         if (Decide.fromFdroidClient(installer)) return true
139         val view = Intent(Intent.ACTION_VIEW, Uri.parse("fdroidrepos://f-droid.org/repo"))
140             .setPackage(installer)
141         return try {
142             pm.queryIntentActivities(view, 0).isNotEmpty()
143         } catch (e: Exception) {
144             false
145         }
146     }
147 
148     /** The package that installed the app, as the system knows it. */
149     private fun installerOf(pm: PackageManager, pkg: String): String? = try {
150         pm.getInstallSourceInfo(pkg).installingPackageName
151     } catch (e: Exception) {
152         null
153     }
154 }