`check()` had exactly one caller: a button on the sync screen. So a new build was found only by someone who went looking for one — and having to remember to go looking is the same as not being told. The operator has been doing that by hand every time. Three parts. FIND. The app checks when it comes forward, which is the moment the person is present. Rate-limited to six hours in the view model, so flicking between two apps is not a re-check, and skipped entirely on an unlinked device — updates come from a linked server and there is nothing to ask. Same ForegroundTransitions shape as AutomaticSync, for the same reason. FETCH. Finding one downloads it, so the nag is a one-tap install rather than the start of a wait. NOT over mobile data: fifty-odd megabytes is a bill nobody agreed to, so this is gated on an unmetered connection (new ACCESS_NETWORK_STATE permission — normal, no prompt). On a metered link the update is still found and still nags; Install downloads it then, which is a choice rather than a surprise. NAG. A banner on the board, under the error banners — an update is worth saying and never worth saying before a note failed to save. "Later" clears it for this sitting only: the next time the app comes forward the check finds the same build and says so again. That is the difference between a reminder and a notice you can lose. downloadAndInstall now skips the download when the background fetch already did it, so the sync screen's button and the banner's are the same action with the same name — whether the bytes are already there is this class's problem, not the person's.
143 lines
7.0 KiB
XML
143 lines
7.0 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
|
|
<!--
|
|
INTERNET is requested but nothing uses it until the user links a server.
|
|
The app is local-first: the store, capture and the whole board work with
|
|
this permission never exercised.
|
|
-->
|
|
<uses-permission android:name="android.permission.INTERNET" />
|
|
<!-- Only to answer "is this connection metered?" before the app downloads its own
|
|
update in the background. Normal permission, no prompt, no location. -->
|
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
|
|
<!--
|
|
Four more permissions are NOT declared here and still reach the merged
|
|
manifest, contributed by WorkManager for the automatic sync:
|
|
|
|
RECEIVE_BOOT_COMPLETED reschedules the periodic sync after a restart,
|
|
instead of it silently stopping until the app is
|
|
next opened by hand
|
|
ACCESS_NETWORK_STATE evaluates the "needs a network" constraint, so a
|
|
run is not attempted with no route to the server
|
|
WAKE_LOCK holds the device awake for the seconds a sync
|
|
takes, so it is not suspended mid-request
|
|
FOREGROUND_SERVICE used only for expedited work; nothing here asks
|
|
for it, and it arrives with the library
|
|
|
|
Verified against the built APK's merged manifest, not assumed. Noted here
|
|
because all four appear in the app's permission list and nothing else in
|
|
this file would explain where they came from.
|
|
-->
|
|
|
|
<!--
|
|
usesCleartextTraffic, deliberately.
|
|
|
|
Android blocks plain HTTP by default from API 28, and the core explicitly
|
|
supports a self-hosted server on a LAN — `http://192.168.1.10:8000` is a
|
|
case it has a test for. Leaving the platform default would make this app
|
|
unusable for exactly the people it is built for, with a transport error
|
|
they could do nothing about.
|
|
|
|
Scoped by the fact that the app talks to ONE host: the server the user
|
|
typed in. There is no ad SDK, no analytics, nothing else making requests.
|
|
A network-security-config would be tighter in principle, but it matches on
|
|
domains and IP literals rather than CIDR ranges, so it cannot express
|
|
"any address on my own network" — the case that actually matters here.
|
|
|
|
The trade is not made silently: the sync screen shows an unmissable
|
|
warning when the probed address is http://, BEFORE any credential field
|
|
appears. See SyncScreen.kt.
|
|
-->
|
|
<!--
|
|
Reminders.
|
|
|
|
POST_NOTIFICATIONS is a runtime permission from API 33. It is asked for in
|
|
context — the first time the app opens holding a reminder that could fire,
|
|
never at launch on an empty board, where there would be nothing to explain
|
|
why it is being asked.
|
|
|
|
SCHEDULE_EXACT_ALARM rather than USE_EXACT_ALARM. USE_EXACT_ALARM is granted
|
|
at install with no prompt, and is reserved for apps whose whole purpose is an
|
|
alarm clock or calendar; a note app claiming it would be claiming something
|
|
untrue. SCHEDULE_EXACT_ALARM is the one the person can grant or refuse, and
|
|
refusing costs precision, not the feature — see Reminders.scheduleNext.
|
|
|
|
RECEIVE_BOOT_COMPLETED already arrives via WorkManager (below), but is
|
|
declared here too because ReminderReceiver now depends on it directly. A
|
|
permission this file relies on should be visible in this file.
|
|
-->
|
|
<!--
|
|
Updating this app from the server it syncs with (M12 step 7).
|
|
|
|
REQUEST_INSTALL_PACKAGES lets the app hand an APK to the system installer at
|
|
all. It is NOT what makes an install look suspicious to on-device heuristics
|
|
— Mihon declares it too — the legacy ACTION_VIEW install intent was, and this
|
|
app uses a PackageInstaller session instead. See AppUpdate.kt and Scribe note
|
|
2437. The person must additionally grant "install unknown apps" in system
|
|
settings; the update card asks before downloading anything.
|
|
|
|
UPDATE_PACKAGES_WITHOUT_USER_ACTION (API 31+) is what removes the install
|
|
confirmation on the UPDATE path, and only there — Android will not let an app
|
|
silently put a NEW package on a device, which is correct. It also only applies
|
|
when the new build is signed with the same key as the installed one, which is
|
|
why signing had to land before any of this could work.
|
|
-->
|
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
|
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
|
|
|
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
|
|
|
<application
|
|
android:name=".ThoughtSyncApplication"
|
|
android:allowBackup="true"
|
|
android:icon="@mipmap/ic_launcher"
|
|
android:label="@string/app_name"
|
|
android:roundIcon="@mipmap/ic_launcher_round"
|
|
android:supportsRtl="true"
|
|
android:theme="@style/Theme.ThoughtSync"
|
|
android:usesCleartextTraffic="true">
|
|
<activity
|
|
android:name=".MainActivity"
|
|
android:exported="true"
|
|
android:windowSoftInputMode="adjustResize"
|
|
android:theme="@style/Theme.ThoughtSync">
|
|
<intent-filter>
|
|
<action android:name="android.intent.action.MAIN" />
|
|
<category android:name="android.intent.category.LAUNCHER" />
|
|
</intent-filter>
|
|
</activity>
|
|
|
|
<!--
|
|
Not exported: every intent that reaches it is one this app created, with
|
|
an explicit component. Exporting would let any app on the device mark
|
|
someone's reminders as done.
|
|
|
|
The two system broadcasts are the exception and need the filter, because
|
|
the system is the sender. Both exist for the same reason — pending alarms
|
|
do not survive either a reboot or an app update, so without this a phone
|
|
that restarts overnight would quietly stop reminding anyone of anything.
|
|
-->
|
|
<!--
|
|
Where the system reports what happened to an install we committed. Not
|
|
exported: the only sender is the PendingIntent this app handed to
|
|
PackageInstaller. Without it a failed install would be indistinguishable
|
|
from someone declining the dialog (Scribe #2438).
|
|
-->
|
|
<receiver
|
|
android:name=".UpdateReceiver"
|
|
android:exported="false" />
|
|
|
|
<receiver
|
|
android:name=".ReminderReceiver"
|
|
android:exported="false">
|
|
<intent-filter>
|
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
|
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
|
</intent-filter>
|
|
</receiver>
|
|
</application>
|
|
</manifest>
|