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