Skip to content

Commit 68a114c

Browse files
committed
add patch level expiry warning popup & notification
Implemented inside of SystemUI as a periodic JobService with a popup and notification. Set `DEBUG_ALWAYS_EXPIRED` to true to test it. Pair with corresponding packages/apps/Settings commit Test: atest KeyguardViewMediatorTest (choose A for all)
1 parent 896bd26 commit 68a114c

File tree

13 files changed

+1334
-4
lines changed

13 files changed

+1334
-4
lines changed

core/java/android/ext/settings/ExtSettings.java

+6
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ public class ExtSettings {
9393
public static final BoolSetting SHOW_SYSTEM_PROCESS_CRASH_NOTIFICATIONS = new BoolSetting(
9494
Setting.Scope.GLOBAL, Settings.Global.SHOW_SYSTEM_PROCESS_CRASH_NOTIFICATIONS, false);
9595

96+
public static final BoolSetting USER_DISABLE_PATCH_LEVEL_EXPIRY_WARNING = new BoolSetting(
97+
Setting.Scope.GLOBAL, Settings.Global.PATCH_LEVEL_WARNING_DISABLED, false);
98+
99+
public static final BoolSysProperty DEVICE_DISABLED_PATCH_LEVEL_EXPIRY_WARNING = new BoolSysProperty(
100+
"ro.disable_patch_level_expiry_warning", false);
101+
96102
private ExtSettings() {}
97103

98104
public static Function<Context, Boolean> defaultBool(@BoolRes int res) {

core/java/android/provider/Settings.java

+7
Original file line numberDiff line numberDiff line change
@@ -18160,6 +18160,13 @@ public static final class Global extends NameValueTable {
1816018160
*/
1816118161
public static final String ONE_HANDED_KEYGUARD_SIDE = "one_handed_keyguard_side";
1816218162

18163+
/**
18164+
* Whether patch level expiry popup & notification warning is disabled.
18165+
*
18166+
* @hide
18167+
*/
18168+
public static final String PATCH_LEVEL_WARNING_DISABLED = "grapheneos_patch_level_warning_disabled";
18169+
1816318170
/**
1816418171
* Global settings that shouldn't be persisted.
1816518172
*

packages/SystemUI/AndroidManifest.xml

+4
Original file line numberDiff line numberDiff line change
@@ -1151,5 +1151,9 @@
11511151
android:showForAllUsers="true"
11521152
android:theme="@style/ShortcutHelperTheme"
11531153
android:excludeFromRecents="true" />
1154+
1155+
<service android:name=".patchlevelwarning.PeriodicPatchLevelExpiryCheck"
1156+
android:exported="false"
1157+
android:permission="android.permission.BIND_JOB_SERVICE"/>
11541158
</application>
11551159
</manifest>

packages/SystemUI/res/values/strings.xml

+25
Original file line numberDiff line numberDiff line change
@@ -3866,4 +3866,29 @@ Action + ESC for this.</string>
38663866
<string name="qs_edit_mode_category_unknown">
38673867
Unknown
38683868
</string>
3869+
3870+
<string name="patch_level_expiry_warning_notification_title">Patch level warning</string>
3871+
<string name="patch_level_expiry_warning_notification_tap_to_learn_more">Tap to learn more about keeping your device up-to-date and safe.</string>
3872+
<string name="patch_level_expiry_warning_dialog_title_updater_enabled">Check for new updates?</string>
3873+
<string name="patch_level_expiry_warning_dialog_title_updater_disabled">Enable automatic updates?</string>
3874+
<string name="patch_level_expiry_warning_dialog_title_updater_missing">System updater missing</string>
3875+
<string name="patch_level_expiry_warning_dialog_message_months_since_patch_level">{months, plural,
3876+
=1 {Your device\'s Android security patch level is {device_patch_level}, which is over # month old. Security updates are released every month.}
3877+
other {Your device\'s Android security patch level is {device_patch_level}, which is over # months old. Security updates are released every month.}
3878+
}</string>
3879+
<string name="patch_level_expiry_warning_dialog_message_debug__s">DEBUG_ALWAYS_EXPIRED is on; actual patch level is %1$s</string>
3880+
<string name="patch_level_expiry_warning_dialog_message_updater_enabled">Your device might not be checking for updates recently due to your settings. To stay secure, it\'s strongly recommended to manually check for updates and adjust your settings if needed to allow for automatic update checks.</string>
3881+
<string name="patch_level_expiry_warning_dialog_message_updater_disabled">Automatic system updates are currently disabled, leaving your device vulnerable. It\'s strongly recommended to enable automatic updates to stay secure.</string>
3882+
<string name="patch_level_expiry_warning_dialog_message_updater_missing">The system updater is missing from your device, so automatic updates are not possible.</string>
3883+
<string name="patch_level_expiry_warning_dialog_message_updater_missing_recommend_contacting_community_github">This is not normal, so it\'s strongly recommended to reach out to GrapheneOS community platforms and GitHub issue trackers for assistance. You can learn more about these platforms at <xliff:g id="contact_url_text" example="grapheneos.org/contact">%1$s</xliff:g></string>
3884+
<string name="patch_level_expiry_warning_dialog_message_not_system_user">Automatic system updates can only be managed by the main user.</string>
3885+
<string name="patch_level_expiry_warning_dialog_message_learn_more">Learn more about updates (including alternative update methods) and current releases at <xliff:g id="learn_more_url_text" example="grapheneos.org/usage">%1$s</xliff:g></string>
3886+
<string name="patch_level_expiry_warning_dialog_updater_unavailable_error_toast">System updater currently unavailable</string>
3887+
<string name="patch_level_expiry_warning_dialog_webpage_unavailable_error_toast">Unable to open webpage</string>
3888+
<string name="patch_level_expiry_warning_dialog_unable_to_parse_patch_level__s">The Android security patch level of your device (%1$s) could not be parsed by the system.</string>
3889+
<string name="patch_level_expiry_warning_dialog_expected_patch_level_format__s">The expected date format for the security patch level is: %1$s.</string>
3890+
<string name="patch_level_expiry_warning_dialog_unable_to_parse_patch_level_contact_community_github">Please use the community platforms and GitHub issue trackers for assistance. You can learn more about these platforms at <xliff:g id="contact_url_text" example="grapheneos.org/contact">%1$s</xliff:g></string>
3891+
<string name="patch_level_expiry_warning_dialog_updater_open_button">Check for updates</string>
3892+
<string name="patch_level_expiry_warning_dialog_updater_enable_button">Enable automatic updates</string>
3893+
<string name="patch_level_expiry_warning_dialog_tap_on_notification_to_reopen_dialog">Tap on the notification to reopen the popup.</string>
38693894
</resources>

packages/SystemUI/src/com/android/systemui/BootReceiver.java

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import android.os.ServiceManager;
1010
import android.util.Log;
1111

12+
import com.android.systemui.patchlevelwarning.PeriodicPatchLevelExpiryCheck;
13+
1214
import vendor.google.google_battery.IGoogleBattery;
1315

1416
public class BootReceiver extends BroadcastReceiver {
@@ -23,6 +25,8 @@ public void onReceive(Context context, Intent intent) {
2325
if (BatteryChargeLimit.isChargeLimitEnabled(context)) {
2426
setChargingPolicy(IGoogleBattery.BatteryChargingPolicy.LONGLIFE);
2527
}
28+
29+
PeriodicPatchLevelExpiryCheck.schedule(context, true);
2630
}
2731

2832
private static void setChargingPolicy(int policy) {

packages/SystemUI/src/com/android/systemui/SystemUIApplication.java

+4
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ public class SystemUIApplication extends Application implements
7474
private CoreStartable[] mServices;
7575
private boolean mServicesStarted;
7676
private SystemUIAppComponentFactoryBase.ContextAvailableCallback mContextAvailableCallback;
77+
78+
public SysUIComponent getSysUIComponent() {
79+
return mSysUIComponent;
80+
}
7781
private SysUIComponent mSysUIComponent;
7882
private SystemUIInitializer mInitializer;
7983
private ProcessWrapper mProcessWrapper;

packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.android.systemui.dagger.qualifiers.PerUser;
2626
import com.android.systemui.dump.DumpManager;
2727
import com.android.systemui.keyguard.KeyguardSliceProvider;
28+
import com.android.systemui.patchlevelwarning.PeriodicPatchLevelExpiryCheck;
2829
import com.android.systemui.people.PeopleProvider;
2930
import com.android.systemui.startable.Dependencies;
3031
import com.android.systemui.statusbar.NotificationInsetsModule;
@@ -182,4 +183,6 @@ interface Builder {
182183
* Member injection into the supplied argument.
183184
*/
184185
void inject(PeopleProvider peopleProvider);
186+
187+
void inject(PeriodicPatchLevelExpiryCheck periodicPatchLevelExpiryCheck);
185188
}

packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java

+78-1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
import android.content.IntentFilter;
6767
import android.content.pm.PackageManager.NameNotFoundException;
6868
import android.content.pm.UserInfo;
69+
import android.database.ContentObserver;
6970
import android.graphics.Matrix;
7071
import android.hardware.biometrics.BiometricSourceType;
7172
import android.media.AudioAttributes;
@@ -138,6 +139,7 @@
138139
import com.android.systemui.broadcast.BroadcastDispatcher;
139140
import com.android.systemui.classifier.FalsingCollector;
140141
import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
142+
import com.android.systemui.dagger.qualifiers.Background;
141143
import com.android.systemui.dagger.qualifiers.Main;
142144
import com.android.systemui.dagger.qualifiers.UiBackground;
143145
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
@@ -151,6 +153,8 @@
151153
import com.android.systemui.keyguard.shared.model.TransitionStep;
152154
import com.android.systemui.log.SessionTracker;
153155
import com.android.systemui.navigationbar.NavigationModeController;
156+
import com.android.systemui.patchlevelwarning.PatchLevelWarningDialogDelegate;
157+
import com.android.systemui.patchlevelwarning.PeriodicPatchLevelExpiryCheck;
154158
import com.android.systemui.plugins.statusbar.StatusBarStateController;
155159
import com.android.systemui.process.ProcessWrapper;
156160
import com.android.systemui.res.R;
@@ -174,6 +178,7 @@
174178
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
175179
import com.android.systemui.util.DeviceConfigProxy;
176180
import com.android.systemui.util.kotlin.JavaAdapter;
181+
import com.android.systemui.util.settings.GlobalSettings;
177182
import com.android.systemui.util.settings.SecureSettings;
178183
import com.android.systemui.util.settings.SystemSettings;
179184
import com.android.systemui.util.time.SystemClock;
@@ -1434,6 +1439,21 @@ public void onPrimaryBouncerShowingChanged() {
14341439
private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
14351440

14361441
private WindowManagerOcclusionManager mWmOcclusionManager;
1442+
1443+
private final Lazy<PatchLevelWarningDialogDelegate> mPatchLevelWarningDialogDelegate;
1444+
1445+
private final Handler mBgHandler;
1446+
1447+
private final GlobalSettings mGlobalSettings;
1448+
1449+
private final BroadcastReceiver mPatchExpiryNotificiationTapReceiver = new BroadcastReceiver() {
1450+
@Override
1451+
public void onReceive(Context context, Intent intent) {
1452+
if (PeriodicPatchLevelExpiryCheck.OPEN_DIALOG_ACTION.equals(intent.getAction())) {
1453+
maybeShowPatchLevelExpiredDialog();
1454+
}
1455+
}
1456+
};
14371457
/**
14381458
14391459
* Injected constructor. See {@link KeyguardModule}.
@@ -1476,6 +1496,7 @@ public KeyguardViewMediator(
14761496
FeatureFlags featureFlags,
14771497
SecureSettings secureSettings,
14781498
SystemSettings systemSettings,
1499+
GlobalSettings globalSettings,
14791500
SystemClock systemClock,
14801501
ProcessWrapper processWrapper,
14811502
@Main CoroutineDispatcher mainDispatcher,
@@ -1485,7 +1506,9 @@ public KeyguardViewMediator(
14851506
Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
14861507
SelectedUserInteractor selectedUserInteractor,
14871508
KeyguardInteractor keyguardInteractor,
1488-
WindowManagerOcclusionManager wmOcclusionManager) {
1509+
WindowManagerOcclusionManager wmOcclusionManager,
1510+
Lazy<PatchLevelWarningDialogDelegate> patchLevelWarningDialogDelegate,
1511+
@Background Handler bgHandler) {
14891512
mContext = context;
14901513
mUserTracker = userTracker;
14911514
mFalsingCollector = falsingCollector;
@@ -1563,6 +1586,10 @@ public KeyguardViewMediator(
15631586
mShowKeyguardWakeLock.setReferenceCounted(false);
15641587

15651588
mWmOcclusionManager = wmOcclusionManager;
1589+
1590+
mBgHandler = bgHandler;
1591+
mPatchLevelWarningDialogDelegate = patchLevelWarningDialogDelegate;
1592+
mGlobalSettings = globalSettings;
15661593
}
15671594

15681595
public void userActivity() {
@@ -1654,6 +1681,33 @@ private void setupLocked() {
16541681
mJavaAdapter.alwaysCollectFlow(
16551682
mWallpaperRepository.getWallpaperSupportsAmbientMode(),
16561683
this::setWallpaperSupportsAmbientMode);
1684+
1685+
// note: observers are conflated
1686+
final ContentObserver observer = new ContentObserver(mBgHandler) {
1687+
@Override
1688+
public void onChange(boolean selfChange) {
1689+
if (PeriodicPatchLevelExpiryCheck.isPatchLevelWarningEnabled(mContext)) {
1690+
PeriodicPatchLevelExpiryCheck.schedule(mContext, false);
1691+
} else {
1692+
mPatchLevelWarningDialogDelegate.get().markDontShowOnKeyguard();
1693+
PeriodicPatchLevelExpiryCheck.cancel(mContext);
1694+
}
1695+
}
1696+
};
1697+
final var expiryCheckEnabledUri =
1698+
Settings.Global.getUriFor(Settings.Global.PATCH_LEVEL_WARNING_DISABLED);
1699+
mGlobalSettings.registerContentObserverAsync(expiryCheckEnabledUri, observer);
1700+
1701+
final IntentFilter patchExpiryDialogOpenFilter = new IntentFilter();
1702+
patchExpiryDialogOpenFilter.addAction(PeriodicPatchLevelExpiryCheck.OPEN_DIALOG_ACTION);
1703+
patchExpiryDialogOpenFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
1704+
mContext.registerReceiver(mPatchExpiryNotificiationTapReceiver, patchExpiryDialogOpenFilter,
1705+
SYSTEMUI_PERMISSION, null /* scheduler */,
1706+
Context.RECEIVER_NOT_EXPORTED);
1707+
}
1708+
1709+
public void showPatchLevelExpiryWarningOnNextUnlock() {
1710+
mPatchLevelWarningDialogDelegate.get().markShowOnKeyguard();
16571711
}
16581712

16591713
@Override
@@ -2776,13 +2830,36 @@ private void sendUserPresentBroadcast() {
27762830
USER_PRESENT_INTENT_OPTIONS);
27772831
}
27782832
mLockPatternUtils.userPresent(currentUserId);
2833+
2834+
// Show expiry warning popup
2835+
// This *could* be done as a separate app, but an implicit broadcast receiver
2836+
// in a manifest wouldn't receive this broadcast.
2837+
if (mPatchLevelWarningDialogDelegate.get().shouldShowOnThisKeyguardUnlock()) {
2838+
maybeShowPatchLevelExpiredDialog();
2839+
}
27792840
});
27802841
} else {
27812842
mBootSendUserPresent = true;
27822843
}
27832844
}
27842845
}
27852846

2847+
/**
2848+
* Maybe shows patch level expiry dialog with no checks on whether it has been shown on keyguard
2849+
* unlock already. Also used for opening the dialog in the notifications.
2850+
*/
2851+
private void maybeShowPatchLevelExpiredDialog() {
2852+
mPatchLevelWarningDialogDelegate.get().beforeDialogShown();
2853+
// Although the periodic service will mark us to show the dialog on keyguard unlock,
2854+
// need to sanity check this against the actual expiry state and setting
2855+
if (PeriodicPatchLevelExpiryCheck.isPatchLevelExpiredOrUnparseable() &&
2856+
PeriodicPatchLevelExpiryCheck.isPatchLevelWarningEnabled(mContext)) {
2857+
// post to UI thread of keyguard because possibly still holding a lock here, and
2858+
// should finish rest of keyguard animations
2859+
mHandler.post(() -> mPatchLevelWarningDialogDelegate.get().createDialog().show());
2860+
}
2861+
}
2862+
27862863
/**
27872864
* @see #keyguardDone
27882865
* @see #KEYGUARD_DONE_DRAWING

packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java

+12-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import android.app.IActivityTaskManager;
2020
import android.app.trust.TrustManager;
2121
import android.content.Context;
22+
import android.os.Handler;
2223
import android.os.PowerManager;
2324

2425
import com.android.internal.jank.InteractionJankMonitor;
@@ -43,6 +44,7 @@
4344
import com.android.systemui.classifier.FalsingModule;
4445
import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
4546
import com.android.systemui.dagger.SysUISingleton;
47+
import com.android.systemui.dagger.qualifiers.Background;
4648
import com.android.systemui.dagger.qualifiers.Main;
4749
import com.android.systemui.dagger.qualifiers.UiBackground;
4850
import com.android.systemui.dreams.DreamOverlayStateController;
@@ -66,6 +68,7 @@
6668
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModelModule;
6769
import com.android.systemui.log.SessionTracker;
6870
import com.android.systemui.navigationbar.NavigationModeController;
71+
import com.android.systemui.patchlevelwarning.PatchLevelWarningDialogDelegate;
6972
import com.android.systemui.process.ProcessWrapper;
7073
import com.android.systemui.settings.UserTracker;
7174
import com.android.systemui.shade.ShadeController;
@@ -81,6 +84,7 @@
8184
import com.android.systemui.util.DeviceConfigProxy;
8285
import com.android.systemui.util.ThreadAssert;
8386
import com.android.systemui.util.kotlin.JavaAdapter;
87+
import com.android.systemui.util.settings.GlobalSettings;
8488
import com.android.systemui.util.settings.SecureSettings;
8589
import com.android.systemui.util.settings.SystemSettings;
8690
import com.android.systemui.util.time.SystemClock;
@@ -166,6 +170,7 @@ static KeyguardViewMediator newKeyguardViewMediator(
166170
FeatureFlags featureFlags,
167171
SecureSettings secureSettings,
168172
SystemSettings systemSettings,
173+
GlobalSettings globalSettings,
169174
SystemClock systemClock,
170175
ProcessWrapper processWrapper,
171176
@Main CoroutineDispatcher mainDispatcher,
@@ -175,7 +180,9 @@ static KeyguardViewMediator newKeyguardViewMediator(
175180
Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
176181
SelectedUserInteractor selectedUserInteractor,
177182
KeyguardInteractor keyguardInteractor,
178-
WindowManagerOcclusionManager windowManagerOcclusionManager) {
183+
WindowManagerOcclusionManager windowManagerOcclusionManager,
184+
Lazy<PatchLevelWarningDialogDelegate> patchLevelWarningDialogDelegate,
185+
@Background Handler bgHandler) {
179186
return new KeyguardViewMediator(
180187
context,
181188
uiEventLogger,
@@ -216,6 +223,7 @@ static KeyguardViewMediator newKeyguardViewMediator(
216223
featureFlags,
217224
secureSettings,
218225
systemSettings,
226+
globalSettings,
219227
systemClock,
220228
processWrapper,
221229
mainDispatcher,
@@ -225,7 +233,9 @@ static KeyguardViewMediator newKeyguardViewMediator(
225233
wmLockscreenVisibilityManager,
226234
selectedUserInteractor,
227235
keyguardInteractor,
228-
windowManagerOcclusionManager);
236+
windowManagerOcclusionManager,
237+
patchLevelWarningDialogDelegate,
238+
bgHandler);
229239
}
230240

231241
/** */

0 commit comments

Comments
 (0)