009: Closure wave 3 deblob, handoff, and optional wave4/malloc bacons.

Ship script gates wave 4 behind AXP_DEBLOB_WAVE4, documents secrets cert path,
and adds deblob appliers plus tests; docs capture Swissquote fix and parity.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
sunfish-adminandCursor committed 2026-10-09 17:31:01 +02:00
1 parent deaaf42caa
commit 3f941e8451
64 files changed
+6270 -86

No files matched your search

@@ -0,0 +1,153 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: anupritaisno1 <www.anuprita804@gmail.com>
Date: Mon, 18 Oct 2021 01:35:40 +0300
Subject: [PATCH] automatically reboot device after timeout if set
Signed-off-by: anupritaisno1 <www.anuprita804@gmail.com>
Change-Id: If891bfbcc144c9336ba013260bad2b7c7a59c054
---
core/java/android/provider/Settings.java | 7 ++++
data/etc/com.android.systemui.xml | 1 +
packages/SystemUI/AndroidManifest.xml | 3 ++
.../keyguard/KeyguardViewMediator.java | 35 +++++++++++++++++++
4 files changed, 46 insertions(+)
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 783483d8e491..48913891ac52 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -19027,6 +19027,13 @@ public final class Settings {
public static final String REVIEW_PERMISSIONS_NOTIFICATION_STATE =
"review_permissions_notification_state";
+ /**
+ * Whether to automatically reboot the device after a user defined timeout
+ *
+ * @hide
+ */
+ public static final String SETTINGS_REBOOT_AFTER_TIMEOUT = "settings_reboot_after_timeout";
+
/**
* Whether repair mode is active on the device.
* <p>
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index ce2543a47cf5..67108d16e099 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -54,6 +54,7 @@
<permission name="android.permission.READ_PRECISE_PHONE_STATE"/>
<permission name="android.permission.READ_WALLPAPER_INTERNAL"/>
<permission name="android.permission.REAL_GET_TASKS"/>
+ <permission name="android.permission.REBOOT"/>
<permission name="android.permission.REQUEST_NETWORK_SCORES"/>
<permission name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"/>
<permission name="android.permission.SATELLITE_COMMUNICATION"/>
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 351cce0929b7..00604a316019 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -356,6 +356,9 @@
<uses-permission android:name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS" />
+ <!-- Permission to allow rebooting the device after a user configurable amount of time -->
+ <uses-permission android:name="android.permission.REBOOT" />
+
<uses-permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT" />
<uses-permission android:name="android.permission.MONITOR_STICKY_MODIFIER_STATE" />
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 6034e92be873..67dd84e8ae8f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -240,6 +240,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private final static String TAG = "KeyguardViewMediator";
+ private static final String DELAYED_REBOOT_ACTION =
+ "com.android.internal.policy.impl.PhoneWindowManager.DELAYED_REBOOT";
public static final String DELAYED_KEYGUARD_ACTION =
"com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD";
private static final String DELAYED_LOCK_PROFILE_ACTION =
@@ -417,6 +419,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
*/
private int mDelayedProfileShowingSequence;
+ /**
+ * Same as {@link #mDelayedProfileShowingSequence}, but used for our reboot implementation
+ */
+ private int mDelayedRebootSequence;
+
private final DismissCallbackRegistry mDismissCallbackRegistry;
// the properties of the keyguard
@@ -1503,6 +1510,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
final IntentFilter delayedActionFilter = new IntentFilter();
delayedActionFilter.addAction(DELAYED_KEYGUARD_ACTION);
delayedActionFilter.addAction(DELAYED_LOCK_PROFILE_ACTION);
+ delayedActionFilter.addAction(DELAYED_REBOOT_ACTION);
delayedActionFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
mContext.registerReceiver(mDelayedLockBroadcastReceiver, delayedActionFilter,
SYSTEMUI_PERMISSION, null /* scheduler */,
@@ -1866,6 +1874,18 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
}
+ private void doRebootForOwnerAfterTimeoutIfEnabled(long rebootAfterTimeout) {
+ long when = mSystemClock.elapsedRealtime() + rebootAfterTimeout;
+ Intent rebootIntent = new Intent(DELAYED_REBOOT_ACTION);
+ rebootIntent.putExtra("seq", mDelayedRebootSequence);
+ rebootIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ PendingIntent sender = PendingIntent.getBroadcast(mContext,
+ 0, rebootIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, when, sender);
+ if (DEBUG) Log.d(TAG, "setting alarm to reboot device, timeout = "
+ + String.valueOf(rebootAfterTimeout));
+ }
+
private void doKeyguardForChildProfilesLocked() {
for (UserInfo profile : mUserTracker.getUserProfiles()) {
if (!profile.isEnabled()) continue;
@@ -1884,6 +1904,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mDelayedProfileShowingSequence++;
}
+ private void cancelDoRebootForOwnerAfterTimeoutIfEnabled() {
+ mDelayedRebootSequence++;
+ }
+
/**
* It will let us know when the device is waking up.
*/
@@ -2298,6 +2322,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen");
showKeyguard(options);
+ final long rebootAfterTimeout = Settings.Global.getLong(mContext.getContentResolver(), Settings.Global.SETTINGS_REBOOT_AFTER_TIMEOUT, 0);
+ if (rebootAfterTimeout >= 1) {
+ doRebootForOwnerAfterTimeoutIfEnabled(rebootAfterTimeout);
+ }
}
@SuppressLint("MissingPermission")
@@ -2478,6 +2506,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
}
}
+ } else if (DELAYED_REBOOT_ACTION.equals(intent.getAction())) {
+ final int sequence = intent.getIntExtra("seq", 0);
+ if (sequence == mDelayedRebootSequence) {
+ PowerManager pm = mContext.getSystemService(PowerManager.class);
+ pm.reboot(null);
+ }
}
}
};
@@ -3200,6 +3234,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mHideAnimationRun = false;
adjustStatusBarLocked();
sendUserPresentBroadcast();
+ cancelDoRebootForOwnerAfterTimeoutIfEnabled();
}
private Configuration.Builder createInteractionJankMonitorConf(int cuj) {
@@ -0,0 +1,62 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Dmitry Muhomor <muhomor.dmitry@gmail.com>
Date: Tue, 6 Sep 2022 16:48:26 +0300
Subject: [PATCH] bugfix: Bluetooth auto turn off ignored connected BLE devices
Previous attempt at fixing this didn't work properly, because getConnectionStateLeAware() didn't
actually report BLE state.
---
.../android/server/ext/BluetoothAutoOff.java | 20 ++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
diff --git a/services/core/java/com/android/server/ext/BluetoothAutoOff.java b/services/core/java/com/android/server/ext/BluetoothAutoOff.java
index 4e7dbc042f37..a091b006214f 100644
--- a/services/core/java/com/android/server/ext/BluetoothAutoOff.java
+++ b/services/core/java/com/android/server/ext/BluetoothAutoOff.java
@@ -3,6 +3,7 @@ package com.android.server.ext;
import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -12,12 +13,14 @@ import android.provider.Settings;
import android.util.Slog;
class BluetoothAutoOff extends DelayedConditionalAction {
+ private final BluetoothManager manager;
@Nullable
private final BluetoothAdapter adapter;
BluetoothAutoOff(SystemServerExt sse) {
super(sse, sse.bgHandler);
- adapter = sse.context.getSystemService(BluetoothManager.class).getAdapter();
+ manager = sse.context.getSystemService(BluetoothManager.class);
+ adapter = manager.getAdapter();
}
@Override
@@ -51,11 +54,18 @@ class BluetoothAutoOff extends DelayedConditionalAction {
private boolean isAdapterOnAndDisconnected() {
if (adapter != null) {
- int state = adapter.getLeStateSysApi(); // getState() converts BLE states into STATE_OFF
+ if (adapter.isLeEnabled()) {
+ if (adapter.getConnectionState() == BluetoothAdapter.STATE_DISCONNECTED) {
+ // Bluetooth GATT Profile (Bluetooth LE) connection state is ignored
+ // by getConnectionState()
+ return manager.getConnectedDevices(BluetoothProfile.GATT).size() == 0;
+ }
+ }
- if (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_BLE_ON) {
- // getConnectionState() converts BLE states into STATE_DISCONNECTED
- return adapter.getConnectionStateLeAware() == BluetoothAdapter.STATE_DISCONNECTED;
+ // isLeEnabled() currently implies isEnabled(), but check again anyway in case
+ // this changes in the future
+ if (adapter.isEnabled()) {
+ return adapter.getConnectionState() == BluetoothAdapter.STATE_DISCONNECTED;
}
}
@@ -0,0 +1,120 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Dmitry Muhomor <muhomor.dmitry@gmail.com>
Date: Sun, 31 Jul 2022 11:19:33 +0300
Subject: [PATCH] Bluetooth auto turn off
Co-authored-by: Pratyush <codelab@pratyush.dev>
---
core/java/android/provider/Settings.java | 6 ++
.../android/server/ext/BluetoothAutoOff.java | 69 +++++++++++++++++++
.../android/server/ext/SystemServerExt.java | 4 ++
3 files changed, 79 insertions(+)
create mode 100644 services/core/java/com/android/server/ext/BluetoothAutoOff.java
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 49f59950b9e0..58f1077af988 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -19549,6 +19549,12 @@ public final class Settings {
*/
public static final String WIFI_OFF_TIMEOUT = "wifi_off_timeout";
+ /**
+ * The amount of time in milliseconds before a disconnected Bluetooth adapter is turned off
+ * @hide
+ */
+ public static final String BLUETOOTH_OFF_TIMEOUT = "bluetooth_off_timeout";
+
/**
* Whether repair mode is active on the device.
* <p>
diff --git a/services/core/java/com/android/server/ext/BluetoothAutoOff.java b/services/core/java/com/android/server/ext/BluetoothAutoOff.java
new file mode 100644
index 000000000000..4e7dbc042f37
--- /dev/null
+++ b/services/core/java/com/android/server/ext/BluetoothAutoOff.java
@@ -0,0 +1,69 @@
+package com.android.server.ext;
+
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Build;
+import android.provider.Settings;
+import android.util.Slog;
+
+class BluetoothAutoOff extends DelayedConditionalAction {
+ @Nullable
+ private final BluetoothAdapter adapter;
+
+ BluetoothAutoOff(SystemServerExt sse) {
+ super(sse, sse.bgHandler);
+ adapter = sse.context.getSystemService(BluetoothManager.class).getAdapter();
+ }
+
+ @Override
+ protected boolean shouldScheduleAlarm() {
+ return isAdapterOnAndDisconnected();
+ }
+
+ @Override
+ protected void alarmTriggered() {
+ if (isAdapterOnAndDisconnected()) {
+ adapter.disable();
+ }
+ }
+
+ @Override
+ protected void registerStateListener() {
+ IntentFilter f = new IntentFilter();
+ f.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+ f.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
+
+ sse.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context broadcastContext, Intent intent) {
+ if (Build.isDebuggable()) {
+ Slog.d("BtAutoOff", "" + intent + ", extras " + intent.getExtras().deepCopy());
+ }
+ update();
+ }
+ }, f, handler);
+ }
+
+ private boolean isAdapterOnAndDisconnected() {
+ if (adapter != null) {
+ int state = adapter.getLeStateSysApi(); // getState() converts BLE states into STATE_OFF
+
+ if (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_BLE_ON) {
+ // getConnectionState() converts BLE states into STATE_DISCONNECTED
+ return adapter.getConnectionStateLeAware() == BluetoothAdapter.STATE_DISCONNECTED;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ protected String getDelayGlobalSettingsKey() {
+ return Settings.Global.BLUETOOTH_OFF_TIMEOUT;
+ }
+}
diff --git a/services/core/java/com/android/server/ext/SystemServerExt.java b/services/core/java/com/android/server/ext/SystemServerExt.java
index 66350e2b7f74..3c341ed25f4e 100644
--- a/services/core/java/com/android/server/ext/SystemServerExt.java
+++ b/services/core/java/com/android/server/ext/SystemServerExt.java
@@ -53,6 +53,10 @@ public final class SystemServerExt {
if (packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI, 0)) {
new WifiAutoOff(this);
}
+
+ if (packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH, 0)) {
+ new BluetoothAutoOff(this);
+ }
}
public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter, Handler handler) {
@@ -0,0 +1,231 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Dmitry Muhomor <muhomor.dmitry@gmail.com>
Date: Sun, 31 Jul 2022 10:06:14 +0300
Subject: [PATCH] infrastructure for system_server extensions
---
.../server/ext/DelayedConditionalAction.java | 135 ++++++++++++++++++
.../android/server/ext/SystemServerExt.java | 58 ++++++++
.../java/com/android/server/SystemServer.java | 2 +
3 files changed, 195 insertions(+)
create mode 100644 services/core/java/com/android/server/ext/DelayedConditionalAction.java
create mode 100644 services/core/java/com/android/server/ext/SystemServerExt.java
diff --git a/services/core/java/com/android/server/ext/DelayedConditionalAction.java b/services/core/java/com/android/server/ext/DelayedConditionalAction.java
new file mode 100644
index 000000000000..d72f302e9d42
--- /dev/null
+++ b/services/core/java/com/android/server/ext/DelayedConditionalAction.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2022 GrapheneOS
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ext;
+
+import android.app.AlarmManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.util.Slog;
+
+/**
+ * Infrastructure for actions that:
+ * - happen after a user-configurable device-wide (Settings.Global) delay
+ * - need to be taken even when the device is in deep sleep
+ * - need to be rescheduled based on some listenable event
+ */
+public abstract class DelayedConditionalAction {
+ private static final String TAG = "DelayedConditionalAction";
+
+ protected final SystemServerExt sse;
+ protected final Thread thread;
+ protected final Handler handler;
+
+ protected final ContentResolver contentResolver;
+ protected final AlarmManager alarmManager;
+ private final AlarmManager.OnAlarmListener alarmListener;
+
+ protected DelayedConditionalAction(SystemServerExt sse, Handler handler) {
+ this.sse = sse;
+
+ Looper looper = handler.getLooper();
+ thread = looper.getThread();
+ this.handler = handler;
+
+ if (Build.isDebuggable()) {
+ if (thread != Thread.currentThread()) {
+ throw new IllegalStateException("all calls should happen on the same thread");
+ }
+ }
+
+ Context ctx = sse.context;
+ contentResolver = ctx.getContentResolver();
+ alarmManager = ctx.getSystemService(AlarmManager.class);
+
+ alarmListener = () -> {
+ if (delayDurationMillis() == 0) {
+ return;
+ }
+
+ alarmTriggered();
+ };
+
+ registerStateListener();
+
+ Uri delaySettingUri = Settings.Global.getUriFor(getDelayGlobalSettingsKey());
+
+ ContentObserver delayChangeListener = new ContentObserver(handler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ update();
+ }
+ };
+
+ contentResolver.registerContentObserver(delaySettingUri, false, delayChangeListener);
+ }
+
+ private boolean alarmScheduled;
+
+ protected final void update() {
+ final Thread curThread = Thread.currentThread();
+ if (curThread != thread) {
+ String msg = "update() called on an unknown thread " + curThread;
+ if (Build.isDebuggable()) {
+ throw new IllegalStateException(msg);
+ } else {
+ Slog.e(TAG, msg, new Throwable());
+ return;
+ }
+ }
+
+ if (alarmScheduled) {
+ alarmManager.cancel(alarmListener);
+ alarmScheduled = false;
+ }
+
+ if (!shouldScheduleAlarm()) {
+ return;
+ }
+
+ long delayMillis = delayDurationMillis();
+
+ if (delayMillis == 0) {
+ return;
+ }
+
+ final long triggerAt = SystemClock.elapsedRealtime() + delayMillis;
+ alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAt,
+ getClass().getName(), alarmListener, handler);
+ alarmScheduled = true;
+ }
+
+ private long delayDurationMillis() {
+ return Settings.Global.getLong(contentResolver, getDelayGlobalSettingsKey(), 0);
+ }
+
+ // Make sure to use the same Handler that is used for all other callbacks;
+ // call update() to reschedule / cancel the alarm
+ protected abstract void registerStateListener();
+
+ protected abstract boolean shouldScheduleAlarm();
+ protected abstract void alarmTriggered();
+
+ // android.provider.Settings.Global key
+ protected abstract String getDelayGlobalSettingsKey();
+}
diff --git a/services/core/java/com/android/server/ext/SystemServerExt.java b/services/core/java/com/android/server/ext/SystemServerExt.java
new file mode 100644
index 000000000000..83d895650473
--- /dev/null
+++ b/services/core/java/com/android/server/ext/SystemServerExt.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 GrapheneOS
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ext;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.os.Handler;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.server.pm.PackageManagerService;
+
+public final class SystemServerExt {
+
+ public final Context context;
+ public final Handler bgHandler;
+ public final PackageManagerService packageManager;
+
+ private SystemServerExt(Context systemContext, PackageManagerService pm) {
+ context = systemContext;
+ bgHandler = BackgroundThread.getHandler();
+ packageManager = pm;
+ }
+
+ /*
+ Called after system server has completed its initialization,
+ but before any of the apps are started.
+
+ Call from com.android.server.SystemServer#startOtherServices(), at the end of lambda
+ that is passed into mActivityManagerService.systemReady()
+ */
+ public static void init(Context systemContext, PackageManagerService pm) {
+ SystemServerExt sse = new SystemServerExt(systemContext, pm);
+ sse.bgHandler.post(sse::initBgThread);
+ }
+
+ void initBgThread() {
+
+ }
+
+ public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter, Handler handler) {
+ context.registerReceiver(receiver, filter, null, handler);
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e92ba3ed7dc3..df60c628ccdb 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -3454,6 +3454,8 @@ public final class SystemServer implements Dumpable {
reportWtf("Triggering OdsignStatsLogger", e);
}
t.traceEnd();
+
+ com.android.server.ext.SystemServerExt.init(mSystemContext, mPackageManagerService);
}, t);
t.traceBegin("LockSettingsThirdPartyAppsStarted");
@@ -0,0 +1,128 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Dmitry Muhomor <muhomor.dmitry@gmail.com>
Date: Sun, 31 Jul 2022 10:06:14 +0300
Subject: [PATCH] Wi-Fi auto turn off
Co-authored-by: Pratyush <codelab@pratyush.dev>
---
core/java/android/provider/Settings.java | 6 ++
.../android/server/ext/SystemServerExt.java | 5 +-
.../com/android/server/ext/WifiAutoOff.java | 69 +++++++++++++++++++
3 files changed, 79 insertions(+), 1 deletion(-)
create mode 100644 services/core/java/com/android/server/ext/WifiAutoOff.java
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 10efaa78e435..49f59950b9e0 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -19543,6 +19543,12 @@ public final class Settings {
*/
public static final String SETTINGS_REBOOT_AFTER_TIMEOUT = "settings_reboot_after_timeout";
+ /**
+ * The amount of time in milliseconds before a disconnected Wi-Fi adapter is turned off
+ * @hide
+ */
+ public static final String WIFI_OFF_TIMEOUT = "wifi_off_timeout";
+
/**
* Whether repair mode is active on the device.
* <p>
diff --git a/services/core/java/com/android/server/ext/SystemServerExt.java b/services/core/java/com/android/server/ext/SystemServerExt.java
index 83d895650473..66350e2b7f74 100644
--- a/services/core/java/com/android/server/ext/SystemServerExt.java
+++ b/services/core/java/com/android/server/ext/SystemServerExt.java
@@ -19,6 +19,7 @@ package com.android.server.ext;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.os.Handler;
import com.android.internal.os.BackgroundThread;
@@ -49,7 +50,9 @@ public final class SystemServerExt {
}
void initBgThread() {
-
+ if (packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI, 0)) {
+ new WifiAutoOff(this);
+ }
}
public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter, Handler handler) {
diff --git a/services/core/java/com/android/server/ext/WifiAutoOff.java b/services/core/java/com/android/server/ext/WifiAutoOff.java
new file mode 100644
index 000000000000..c7a3c05fe766
--- /dev/null
+++ b/services/core/java/com/android/server/ext/WifiAutoOff.java
@@ -0,0 +1,69 @@
+package com.android.server.ext;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.provider.Settings;
+import android.util.Slog;
+
+class WifiAutoOff extends DelayedConditionalAction {
+ private final WifiManager wifiManager;
+
+ WifiAutoOff(SystemServerExt sse) {
+ super(sse, sse.bgHandler);
+ wifiManager = sse.context.getSystemService(WifiManager.class);
+ }
+
+ @Override
+ protected boolean shouldScheduleAlarm() {
+ return isWifiEnabledAndNotConnected();
+ }
+
+ @Override
+ protected void alarmTriggered() {
+ if (isWifiEnabledAndNotConnected()) {
+ wifiManager.setWifiEnabled(false);
+ }
+ }
+
+ private boolean isWifiEnabledAndNotConnected() {
+ if (wifiManager.isWifiEnabled()) {
+ WifiInfo i = wifiManager.getConnectionInfo();
+ if (i == null) {
+ return true;
+ }
+ return i.getBSSID() == null;
+ }
+
+ return false;
+ }
+
+ @Override
+ protected void registerStateListener() {
+ IntentFilter f = new IntentFilter();
+ f.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ f.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ // ConnectivityManager APIs seem unfit for listening to Wi-Fi state specifically, they look
+ // to be higher level than that, eg VPN over Wi-Fi isn't considered to be a Wi-Fi connection
+ // by ConnectivityManager
+
+ sse.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Build.isDebuggable()) {
+ Slog.d("WifiAutoOff", "" + intent + ", extras " + intent.getExtras().deepCopy());
+ }
+ update();
+ }
+ }, f, handler);
+ }
+
+ @Override
+ protected String getDelayGlobalSettingsKey() {
+ return Settings.Global.WIFI_OFF_TIMEOUT;
+ }
+}
@@ -0,0 +1,141 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Dmitry Muhomor <muhomor.dmitry@gmail.com>
Date: Mon, 11 Mar 2024 19:40:18 +0200
Subject: [PATCH] SystemUI: require unlocking to use QS tiles by default
---
.../src/com/android/systemui/qs/QSHost.java | 1 +
.../com/android/systemui/qs/QSHostAdapter.kt | 4 +++
.../systemui/qs/tileimpl/QSTileImpl.java | 36 +++++++++++++++++--
.../systemui/qs/QSHostAdapterKosmos.kt | 2 ++
4 files changed, 40 insertions(+), 3 deletions(-)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 7c7f48e0d37b..87e87348d0f2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -51,6 +51,7 @@ public interface QSHost {
Context getUserContext();
int getUserId();
Collection<QSTile> getTiles();
+ com.android.systemui.statusbar.policy.KeyguardStateController getKeyguardStateController();
void addCallback(Callback callback);
void removeCallback(Callback callback);
void removeTile(String tileSpec);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
index dc3b58247152..08f3fc9d7bdb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
@@ -31,6 +31,7 @@ import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.shade.ShadeDisplayAware
+import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -53,6 +54,7 @@ constructor(
@ShadeDisplayAware private val context: Context,
private val tileServiceRequestControllerBuilder: TileServiceRequestController.Builder,
@Application private val scope: CoroutineScope,
+ private val keyguardStateController_: KeyguardStateController,
dumpManager: DumpManager,
) : QSHost {
@@ -72,6 +74,8 @@ constructor(
return interactor.currentQSTiles
}
+ override fun getKeyguardStateController() = keyguardStateController_
+
override fun getSpecs(): List<String> {
return interactor.currentTilesSpecs.map { it.spec }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 1d1e9911884c..e9dc57e4c47d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -71,6 +71,7 @@ import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.SideLabelTileLayout;
import com.android.systemui.qs.flags.QsInCompose;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.io.PrintWriter;
import java.util.Objects;
@@ -272,6 +273,32 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
return true;
}
+ public enum Action {
+ CLICK,
+ SECONDARY_CLICK,
+ LONG_CLICK,
+ }
+
+ public boolean isAllowedWhenLocked(Action action) {
+ return false;
+ }
+
+ void handleAction(Action action, Runnable handler) {
+ if (isAllowedWhenLocked(action)) {
+ handler.run();
+ return;
+ }
+
+ KeyguardStateController ksc = mHost.getKeyguardStateController();
+ boolean hasSecureKeyguard = ksc.isMethodSecure() && ksc.isShowing();
+
+ if (hasSecureKeyguard) {
+ mActivityStarter.postQSRunnableDismissingKeyguard(handler);
+ } else {
+ handler.run();
+ }
+ }
+
// safe to call from any thread
public void addCallback(Callback callback) {
@@ -616,16 +643,19 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
} else {
mQSLogger.logHandleClick(mTileSpec, msg.arg1);
- handleClick((Expandable) msg.obj);
+ Expandable view = (Expandable) msg.obj;
+ handleAction(Action.CLICK, () -> handleClick(view));
}
} else if (msg.what == SECONDARY_CLICK) {
name = "handleSecondaryClick";
mQSLogger.logHandleSecondaryClick(mTileSpec, msg.arg1);
- handleSecondaryClick((Expandable) msg.obj);
+ Expandable view = (Expandable) msg.obj;
+ handleAction(Action.SECONDARY_CLICK, () -> handleSecondaryClick(view));
} else if (msg.what == LONG_CLICK) {
name = "handleLongClick";
mQSLogger.logHandleLongClick(mTileSpec, msg.arg1);
- handleLongClick((Expandable) msg.obj);
+ Expandable view = (Expandable) msg.obj;
+ handleAction(Action.LONG_CLICK, () -> handleLongClick(view));
} else if (msg.what == REFRESH_STATE) {
name = "handleRefreshState";
handleRefreshState(msg.obj);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QSHostAdapterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QSHostAdapterKosmos.kt
index 0bf801b35ad1..7cb93459077c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QSHostAdapterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QSHostAdapterKosmos.kt
@@ -22,6 +22,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.qs.external.tileServiceRequestControllerBuilder
import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+import com.android.systemui.statusbar.policy.keyguardStateController
val Kosmos.qsHostAdapter by
Kosmos.Fixture {
@@ -30,6 +31,7 @@ val Kosmos.qsHostAdapter by
applicationContext,
tileServiceRequestControllerBuilder,
applicationCoroutineScope,
+ keyguardStateController,
dumpManager,
)
}
@@ -0,0 +1,196 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: pratyush <codelab@pratyush.dev>
Date: Mon, 7 Jun 2021 22:15:59 +0100
Subject: [PATCH] add auto-reboot setting
---
res/values/arrays.xml | 31 +++++++
res/values/strings.xml | 3 +
res/xml/security_dashboard_settings.xml | 10 ++-
.../AutoRebootPreferenceController.java | 82 +++++++++++++++++++
.../settings/security/SecuritySettings.java | 1 +
5 files changed, 126 insertions(+), 1 deletion(-)
create mode 100644 src/com/android/settings/security/AutoRebootPreferenceController.java
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index febdb0412a4..c5df677975d 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -105,6 +105,37 @@
<item>1800000</item>
</string-array>
+ <!-- Auto reboot settings -->
+ <string-array name="auto_reboot_entries">
+ <item>Off</item>
+ <item>10 minutes</item>
+ <item>30 minutes</item>
+ <item>1 hour</item>
+ <item>2 hours</item>
+ <item>4 hours</item>
+ <item>8 hours</item>
+ <item>12 hours</item>
+ <item>24 hours</item>
+ <item>36 hours</item>
+ <item>48 hours</item>
+ <item>72 hours</item>
+ </string-array>
+
+ <string-array name="auto_reboot_values" translatable="false">
+ <item>0</item> <!-- Disabled -->
+ <item>600000</item>
+ <item>1800000</item>
+ <item>3600000</item>
+ <item>7200000</item>
+ <item>14400000</item>
+ <item>28800000</item>
+ <item>43200000</item>
+ <item>86400000</item>
+ <item>129600000</item>
+ <item>172800000</item>
+ <item>259200000</item>
+ </string-array>
+
<!-- Wi-Fi settings -->
<!-- Match this with the order of NetworkInfo.DetailedState. --> <skip />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 2e44c013a96..b7105938345 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -675,6 +675,9 @@
<!-- Text shown for title of owner info setting [CHAR LIMIT=30]-->
<string name="owner_info_settings_title">Add text on lock screen</string>
+ <string name="auto_reboot_title">Auto reboot</string>
+ <string name="auto_reboot_summary">Automatically reboot the device, if the phone hasn\'t been unlocked within the selected number of hours.</string>
+
<!-- Text shown for summary of owner info setting (if none set) [CHAR LIMIT=40]-->
<string name="owner_info_settings_summary">None</string>
<!-- Hint text shown in owner info edit text [CHAR LIMIT=50] -->
diff --git a/res/xml/security_dashboard_settings.xml b/res/xml/security_dashboard_settings.xml
index 0550441d3f2..d0aeb27fe53 100644
--- a/res/xml/security_dashboard_settings.xml
+++ b/res/xml/security_dashboard_settings.xml
@@ -60,6 +60,14 @@
android:title="@string/security_settings_biometric_preference_title"
android:summary="@string/summary_placeholder"
settings:keywords="@string/keywords_biometric_settings" />
+
+ <ListPreference
+ android:key="auto_reboot"
+ android:title="@string/auto_reboot_title"
+ android:summary="@string/auto_reboot_summary"
+ android:persistent="false"
+ android:entries="@array/auto_reboot_entries"
+ android:entryValues="@array/auto_reboot_values" />
</PreferenceCategory>
<Preference
@@ -71,4 +79,4 @@
settings:controller="com.android.settings.security.SecurityAdvancedSettingsController"
settings:keywords="@string/security_advanced_settings_keywords" />
-</PreferenceScreen>
\ No newline at end of file
+</PreferenceScreen>
diff --git a/src/com/android/settings/security/AutoRebootPreferenceController.java b/src/com/android/settings/security/AutoRebootPreferenceController.java
new file mode 100644
index 00000000000..c7a75219a5e
--- /dev/null
+++ b/src/com/android/settings/security/AutoRebootPreferenceController.java
@@ -0,0 +1,82 @@
+package com.android.settings.security;
+
+import android.content.Context;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.lifecycle.events.OnResume;
+
+public class AutoRebootPreferenceController extends AbstractPreferenceController
+ implements PreferenceControllerMixin, OnResume,
+ Preference.OnPreferenceChangeListener {
+
+ private static final String KEY_AUTO_REBOOT = "auto_reboot";
+ private static final String PREF_KEY_SECURITY_CATEGORY = "security_category";
+
+ private PreferenceCategory mSecurityCategory;
+ private boolean mIsAdmin;
+ private final UserManager mUm;
+
+ public AutoRebootPreferenceController(Context context) {
+ super(context);
+ mUm = UserManager.get(context);
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mSecurityCategory = screen.findPreference(PREF_KEY_SECURITY_CATEGORY);
+ updatePreferenceState();
+ }
+
+ @Override
+ public boolean isAvailable() {
+ mIsAdmin = mUm.isAdminUser();
+ return mIsAdmin;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_AUTO_REBOOT;
+ }
+
+ // TODO: should we use onCreatePreferences() instead?
+ private void updatePreferenceState() {
+ if (mSecurityCategory == null) {
+ return;
+ }
+
+ if (mIsAdmin) {
+ ListPreference autoReboot =
+ (ListPreference) mSecurityCategory.findPreference(KEY_AUTO_REBOOT);
+ autoReboot.setValue(Long.toString(Settings.Global.getLong(
+ mContext.getContentResolver(), Settings.Global.SETTINGS_REBOOT_AFTER_TIMEOUT, 0)));
+ } else {
+ mSecurityCategory.removePreference(
+ mSecurityCategory.findPreference(KEY_AUTO_REBOOT));
+ }
+ }
+
+ @Override
+ public void onResume() {
+ updatePreferenceState();
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object value) {
+ final String key = preference.getKey();
+ if (KEY_AUTO_REBOOT.equals(key) && mIsAdmin) {
+ long timeout = Long.parseLong((String) value);
+ Settings.Global.putLong(mContext.getContentResolver(), Settings.Global.SETTINGS_REBOOT_AFTER_TIMEOUT, timeout);
+ }
+ return true;
+ }
+}
diff --git a/src/com/android/settings/security/SecuritySettings.java b/src/com/android/settings/security/SecuritySettings.java
index 797d3ec621e..3f0645f7dd8 100644
--- a/src/com/android/settings/security/SecuritySettings.java
+++ b/src/com/android/settings/security/SecuritySettings.java
@@ -114,6 +114,7 @@ public class SecuritySettings extends DashboardFragment {
securityPreferenceControllers.add(new CombinedBiometricStatusPreferenceController(
context, lifecycle));
securityPreferenceControllers.add(new ChangeScreenLockPreferenceController(context, host));
+ securityPreferenceControllers.add(new AutoRebootPreferenceController(context));
controllers.add(new PreferenceCategoryController(context, SECURITY_CATEGORY)
.setChildren(securityPreferenceControllers));
controllers.addAll(securityPreferenceControllers);
@@ -0,0 +1,238 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Oliver Scott <olivercscott@gmail.com>
Date: Fri, 2 Oct 2020 12:37:30 -0400
Subject: [PATCH] add bluetooth auto-turn-off setting
---
res/values/arrays.xml | 44 +++++++
res/values/strings.xml | 19 +++
res/xml/connected_devices.xml | 8 ++
.../BluetoothTimeoutPreferenceController.java | 115 ++++++++++++++++++
4 files changed, 186 insertions(+)
create mode 100644 src/com/android/settings/bluetooth/BluetoothTimeoutPreferenceController.java
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index c5df677975d..1a2796ad276 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -18,6 +18,50 @@
*/
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Bluetooth settings. The delay in inactivity before bluetooth is turned off. These are shown in a list dialog. -->
+ <string-array name="bluetooth_timeout_entries">
+ <item>@string/bluetooth_timeout_summary_never</item>
+ <item>@string/bluetooth_timeout_summary_15secs</item>
+ <item>@string/bluetooth_timeout_summary_30secs</item>
+ <item>@string/bluetooth_timeout_summary_1min</item>
+ <item>@string/bluetooth_timeout_summary_2mins</item>
+ <item>@string/bluetooth_timeout_summary_5mins</item>
+ <item>@string/bluetooth_timeout_summary_10mins</item>
+ <item>@string/bluetooth_timeout_summary_30mins</item>
+ <item>@string/bluetooth_timeout_summary_1hour</item>
+ <item>@string/bluetooth_timeout_summary_2hours</item>
+ <item>@string/bluetooth_timeout_summary_4hours</item>
+ <item>@string/bluetooth_timeout_summary_8hours</item>
+ </string-array>
+
+ <!-- Do not translate. -->
+ <string-array name="bluetooth_timeout_values" translatable="false">
+ <!-- Do not translate. -->
+ <item>0</item>
+ <!-- Do not translate. -->
+ <item>15000</item>
+ <!-- Do not translate. -->
+ <item>30000</item>
+ <!-- Do not translate. -->
+ <item>60000</item>
+ <!-- Do not translate. -->
+ <item>120000</item>
+ <!-- Do not translate. -->
+ <item>300000</item>
+ <!-- Do not translate. -->
+ <item>600000</item>
+ <!-- Do not translate. -->
+ <item>1800000</item>
+ <!-- Do not translate. -->
+ <item>3600000</item>
+ <!-- Do not translate. -->
+ <item>7200000</item>
+ <!-- Do not translate. -->
+ <item>14400000</item>
+ <!-- Do not translate. -->
+ <item>28800000</item>
+ </string-array>
+
<!-- Display settings. The delay in inactivity before the screen is turned off. These are shown in a list dialog. -->
<string-array name="screen_timeout_entries">
<item>15 seconds</item>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b7105938345..cf41793fd42 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -27,6 +27,25 @@
<!-- Used in confirmation dialogs as the action that the user will tap to turn on the feature. [CHAR LIMIT=40]-->
<string name="confirmation_turn_on">Turn on</string>
+ <!-- Connected devices screen, setting option name to change bluetooth timeout -->
+ <string name="bluetooth_timeout">Bluetooth timeout</string>
+
+ <!-- Connected devices screen, setting option summary to change bluetooth timeout -->
+ <string name="bluetooth_timeout_summary">Bluetooth will turn off after <xliff:g id="timeout_description">%1$s</xliff:g> if no devices connected</string>
+ <string name="bluetooth_timeout_summary2">Do not automatically turn off Bluetooth</string>
+ <string name="bluetooth_timeout_summary_never">Never</string>
+ <string name="bluetooth_timeout_summary_15secs">15 seconds</string>
+ <string name="bluetooth_timeout_summary_30secs">30 seconds</string>
+ <string name="bluetooth_timeout_summary_1min">1 minute</string>
+ <string name="bluetooth_timeout_summary_2mins">2 minutes</string>
+ <string name="bluetooth_timeout_summary_5mins">5 minutes</string>
+ <string name="bluetooth_timeout_summary_10mins">10 minutes</string>
+ <string name="bluetooth_timeout_summary_30mins">30 minutes</string>
+ <string name="bluetooth_timeout_summary_1hour">1 hour</string>
+ <string name="bluetooth_timeout_summary_2hours">2 hours</string>
+ <string name="bluetooth_timeout_summary_4hours">4 hours</string>
+ <string name="bluetooth_timeout_summary_8hours">8 hours</string>
+
<!-- Device Info screen. Used for a status item's value when the proper value is not known -->
<string name="device_info_default">Unknown</string>
<!-- String for removal of sensitive info on about, depending on tap -->
diff --git a/res/xml/connected_devices.xml b/res/xml/connected_devices.xml
index 95aa8773c68..d66915369e4 100644
--- a/res/xml/connected_devices.xml
+++ b/res/xml/connected_devices.xml
@@ -60,6 +60,14 @@
settings:useAdminDisabledSummary="true"
settings:userRestriction="no_config_bluetooth" />
+ <androidx.preference.ListPreference
+ android:key="bluetooth_timeout"
+ android:title="@string/bluetooth_timeout"
+ android:summary="@string/summary_placeholder"
+ android:entries="@array/bluetooth_timeout_entries"
+ android:entryValues="@array/bluetooth_timeout_values"
+ settings:controller="com.android.settings.bluetooth.BluetoothTimeoutPreferenceController"/>
+
<PreferenceCategory
android:key="previously_connected_devices"
android:title="@string/connected_device_saved_title"
diff --git a/src/com/android/settings/bluetooth/BluetoothTimeoutPreferenceController.java b/src/com/android/settings/bluetooth/BluetoothTimeoutPreferenceController.java
new file mode 100644
index 00000000000..244147948aa
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothTimeoutPreferenceController.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2020 The Calyx Institute
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.core.PreferenceControllerMixin;
+
+public class BluetoothTimeoutPreferenceController extends BasePreferenceController implements
+ PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
+ private static final String TAG = "BluetoothTimeoutPrefCtrl";
+
+ public static final int FALLBACK_BLUETOOTH_TIMEOUT_VALUE = 0;
+
+ private final String mBluetoothTimeoutKey;
+
+ protected BluetoothAdapter mBluetoothAdapter;
+
+ public BluetoothTimeoutPreferenceController(Context context, String key) {
+ super(context, key);
+ mBluetoothTimeoutKey = key;
+
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (mBluetoothAdapter == null) {
+ Log.e(TAG, "Bluetooth is not supported on this device");
+ return;
+ }
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return mBluetoothAdapter != null ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return mBluetoothTimeoutKey;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ final ListPreference timeoutListPreference = (ListPreference) preference;
+ final long currentTimeout = Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.BLUETOOTH_OFF_TIMEOUT, FALLBACK_BLUETOOTH_TIMEOUT_VALUE);
+ timeoutListPreference.setValue(String.valueOf(currentTimeout));
+ updateTimeoutPreferenceDescription(timeoutListPreference,
+ Long.parseLong(timeoutListPreference.getValue()));
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ try {
+ long value = Long.parseLong((String) newValue);
+ Settings.Global.putLong(mContext.getContentResolver(), Settings.Global.BLUETOOTH_OFF_TIMEOUT, value);
+ updateTimeoutPreferenceDescription((ListPreference) preference, value);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "could not persist bluetooth timeout setting", e);
+ }
+ return true;
+ }
+
+ public static CharSequence getTimeoutDescription(
+ long currentTimeout, CharSequence[] entries, CharSequence[] values) {
+ if (currentTimeout < 0 || entries == null || values == null
+ || values.length != entries.length) {
+ return null;
+ }
+
+ for (int i = 0; i < values.length; i++) {
+ long timeout = Long.parseLong(values[i].toString());
+ if (currentTimeout == timeout) {
+ return entries[i];
+ }
+ }
+ return null;
+ }
+
+ private void updateTimeoutPreferenceDescription(ListPreference preference,
+ long currentTimeout) {
+ final CharSequence[] entries = preference.getEntries();
+ final CharSequence[] values = preference.getEntryValues();
+ final CharSequence timeoutDescription = getTimeoutDescription(
+ currentTimeout, entries, values);
+ String summary = "";
+ if (timeoutDescription != null) {
+ if (currentTimeout != 0)
+ summary = mContext.getString(R.string.bluetooth_timeout_summary, timeoutDescription);
+ else
+ summary = mContext.getString(R.string.bluetooth_timeout_summary2);
+ }
+ preference.setSummary(summary);
+ }
+}
@@ -0,0 +1,238 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Oliver Scott <olivercscott@gmail.com>
Date: Thu, 25 Feb 2021 11:31:14 -0500
Subject: [PATCH] add Wi-Fi timeout feature
---
res/values/arrays.xml | 44 +++++++
res/values/strings.xml | 19 +++
res/xml/wifi_configure_settings.xml | 8 ++
.../wifi/WifiTimeoutPreferenceController.java | 115 ++++++++++++++++++
4 files changed, 186 insertions(+)
create mode 100644 src/com/android/settings/wifi/WifiTimeoutPreferenceController.java
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 1a2796ad276..9344280a905 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -62,6 +62,50 @@
<item>28800000</item>
</string-array>
+ <!-- Wifi settings. The delay in inactivity before wifi is turned off. These are shown in a list dialog. -->
+ <string-array name="wifi_timeout_entries">
+ <item>@string/wifi_timeout_summary_never</item>
+ <item>@string/wifi_timeout_summary_15secs</item>
+ <item>@string/wifi_timeout_summary_30secs</item>
+ <item>@string/wifi_timeout_summary_1min</item>
+ <item>@string/wifi_timeout_summary_2mins</item>
+ <item>@string/wifi_timeout_summary_5mins</item>
+ <item>@string/wifi_timeout_summary_10mins</item>
+ <item>@string/wifi_timeout_summary_30mins</item>
+ <item>@string/wifi_timeout_summary_1hour</item>
+ <item>@string/wifi_timeout_summary_2hours</item>
+ <item>@string/wifi_timeout_summary_4hours</item>
+ <item>@string/wifi_timeout_summary_8hours</item>
+ </string-array>
+
+ <!-- Do not translate. -->
+ <string-array name="wifi_timeout_values" translatable="false">
+ <!-- Do not translate. -->
+ <item>0</item>
+ <!-- Do not translate. -->
+ <item>15000</item>
+ <!-- Do not translate. -->
+ <item>30000</item>
+ <!-- Do not translate. -->
+ <item>60000</item>
+ <!-- Do not translate. -->
+ <item>120000</item>
+ <!-- Do not translate. -->
+ <item>300000</item>
+ <!-- Do not translate. -->
+ <item>600000</item>
+ <!-- Do not translate. -->
+ <item>1800000</item>
+ <!-- Do not translate. -->
+ <item>3600000</item>
+ <!-- Do not translate. -->
+ <item>7200000</item>
+ <!-- Do not translate. -->
+ <item>14400000</item>
+ <!-- Do not translate. -->
+ <item>28800000</item>
+ </string-array>
+
<!-- Display settings. The delay in inactivity before the screen is turned off. These are shown in a list dialog. -->
<string-array name="screen_timeout_entries">
<item>15 seconds</item>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cf41793fd42..1c3f864c956 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -46,6 +46,25 @@
<string name="bluetooth_timeout_summary_4hours">4 hours</string>
<string name="bluetooth_timeout_summary_8hours">8 hours</string>
+ <!-- screen, setting option name to change wifi timeout -->
+ <string name="wifi_timeout">Turn off Wi-Fi automatically</string>
+
+ <!-- screen, setting option summary to change wifi timeout -->
+ <string name="wifi_timeout_summary">Wi-Fi will turn off after <xliff:g id="timeout_description">%1$s</xliff:g> if no network connected</string>
+ <string name="wifi_timeout_summary2">Disabled</string>
+ <string name="wifi_timeout_summary_never">Never</string>
+ <string name="wifi_timeout_summary_15secs">15 seconds</string>
+ <string name="wifi_timeout_summary_30secs">30 seconds</string>
+ <string name="wifi_timeout_summary_1min">1 minute</string>
+ <string name="wifi_timeout_summary_2mins">2 minutes</string>
+ <string name="wifi_timeout_summary_5mins">5 minutes</string>
+ <string name="wifi_timeout_summary_10mins">10 minutes</string>
+ <string name="wifi_timeout_summary_30mins">30 minutes</string>
+ <string name="wifi_timeout_summary_1hour">1 hour</string>
+ <string name="wifi_timeout_summary_2hours">2 hours</string>
+ <string name="wifi_timeout_summary_4hours">4 hours</string>
+ <string name="wifi_timeout_summary_8hours">8 hours</string>
+
<!-- Device Info screen. Used for a status item's value when the proper value is not known -->
<string name="device_info_default">Unknown</string>
<!-- String for removal of sensitive info on about, depending on tap -->
diff --git a/res/xml/wifi_configure_settings.xml b/res/xml/wifi_configure_settings.xml
index 5866c5898d9..663e3109d76 100644
--- a/res/xml/wifi_configure_settings.xml
+++ b/res/xml/wifi_configure_settings.xml
@@ -26,6 +26,14 @@
android:summary="@string/wifi_wakeup_summary"
settings:controller="com.android.settings.wifi.WifiWakeupPreferenceController"/>
+ <ListPreference
+ android:key="wifi_timeout"
+ android:title="@string/wifi_timeout"
+ android:summary="@string/wifi_timeout_summary"
+ android:entries="@array/wifi_timeout_entries"
+ android:entryValues="@array/wifi_timeout_values"
+ settings:controller="com.android.settings.wifi.WifiTimeoutPreferenceController"/>
+
<SwitchPreferenceCompat
android:key="notify_open_networks"
android:title="@string/wifi_notify_open_networks"
diff --git a/src/com/android/settings/wifi/WifiTimeoutPreferenceController.java b/src/com/android/settings/wifi/WifiTimeoutPreferenceController.java
new file mode 100644
index 00000000000..7116c905190
--- /dev/null
+++ b/src/com/android/settings/wifi/WifiTimeoutPreferenceController.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2020 The Calyx Institute
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.wifi;
+
+import android.content.Context;
+import android.net.wifi.WifiManager;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.core.PreferenceControllerMixin;
+
+public class WifiTimeoutPreferenceController extends BasePreferenceController implements
+ PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
+ private static final String TAG = "WifiTimeoutPrefCtrl";
+
+ public static final int FALLBACK_WIFI_TIMEOUT_VALUE = 0;
+
+ private final String mWifiTimeoutKey;
+
+ protected WifiManager mWifiManager;
+
+ public WifiTimeoutPreferenceController(Context context, String key) {
+ super(context, key);
+ mWifiTimeoutKey = key;
+
+ mWifiManager = context.getSystemService(WifiManager.class);
+ if (mWifiManager == null) {
+ Log.e(TAG, "Wifi is not supported on this device");
+ return;
+ }
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return mWifiManager != null ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return mWifiTimeoutKey;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ final ListPreference timeoutListPreference = (ListPreference) preference;
+ final long currentTimeout = Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.WIFI_OFF_TIMEOUT, FALLBACK_WIFI_TIMEOUT_VALUE);
+ timeoutListPreference.setValue(String.valueOf(currentTimeout));
+ updateTimeoutPreferenceDescription(timeoutListPreference,
+ Long.parseLong(timeoutListPreference.getValue()));
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ try {
+ long value = Long.parseLong((String) newValue);
+ Settings.Global.putLong(mContext.getContentResolver(), Settings.Global.WIFI_OFF_TIMEOUT, value);
+ updateTimeoutPreferenceDescription((ListPreference) preference, value);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "could not persist wifi timeout setting", e);
+ }
+ return true;
+ }
+
+ public static CharSequence getTimeoutDescription(
+ long currentTimeout, CharSequence[] entries, CharSequence[] values) {
+ if (currentTimeout < 0 || entries == null || values == null
+ || values.length != entries.length) {
+ return null;
+ }
+
+ for (int i = 0; i < values.length; i++) {
+ long timeout = Long.parseLong(values[i].toString());
+ if (currentTimeout == timeout) {
+ return entries[i];
+ }
+ }
+ return null;
+ }
+
+ private void updateTimeoutPreferenceDescription(ListPreference preference,
+ long currentTimeout) {
+ final CharSequence[] entries = preference.getEntries();
+ final CharSequence[] values = preference.getEntryValues();
+ final CharSequence timeoutDescription = getTimeoutDescription(
+ currentTimeout, entries, values);
+ String summary = "";
+ if (timeoutDescription != null) {
+ if (currentTimeout != 0)
+ summary = mContext.getString(R.string.wifi_timeout_summary, timeoutDescription);
+ else
+ summary = mContext.getString(R.string.wifi_timeout_summary2);
+ }
+ preference.setSummary(summary);
+ }
+}
@@ -0,0 +1,160 @@
#!/usr/bin/env python3
"""Port AXP auto-reboot, Wi-Fi/Bluetooth timeouts, and sensitive QS tiles.
AXP Patch.sh order @ 7b735844, applied with --fuzz=0:
0014-Automatic_Reboot.patch
0015-System_Server_Extensions.patch
0015-WiFi_Timeout.patch
0015-Bluetooth_Timeout.patch
0015-Bluetooth_Timeout-Fix.patch
0041-tile_restrictions.patch
A16 adaptations after the exact hunks:
* privapp-permissions gained READ_SYSTEM_GRAMMATICAL_GENDER, so the REBOOT
line is inserted between REAL_GET_TASKS and REQUEST_NETWORK_SCORES.
* onKeyguardExitFinished() now dismisses the keyguard interactor before the
closing brace. The AXP cancel stays immediately after sendUserPresentBroadcast().
* QSTileImpl no longer imports QsInCompose, so KeyguardStateController is
imported after QSLogger.
"""
from __future__ import annotations
import os
import pathlib
import subprocess
import sys
TREE = pathlib.Path(os.environ.get("SUNFISH_A16_TREE", "/srv/rom/src/lineage"))
PATCH_DIR = pathlib.Path(__file__).resolve().parent / "a15-src" / "frameworks_base"
BASE = TREE / "frameworks/base"
XML = BASE / "data/etc/com.android.systemui.xml"
KEYGUARD = (
BASE / "packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java"
)
QSTILE = BASE / "packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java"
XML_OLD = """ <permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.REQUEST_NETWORK_SCORES"/>
"""
XML_NEW = """ <permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.REBOOT"/>
<permission name="android.permission.REQUEST_NETWORK_SCORES"/>
"""
KEYGUARD_OLD = """ adjustStatusBarLocked();
sendUserPresentBroadcast();
if (!KeyguardWmStateRefactor.isEnabled()) {
mKeyguardInteractor.dismissKeyguard();
}
"""
KEYGUARD_NEW = """ adjustStatusBarLocked();
sendUserPresentBroadcast();
cancelDoRebootForOwnerAfterTimeoutIfEnabled();
if (!KeyguardWmStateRefactor.isEnabled()) {
mKeyguardInteractor.dismissKeyguard();
}
"""
IMPORT_OLD = "import com.android.systemui.qs.logging.QSLogger;\n"
IMPORT_NEW = (
"import com.android.systemui.qs.logging.QSLogger;\n"
"import com.android.systemui.statusbar.policy.KeyguardStateController;\n"
)
def replace_once(path: pathlib.Path, old: str, new: str) -> None:
text = path.read_text()
if new in text:
return
if old not in text:
sys.exit(f"anchor missing in {path}")
path.write_text(text.replace(old, new, 1))
def apply_patch(name: str) -> None:
patch = PATCH_DIR / name
if not patch.is_file():
sys.exit(f"missing {patch}")
result = subprocess.run(
[
"patch",
"-p1",
"--forward",
"--fuzz=0",
"--batch",
"--no-backup-if-mismatch",
"-d",
str(BASE),
"-i",
str(patch),
],
check=False,
text=True,
capture_output=True,
)
sys.stdout.write(result.stdout)
sys.stdout.write(result.stderr)
if result.returncode != 0 and "FAILED" in result.stderr + result.stdout:
print(f"patch_incomplete: {name}")
elif result.returncode != 0:
print(f"patch_exit_{result.returncode}: {name}")
def clean_rejects() -> None:
for path in (XML, KEYGUARD, QSTILE):
for suffix in (".rej", ".orig"):
extra = path.with_name(path.name + suffix)
if extra.exists():
extra.unlink()
def main() -> None:
apply_patch("0014-Automatic_Reboot.patch")
replace_once(XML, XML_OLD, XML_NEW)
replace_once(KEYGUARD, KEYGUARD_OLD, KEYGUARD_NEW)
for name in (
"0015-System_Server_Extensions.patch",
"0015-WiFi_Timeout.patch",
"0015-Bluetooth_Timeout.patch",
"0015-Bluetooth_Timeout-Fix.patch",
"0041-tile_restrictions.patch",
):
apply_patch(name)
replace_once(QSTILE, IMPORT_OLD, IMPORT_NEW)
clean_rejects()
required = [
"settings_reboot_after_timeout",
"android.permission.REBOOT",
"cancelDoRebootForOwnerAfterTimeoutIfEnabled();",
"class SystemServerExt",
"class WifiAutoOff",
"class BluetoothAutoOff",
"import android.bluetooth.BluetoothProfile;",
"WIFI_OFF_TIMEOUT",
"BLUETOOTH_OFF_TIMEOUT",
"isAllowedWhenLocked",
"KeyguardStateController",
]
blob = "\n".join(
path.read_text()
for path in (
BASE / "core/java/android/provider/Settings.java",
XML,
KEYGUARD,
BASE / "services/core/java/com/android/server/ext/SystemServerExt.java",
BASE / "services/core/java/com/android/server/ext/WifiAutoOff.java",
BASE / "services/core/java/com/android/server/ext/BluetoothAutoOff.java",
QSTILE,
)
)
missing = [item for item in required if item not in blob]
if missing:
sys.exit("missing after apply: " + ", ".join(missing))
print("done: SUNFISH_A16_009_A15_REBOOT_TIMEOUTS")
if __name__ == "__main__":
main()
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""Settings UI for the AXP reboot and radio timeouts.
Apply after apply_009_a15_reboot_timeouts.py. Order matches AXP:
bluetooth arrays land before the Wi-Fi arrays, which insert after them.
0006 needs fuzz 2. A16's strings.xml has an empty line where the A15
patch has a single space, and connected_devices.xml shifted by a few
lines. The preference still lands on the connected-devices screen.
"""
from __future__ import annotations
import os
import pathlib
import subprocess
import sys
TREE = pathlib.Path(os.environ.get("SUNFISH_A16_TREE", "/srv/rom/src/lineage"))
PATCH_DIR = pathlib.Path(__file__).resolve().parent / "a15-src" / "settings"
SETTINGS = TREE / "packages/apps/Settings"
MARKERS = (
"res/xml/security_dashboard_settings.xml",
"res/xml/connected_devices.xml",
"res/xml/wifi_configure_settings.xml",
"res/values/arrays.xml",
"res/values/strings.xml",
)
def already_applied() -> bool:
try:
blob = "\n".join((SETTINGS / name).read_text() for name in MARKERS)
except OSError:
return False
needles = (
'android:key="auto_reboot"',
'android:key="bluetooth_timeout"',
'android:key="wifi_timeout"',
'name="auto_reboot_entries"',
'name="bluetooth_timeout_entries"',
'name="wifi_timeout_entries"',
)
return all(needle in blob for needle in needles)
def apply_patch(name: str, fuzz: str) -> None:
result = subprocess.run(
[
"patch",
"-p1",
"--forward",
f"--fuzz={fuzz}",
"--batch",
"--no-backup-if-mismatch",
"-d",
str(SETTINGS),
"-i",
str(PATCH_DIR / name),
],
check=False,
text=True,
capture_output=True,
)
sys.stdout.write(result.stdout)
sys.stdout.write(result.stderr)
for path in SETTINGS.rglob("*.rej"):
path.unlink()
if "FAILED" in result.stdout + result.stderr and not already_applied():
sys.exit(f"failed: {name}")
def main() -> None:
if already_applied():
print("done: SUNFISH_A16_009_A15_SETTINGS_TIMEOUTS")
return
apply_patch("0005-Automatic_Reboot.patch", "0")
apply_patch("0006-Bluetooth_Timeout.patch", "2")
apply_patch("0007-WiFi_Timeout.patch", "0")
if not already_applied():
sys.exit("missing settings timeout markers")
print("done: SUNFISH_A16_009_A15_SETTINGS_TIMEOUTS")
if __name__ == "__main__":
main()
@@ -0,0 +1,155 @@
#!/usr/bin/env python3
"""Port four AXP LOS 22.2 userspace changes onto LOS 23 / Android 16.
Sources, Patch.sh @ 7b735844 (AXP A15):
android_frameworks_base/0004-Fingerprint_Lockout.patch
android_frameworks_base/0007-Always_Restict_Serial.patch
android_frameworks_base/0042-minimal_screenshot_exif.patch
android_bootable_recovery/0001-No_SerialNum_Restrictions.patch
Sunfish fingerprint HAL is HIDL 2.2, and HidlToAidlSensorAdapter still uses
LockoutFrameworkImpl, so the AXP constant change is the live path.
A16 adaptations, behavior unchanged:
* CheckPackageMetadata returns bool, so a serial constraint returns false.
The A15 patch returned INSTALL_ERROR, which does not compile here.
* Screenshot EXIF drops the now-unused Build and DateTimeFormatter imports.
A16 ErrorProne treats unused imports as errors.
Already satisfied on this tree, so this script does not touch them:
* 0028-Remove_Legacy_Package_Query: A16 already removed the
MATCH_UNINSTALLED_PACKAGES / MATCH_ANY_USER branch.
* system/core 0002-ptrace_scope: apply_009_yama_ptrace.py already writes
persist.native_debug.
"""
from __future__ import annotations
import os
import pathlib
import sys
TREE = pathlib.Path(os.environ.get("SUNFISH_A16_TREE", "/srv/rom/src/lineage"))
LOCKOUT = (
TREE
/ "frameworks/base/services/core/java/com/android/server/biometrics"
/ "sensors/fingerprint/hidl/LockoutFrameworkImpl.java"
)
AMS = (
TREE
/ "frameworks/base/services/core/java/com/android/server/am"
/ "ActivityManagerService.java"
)
EXIF = (
TREE
/ "frameworks/base/packages/SystemUI/src/com/android/systemui"
/ "screenshot/ImageExporter.java"
)
RECOVERY = TREE / "bootable/recovery/install/install.cpp"
LOCKOUT_OLD = "private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT = 20;"
LOCKOUT_NEW = "private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT = 5;"
SERIAL_OLD = """ // We deprecated Build.SERIAL and it is not accessible to
// Instant Apps and target APIs higher than O MR1. Since access to the serial
// is now behind a permission we push down the value.
final String buildSerial = (!appInfo.isInstantApp()
&& appInfo.targetSdkVersion < Build.VERSION_CODES.P)
? sTheRealBuildSerial : Build.UNKNOWN;
"""
SERIAL_NEW = """ // AXP/GrapheneOS 0007: deprecated Build.SERIAL stays UNKNOWN.
// Callers use Build.getSerial(), which requires READ_PHONE_STATE.
final String buildSerial = Build.UNKNOWN;
"""
EXIF_OLD = """ exif.setAttribute(ExifInterface.TAG_IMAGE_UNIQUE_ID, uniqueId.toString());
exif.setAttribute(ExifInterface.TAG_SOFTWARE, "Android " + Build.DISPLAY);
exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, Integer.toString(width));
exif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, Integer.toString(height));
String dateTime = DateTimeFormatter.ofPattern("yyyy:MM:dd HH:mm:ss").format(captureTime);
String subSec = DateTimeFormatter.ofPattern("SSS").format(captureTime);
String timeZone = DateTimeFormatter.ofPattern("xxx").format(captureTime);
exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, dateTime);
exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, subSec);
exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL, timeZone);
"""
EXIF_NEW = """ // AXP/Calyx 0042: width and height only. No OS name, unique id, or time.
exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, Integer.toString(width));
exif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, Integer.toString(height));
"""
RECOVERY_OLD = """ // We allow the package to not have any serialno; and we also allow it to carry multiple serial
// numbers split by "|"; e.g. serialno=serialno1|serialno2|serialno3 ... We will fail the
// verification if the device's serialno doesn't match any of these carried numbers.
auto pkg_serial_no = get_value(metadata, "serialno");
if (!pkg_serial_no.empty()) {
auto device_serial_no = android::base::GetProperty("ro.serialno", "");
bool serial_number_match = false;
for (const auto& number : android::base::Split(pkg_serial_no, "|")) {
if (device_serial_no == android::base::Trim(number)) {
serial_number_match = true;
}
}
if (!serial_number_match) {
LOG(ERROR) << "Package is for serial " << pkg_serial_no;
return false;
}
} else if (ota_type == OtaType::BRICK) {
"""
RECOVERY_NEW = """ // AXP/GrapheneOS 0001: any serialno constraint is rejected.
// A16 CheckPackageMetadata returns bool; A15 returned INSTALL_ERROR.
auto pkg_serial_no = get_value(metadata, "serialno");
if (!pkg_serial_no.empty()) {
LOG(ERROR) << "Serial number constraint not permitted: " << pkg_serial_no;
return false;
} else if (ota_type == OtaType::BRICK) {
"""
def replace_once(path: pathlib.Path, old: str, new: str) -> None:
text = path.read_text()
if new in text:
return
if old not in text:
sys.exit(f"anchor missing in {path}")
path.write_text(text.replace(old, new, 1))
def drop_import(text: str, import_line: str, still_used: str) -> str:
if import_line not in text:
return text
body = text.replace(import_line + "\n", "", 1)
if still_used in body:
return text
return body
def apply_exif() -> None:
text = EXIF.read_text()
if EXIF_NEW in text:
updated = text
elif EXIF_OLD not in text:
sys.exit(f"anchor missing in {EXIF}")
else:
updated = text.replace(EXIF_OLD, EXIF_NEW, 1)
updated = drop_import(updated, "import android.os.Build;", "Build.")
updated = drop_import(
updated, "import java.time.format.DateTimeFormatter;", "DateTimeFormatter"
)
EXIF.write_text(updated)
def main() -> None:
replace_once(LOCKOUT, LOCKOUT_OLD, LOCKOUT_NEW)
replace_once(AMS, SERIAL_OLD, SERIAL_NEW)
apply_exif()
replace_once(RECOVERY, RECOVERY_OLD, RECOVERY_NEW)
print("done: SUNFISH_A16_009_A15_SMALL")
if __name__ == "__main__":
main()
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""009: AXP ship identity on LOS 23.2.
- AXP Patch.sh (LineageOS-22.2:405) replaces LINEAGE_BUILDTYPE := UNOFFICIAL
with its own build type in vendor/lineage/config/*.mk. Same here, with PRO
(GOLDEN ro.lineage.releasetype=PRO).
- Drops the old TARGET_UNOFFICIAL_BUILD_ID insert from lineage_sunfish.mk.
- Vendor SPL stays the LOS 23.2 2023-09-05 (AXP GOLDEN ships 2023-08-05).
- build/make/core/config.mk only tags release-keys when the default cert is
under vendor/lineage-priv/. Copies the project certs there (same keys, so
an existing install updates without a wipe).
"""
from __future__ import annotations
import pathlib
import shutil
import sys
TREE = pathlib.Path("/srv/rom/src/lineage")
SRC_CERTS = TREE / "vendor/sunfish-a16/certs"
DST_CERTS = TREE / "vendor/lineage-priv/keys"
OLD_ID = (
"# sunfish-a16: must precede vendor/lineage version.mk (via inherit below)\n"
"TARGET_UNOFFICIAL_BUILD_ID := AXP-Resurection-Pro\n\n"
)
# LOS 23.2 writes 2023-09-05. AXP GOLDEN uses 2023-08-05, but keymaster key
# blobs on an existing install are bound to the vendor patch level and refuse
# a rollback: vdc cryptfs init_user0 fails and recovery asks for a wipe
# (LAB 2026-10-07 17:15 build). Never lower the vendor SPL on an upgrade.
VENDOR_SPL_LOS = (
"# Set Vendor SPL to match platform\n"
"VENDOR_SECURITY_PATCH = 2023-09-05\n"
)
VENDOR_SPL_ROLLBACK = (
"# Stock sunfish vendor SPL (TQ3A.230805), same as AXP GOLDEN.\n"
"# System PLATFORM_SECURITY_PATCH stays the LOS 23.2 ASB.\n"
"VENDOR_SECURITY_PATCH = 2023-08-05\n"
)
def main() -> None:
version = TREE / "vendor/lineage/config/version.mk"
text = version.read_text()
if "LINEAGE_BUILDTYPE := PRO\n" not in text:
old = " LINEAGE_BUILDTYPE := UNOFFICIAL\n"
if old not in text:
sys.exit(f"anchor missing in {version}")
version.write_text(text.replace(old, " LINEAGE_BUILDTYPE := PRO\n", 1))
device_mk = TREE / "device/google/sunfish/lineage_sunfish.mk"
text = device_mk.read_text()
if OLD_ID in text:
device_mk.write_text(text.replace(OLD_ID, "", 1))
if "TARGET_UNOFFICIAL_BUILD_ID" in device_mk.read_text():
sys.exit(f"TARGET_UNOFFICIAL_BUILD_ID still set in {device_mk}")
common = TREE / "device/google/sunfish/device-common.mk"
text = common.read_text()
if VENDOR_SPL_ROLLBACK in text:
text = text.replace(VENDOR_SPL_ROLLBACK, VENDOR_SPL_LOS, 1)
common.write_text(text)
if VENDOR_SPL_LOS not in text:
sys.exit(f"vendor SPL is not 2023-09-05 in {common}")
if not (SRC_CERTS / "releasekey.pk8").is_file():
sys.exit(f"missing {SRC_CERTS}/releasekey.pk8")
DST_CERTS.mkdir(parents=True, exist_ok=True)
for src in SRC_CERTS.iterdir():
if src.name.startswith("avb"):
continue
dst = DST_CERTS / src.name
if dst.is_symlink() or dst.exists():
dst.unlink()
if src.is_symlink():
dst.symlink_to(src.readlink())
else:
shutil.copy2(src, dst)
print("axp identity applied")
if __name__ == "__main__":
main()
@@ -0,0 +1,46 @@
#!/usr/bin/env python3
"""Hide /proc/cmdline from unprivileged processes.
AOSP and GOLDEN's init do this in first-stage init, immediately after
mounting proc:
CHECKCALL(chmod("/proc/cmdline", 0440));
The failure text is exactly `chmod("/proc/cmdline", 0440) failed`, which
is present in GOLDEN's /system/bin/init and absent from the LOS 23.2 init
that left the file mode 0444. LOS 23.2 already chmod's /proc/bootconfig
the same way. init.rc only chowns cmdline to root:radio.
"""
from __future__ import annotations
import os
import pathlib
import sys
TREE = pathlib.Path(os.environ.get("ANDROID_BUILD_TOP", "/srv/rom/src/lineage"))
INIT = TREE / "system/core/init/first_stage_init.cpp"
OLD = """#undef MAKE_STR
std::string cmdline;
android::base::ReadFileToString("/proc/cmdline", &cmdline);
"""
NEW = """#undef MAKE_STR
// Don't expose the raw commandline to unprivileged processes.
CHECKCALL(chmod("/proc/cmdline", 0440));
std::string cmdline;
android::base::ReadFileToString("/proc/cmdline", &cmdline);
"""
def main() -> None:
text = INIT.read_text()
if NEW in text:
print(f"already applied: {INIT}")
return
if OLD not in text:
sys.exit(f"cmdline chmod anchor missing in {INIT}")
INIT.write_text(text.replace(OLD, NEW, 1))
print(f"patched cmdline mode: {INIT}")
if __name__ == "__main__":
main()
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""Drop Google carrier apps and the AOSP IMS entitlement/RCS packages.
inherit-product merges PRODUCT_PACKAGES per makefile, so a filter-out never
sees names added by sunfish-vendor.mk. Remove the package lines where they
are added. CarrierSettings/*.pb copy rules stay: their tokens are paths, not
the CarrierSettings module. Qualcomm radio packages stay (ims, atfwd,
qcrilmsgtunnel, uceShimService, EuiccSupportPixel).
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
TARGETS: tuple[tuple[str, tuple[str, ...], tuple[str, ...]], ...] = (
(
"vendor/google/sunfish/sunfish-vendor.mk",
(
"CarrierServices",
"CarrierSettings",
"CarrierWifi",
"CarrierSetup",
"RilConfigService",
"grilservice",
),
(
"EuiccSupportPixel",
"EuiccSupportPixelPermissions",
"ims",
"QtiTelephonyService",
"atfwd",
"qcrilmsgtunnel",
"uceShimService",
),
),
(
"device/google/sunfish/device-lineage.mk",
("PresencePolling", "RcsService"),
("GoogleParts",),
),
(
"build/make/target/product/telephony_product.mk",
("ImsServiceEntitlement",),
("Dialer",),
),
)
_RCS_STUB = "# RCS\nPRODUCT_PACKAGES += \\\n\n"
def _token(line: str) -> str:
return line.strip().rstrip("\\").strip()
def _has(text: str, name: str) -> bool:
return any(_token(line) == name for line in text.splitlines())
def apply_file(path: Path, packages: tuple[str, ...], keep: tuple[str, ...]) -> str:
text = path.read_text()
for name in keep:
if not _has(text, name):
raise SystemExit(f"FAIL: {path} lost {name}")
removed = {name: 0 for name in packages}
kept: list[str] = []
for line in text.splitlines(keepends=True):
token = _token(line)
if token in removed:
removed[token] += 1
continue
kept.append(line)
if all(count == 0 for count in removed.values()):
return "already"
if any(count != 1 for count in removed.values()):
raise SystemExit(f"FAIL: unexpected removals in {path}: {removed}")
new = "".join(kept)
if path.name == "device-lineage.mk":
new = new.replace(_RCS_STUB, "", 1)
for name in keep:
if not _has(new, name):
raise SystemExit(f"FAIL: edit would drop {name} from {path}")
for name in packages:
if _has(new, name):
raise SystemExit(f"FAIL: {name} still listed in {path}")
path.write_text(new)
return "dropped"
def main() -> int:
top = Path(os.environ.get("ANDROID_BUILD_TOP", ".")).resolve()
results: list[str] = []
for rel, packages, keep in TARGETS:
path = top / rel
if not path.is_file():
print(f"FAIL: missing {path}", file=sys.stderr)
return 1
state = apply_file(path, packages, keep)
results.append(f"{state} {rel}")
print("OK: " + "; ".join(results))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,68 @@
#!/usr/bin/env python3
"""Drop the Google eUICC LPA from the sunfish vendor makefile.
inherit-product merges PRODUCT_PACKAGES per makefile, so a filter-out in
apps.mk never sees the names added by sunfish-vendor.mk. A Soong phony
does not set PACKAGES.*.OVERRIDES. The 20261009T073753Z and 20261009T090831Z
zips still installed both APKs. Remove the two entries where they are added.
EuiccSupportPixel and EuiccSupportPixelPermissions stay.
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
PACKAGES = ("EuiccGoogle", "EuiccGoogleOverlay")
KEEP = ("EuiccSupportPixel", "EuiccSupportPixelPermissions")
def _package_line(line: str) -> str | None:
stripped = line.strip().rstrip("\\").strip()
if stripped in PACKAGES:
return stripped
return None
def main() -> int:
top = Path(os.environ.get("ANDROID_BUILD_TOP", ".")).resolve()
mk = top / "vendor/google/sunfish/sunfish-vendor.mk"
if not mk.is_file():
print(f"FAIL: missing {mk}", file=sys.stderr)
return 1
text = mk.read_text()
for name in KEEP:
if name not in text:
print(f"FAIL: {mk} lost {name}", file=sys.stderr)
return 1
removed = {name: 0 for name in PACKAGES}
kept: list[str] = []
for line in text.splitlines(keepends=True):
hit = _package_line(line)
if hit is None:
kept.append(line)
continue
removed[hit] += 1
if all(count == 0 for count in removed.values()):
print(f"OK: already applied ({mk})")
return 0
if any(count != 1 for count in removed.values()):
print(f"FAIL: unexpected removals {removed}", file=sys.stderr)
return 1
new = "".join(kept)
for name in KEEP:
if name not in new:
print(f"FAIL: edit would drop {name}", file=sys.stderr)
return 1
for name in PACKAGES:
if f" {name} \\" in new or f" {name}\n" in new:
print(f"FAIL: {name} still listed", file=sys.stderr)
return 1
mk.write_text(new)
print(f"OK: dropped {', '.join(PACKAGES)} from {mk}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,192 @@
#!/usr/bin/env python3
"""Drop commands present on LAB and absent on GOLDEN.
Keeps the GPS binaries loc_launcher, lowi-server and xtra-daemon.
bpftool, alloctop and apexd-lsof.sh are not product packages: bpfloader,
dumpstate and apexd require them. Remove only those required entries.
pbtombstone stays in PRODUCT_HOST_PACKAGES; only the system copy goes.
Qualcomm diag binaries and imsrcsd go. imsdatadaemon and the other IMS
call daemons stay.
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
MAKE_DROPS: tuple[tuple[str, tuple[str, ...], tuple[str, ...]], ...] = (
(
"vendor/google/sunfish/sunfish-vendor.mk",
(
"cnss_diag",
"diag_klog",
"diag_mdlog",
"diag_socket_log",
"diag_uart_log",
"ssr_diag",
"test_diag",
"imsrcsd",
),
(
"loc_launcher",
"lowi-server",
"xtra-daemon",
"cnss-daemon",
"imsdatadaemon",
"imsqmidaemon",
"ims_rtp_daemon",
),
),
(
"device/google/sunfish/device-lineage.mk",
("disable_configstore",),
("chre_daemon_msm",),
),
)
HOST_SAFE = {"pbtombstone"}
BP_LINES: tuple[tuple[str, str], ...] = (
("system/bpf/loader/Android.bp", '"bpftool",'),
("frameworks/native/cmds/dumpstate/Android.bp", '"alloctop",'),
("system/apex/apexd/Android.bp", 'required: ["apexd-lsof.sh"],'),
)
def _token(line: str) -> str:
return line.strip().rstrip("\\").strip()
def _has(text: str, name: str) -> bool:
return any(_token(line) == name for line in text.splitlines())
def _assignment(line: str, current: str | None) -> str | None:
stripped = line.strip()
if stripped.startswith("PRODUCT_HOST_PACKAGES") and "+=" in stripped:
return "PRODUCT_HOST_PACKAGES"
if stripped.startswith("PRODUCT_PACKAGES") and "+=" in stripped:
return "PRODUCT_PACKAGES"
return current
def apply_make(path: Path, packages: tuple[str, ...], keep: tuple[str, ...]) -> str:
text = path.read_text()
for name in keep:
if not _has(text, name):
raise SystemExit(f"FAIL: {path} lost {name}")
removed = {name: 0 for name in packages}
rc_removed = 0
kept: list[str] = []
assignment: str | None = None
for line in text.splitlines(keepends=True):
assignment = _assignment(line, assignment)
token = _token(line)
if token in removed and not (token in HOST_SAFE and assignment == "PRODUCT_HOST_PACKAGES"):
removed[token] += 1
continue
if "imsrcsd.rc:" in line:
rc_removed += 1
continue
kept.append(line)
pending = [name for name, count in removed.items() if count == 0]
if pending and any(count for count in removed.values()):
raise SystemExit(f"FAIL: partial removal in {path}: {removed}")
if not any(count for count in removed.values()) and rc_removed == 0:
return "already"
if any(count != 1 for count in removed.values()):
raise SystemExit(f"FAIL: unexpected removals in {path}: {removed}")
if "imsrcsd" in packages and rc_removed != 1:
raise SystemExit(f"FAIL: imsrcsd.rc removals={rc_removed} in {path}")
new = "".join(kept)
for name in keep:
if not _has(new, name):
raise SystemExit(f"FAIL: edit would drop {name} from {path}")
for name in packages:
if _has(new, name):
raise SystemExit(f"FAIL: {name} still listed in {path}")
if "imsrcsd.rc:" in new:
raise SystemExit(f"FAIL: imsrcsd.rc still copied from {path}")
path.write_text(new)
return "dropped"
def apply_pbtombstone(path: Path) -> str:
text = path.read_text()
if not _has(text, "logcat") or not _has(text, "fastboot"):
raise SystemExit(f"FAIL: {path} lost logcat or fastboot")
removed = 0
kept: list[str] = []
assignment: str | None = None
for line in text.splitlines(keepends=True):
assignment = _assignment(line, assignment)
token = _token(line)
if token == "pbtombstone" and assignment == "PRODUCT_PACKAGES":
removed += 1
continue
kept.append(line)
if removed == 0:
if not any(
_token(line) == "pbtombstone" and True
for line in text.splitlines()
):
raise SystemExit(f"FAIL: {path} has no pbtombstone at all")
return "already"
if removed != 1:
raise SystemExit(f"FAIL: pbtombstone system removals={removed}")
new = "".join(kept)
host_hits = 0
assignment = None
for line in new.splitlines():
assignment = _assignment(line, assignment)
if _token(line) == "pbtombstone" and assignment == "PRODUCT_HOST_PACKAGES":
host_hits += 1
if host_hits != 1:
raise SystemExit(f"FAIL: host pbtombstone count={host_hits}")
path.write_text(new)
return "dropped"
def apply_bp(path: Path, needle: str) -> str:
text = path.read_text()
hits = [line for line in text.splitlines() if line.strip() == needle]
if not hits:
return "already"
if len(hits) != 1:
raise SystemExit(f"FAIL: {needle} appears {len(hits)} times in {path}")
new = "".join(
line for line in text.splitlines(keepends=True) if line.strip() != needle
)
if needle in new:
raise SystemExit(f"FAIL: {needle} still present in {path}")
path.write_text(new)
return "dropped"
def main() -> int:
top = Path(os.environ.get("ANDROID_BUILD_TOP", ".")).resolve()
results: list[str] = []
for rel, packages, keep in MAKE_DROPS:
path = top / rel
if not path.is_file():
print(f"FAIL: missing {path}", file=sys.stderr)
return 1
results.append(f"{apply_make(path, packages, keep)} {rel}")
pb = top / "build/make/target/product/base_system.mk"
if not pb.is_file():
print(f"FAIL: missing {pb}", file=sys.stderr)
return 1
results.append(f"{apply_pbtombstone(pb)} {pb.relative_to(top)}")
for rel, needle in BP_LINES:
path = top / rel
if not path.is_file():
print(f"FAIL: missing {path}", file=sys.stderr)
return 1
results.append(f"{apply_bp(path, needle)} {rel}")
print("OK: " + "; ".join(results))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""Drop Lineage tools that AXP.OS PRO sunfish does not ship.
GOLDEN has no /system_ext/bin/{bash,htop,nano,unrar} and no
/system/addon.d/50-lineage.sh. It does ship curl and vim, so those tokens
stay. Qualcomm diag binaries are a separate change.
inherit-product merges PRODUCT_PACKAGES per makefile, so filter-out cannot
remove names added here. Delete the exact package-token lines.
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
TARGETS: tuple[tuple[str, tuple[str, ...], tuple[str, ...]], ...] = (
(
"vendor/lineage/config/common.mk",
("bash", "htop", "nano", "50-lineage.sh"),
("curl", "getcap", "setcap", "vim", "nano_recovery"),
),
(
"vendor/lineage/config/common_mobile_full.mk",
("unrar",),
("zstd",),
),
)
def _token(line: str) -> str:
return line.strip().rstrip("\\").strip()
def _has(text: str, name: str) -> bool:
return any(_token(line) == name for line in text.splitlines())
def apply_file(path: Path, packages: tuple[str, ...], keep: tuple[str, ...]) -> str:
text = path.read_text()
for name in keep:
if not _has(text, name):
raise SystemExit(f"FAIL: {path} lost {name}")
removed = {name: 0 for name in packages}
kept: list[str] = []
for line in text.splitlines(keepends=True):
token = _token(line)
if token in removed:
removed[token] += 1
continue
kept.append(line)
if all(count == 0 for count in removed.values()):
return "already"
if any(count != 1 for count in removed.values()):
raise SystemExit(f"FAIL: unexpected removals in {path}: {removed}")
new = "".join(kept)
for name in keep:
if not _has(new, name):
raise SystemExit(f"FAIL: edit would drop {name} from {path}")
for name in packages:
if _has(new, name):
raise SystemExit(f"FAIL: {name} still listed in {path}")
path.write_text(new)
return "dropped"
def main() -> int:
top = Path(os.environ.get("ANDROID_BUILD_TOP", ".")).resolve()
results: list[str] = []
for rel, packages, keep in TARGETS:
path = top / rel
if not path.is_file():
print(f"FAIL: missing {path}", file=sys.stderr)
return 1
results.append(f"{apply_file(path, packages, keep)} {rel}")
print("OK: " + "; ".join(results))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,576 @@
#!/usr/bin/env python3
"""009: SUPL privacy, plus an optional stop for LPP / XTRA / IZat.
- Never send IMSI or MSISDN to a SUPL server (AXP/Divest), including emergency.
LOS 23 already skips that outside an emergency call.
- Settings toggle "Force disable SUPL", default off. On forces SUPL_MODE=0.
- GNSS NTP in sunfish gps.conf: 2.android.pool.ntp.org.
- Settings toggle "Stop extra location assistance", default off.
Off keeps Lineage LPP, XTRA and IZat. On sets LPP_PROFILE=0 (LPPE is
carried by that LPP session), skips the framework XTRA download, and
stops loc_launcher. The vendor binaries stay on the image.
"""
from __future__ import annotations
import pathlib
import sys
MARKER = "sunfish-a16-009-supl-privacy"
TREE = pathlib.Path("/srv/rom/src/lineage")
def must(path: pathlib.Path, old: str, new: str, already: str) -> None:
text = path.read_text()
if already in text:
return
if old not in text:
sys.exit(f"anchor missing in {path}")
path.write_text(text.replace(old, new, 1))
def main() -> None:
settings = TREE / "frameworks/base/core/java/android/provider/Settings.java"
gnss_cfg = (
TREE
/ "frameworks/base/services/core/java/com/android/server/location/gnss/GnssConfiguration.java"
)
gnss_prov = (
TREE
/ "frameworks/base/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java"
)
loc_xml = TREE / "packages/apps/Settings/res/xml/location_settings.xml"
strings = TREE / "packages/apps/Settings/res/values/strings.xml"
gps = TREE / "device/google/sunfish/gps.conf"
init_rc = TREE / "device/google/sunfish/init.hardware.rc"
controller = (
TREE
/ "packages/apps/Settings/src/com/android/settings/location/ForceDisableSuplPrefController.java"
)
must(
settings,
""" public static final String ASSISTED_GPS_ENABLED = "assisted_gps_enabled";
""",
""" public static final String ASSISTED_GPS_ENABLED = "assisted_gps_enabled";
/**
* Force disable Secure User Plane Location (SUPL), 0 or 1.
* Default 0 keeps carrier SUPL. AXP/Graphene toggle, """
+ MARKER
+ """.
* @hide
*/
@Readable
public static final String FORCE_DISABLE_SUPL = "force_disable_supl";
/** @hide */
public static final int FORCE_DISABLE_SUPL_DEFAULT = 0;
""",
already="FORCE_DISABLE_SUPL",
)
must(
settings,
""" /** @hide */
public static final int FORCE_DISABLE_SUPL_DEFAULT = 0;
""",
""" /** @hide */
public static final int FORCE_DISABLE_SUPL_DEFAULT = 0;
/**
* Stop LPP, the framework XTRA download, and loc_launcher
* (xtra-daemon, lowi, IZat). 0 or 1. Default 0 keeps Lineage
* assistance. Binaries stay installed. """
+ MARKER
+ """.
* @hide
*/
@Readable
public static final String FORCE_DISABLE_LOCATION_ASSIST =
"force_disable_location_assist";
/** @hide */
public static final int FORCE_DISABLE_LOCATION_ASSIST_DEFAULT = 0;
""",
already="FORCE_DISABLE_LOCATION_ASSIST",
)
must(
gnss_cfg,
"import android.os.SystemProperties;\n",
"import android.os.SystemProperties;\nimport android.provider.Settings;\n",
already="import android.provider.Settings;\n",
)
must(
gnss_cfg,
"import android.util.Log;\n",
"import android.util.Log;\nimport android.util.Slog;\n",
already="import android.util.Slog;\n",
)
must(
gnss_cfg,
""" loadPropertiesFromGpsDebugConfig(mProperties, DEBUG_PROPERTIES_VENDOR_FILE);
loadPropertiesFromGpsDebugConfig(mProperties, DEBUG_PROPERTIES_SYSTEM_FILE);
""",
""" loadPropertiesFromGpsDebugConfig(mProperties, DEBUG_PROPERTIES_VENDOR_FILE);
loadPropertiesFromGpsDebugConfig(mProperties, DEBUG_PROPERTIES_SYSTEM_FILE);
applyConfigOverrides(mContext, mProperties); /* """
+ MARKER
+ """ */
""",
already="applyConfigOverrides(mContext, mProperties)",
)
if "void applyConfigOverrides(" not in gnss_cfg.read_text():
text = gnss_cfg.read_text()
needle = " private static native boolean native_set_es_extension_sec(int emergencyExtensionSeconds);\n"
insert = needle + """
/** """ + MARKER + """: optional SUPL off. Default leaves carrier SUPL_MODE. */
private static void applyConfigOverrides(Context ctx, Properties props) {
String key = Settings.Global.FORCE_DISABLE_SUPL;
int def = Settings.Global.FORCE_DISABLE_SUPL_DEFAULT;
if (Settings.Global.getInt(ctx.getContentResolver(), key, def) == 1) {
props.setProperty(CONFIG_SUPL_MODE, "0");
Slog.d(TAG, "SUPL is force disabled");
}
}
"""
if needle not in text:
sys.exit("native_set_es_extension_sec missing")
gnss_cfg.write_text(text.replace(needle, insert, 1))
must(
gnss_cfg,
""" if (Settings.Global.getInt(ctx.getContentResolver(), key, def) == 1) {
props.setProperty(CONFIG_SUPL_MODE, "0");
Slog.d(TAG, "SUPL is force disabled");
}
}
""",
""" if (Settings.Global.getInt(ctx.getContentResolver(), key, def) == 1) {
props.setProperty(CONFIG_SUPL_MODE, "0");
Slog.d(TAG, "SUPL is force disabled");
}
String assist = Settings.Global.FORCE_DISABLE_LOCATION_ASSIST;
int assistDef = Settings.Global.FORCE_DISABLE_LOCATION_ASSIST_DEFAULT;
if (Settings.Global.getInt(ctx.getContentResolver(), assist, assistDef) == 1) {
// LPPE is carried by the LPP session. This HAL only has a setter
// for LPP_PROFILE, and this override runs after carrier config.
props.setProperty(CONFIG_LPP_PROFILE, "0");
Slog.d(TAG, "LPP assistance stopped");
}
}
""",
already="LPP assistance stopped",
)
must(
gnss_prov,
""" mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.ASSISTED_GPS_ENABLED),
false,
new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
toggleXtraDaemon();
}
}, UserHandle.USER_ALL);
""",
""" mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.ASSISTED_GPS_ENABLED),
false,
new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
toggleXtraDaemon();
}
}, UserHandle.USER_ALL);
/* """
+ MARKER
+ """ */
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.FORCE_DISABLE_SUPL),
false,
new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
reloadGpsProperties();
}
}, UserHandle.USER_ALL);
""",
already="Settings.Global.FORCE_DISABLE_SUPL",
)
must(
gnss_prov,
""" Settings.Global.getUriFor(Settings.Global.FORCE_DISABLE_SUPL),
false,
new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
reloadGpsProperties();
}
}, UserHandle.USER_ALL);
""",
""" Settings.Global.getUriFor(Settings.Global.FORCE_DISABLE_SUPL),
false,
new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
reloadGpsProperties();
}
}, UserHandle.USER_ALL);
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.FORCE_DISABLE_LOCATION_ASSIST),
false,
new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
reloadGpsProperties();
}
}, UserHandle.USER_ALL);
""",
already="Settings.Global.FORCE_DISABLE_LOCATION_ASSIST",
)
must(
gnss_prov,
""" toggleXtraDaemon();
}
public GnssLocationProvider(Context context, GnssNative gnssNative,
""",
""" applyLocationAssistPrivacy();
toggleXtraDaemon();
}
public GnssLocationProvider(Context context, GnssNative gnssNative,
""",
already="applyLocationAssistPrivacy();",
)
must(
gnss_prov,
""" if (!isAssistedGpsEnabled()) {
// PSDS download disabled by system setting, don't try
Log.d(TAG, "handleDownloadPsdsData() called when PSDS disabled by system setting");
return;
}
""",
""" if (!isAssistedGpsEnabled()) {
// PSDS download disabled by system setting, don't try
Log.d(TAG, "handleDownloadPsdsData() called when PSDS disabled by system setting");
return;
}
if (Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.FORCE_DISABLE_LOCATION_ASSIST,
Settings.Global.FORCE_DISABLE_LOCATION_ASSIST_DEFAULT) == 1) {
Log.d(TAG, "handleDownloadPsdsData() skipped: location assistance stopped");
return;
}
""",
already="location assistance stopped",
)
must(
gnss_prov,
""" private void toggleXtraDaemon() {
Log.i(TAG, "Toggling xtra-daemon via property");
SystemProperties.set("persist.sys.xtra-daemon.enabled",
Boolean.toString(isAssistedGpsEnabled()));
}
""",
""" /** """
+ MARKER
+ """ Stop loc_launcher. Does not delete xtra-daemon, lowi or IZat. */
private void applyLocationAssistPrivacy() {
boolean stop = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.FORCE_DISABLE_LOCATION_ASSIST,
Settings.Global.FORCE_DISABLE_LOCATION_ASSIST_DEFAULT) == 1;
SystemProperties.set("persist.sys.axp.loc_privacy", stop ? "1" : "0");
}
private void toggleXtraDaemon() {
Log.i(TAG, "Toggling xtra-daemon via property");
SystemProperties.set("persist.sys.xtra-daemon.enabled",
Boolean.toString(isAssistedGpsEnabled()));
}
""",
already="persist.sys.axp.loc_privacy",
)
old_setid = """ TelephonyManager phone = (TelephonyManager)
mContext.getSystemService(Context.TELEPHONY_SERVICE);
int type = AGPS_SETID_TYPE_NONE;
String setId = null;
final Boolean isEmergency = mNIHandler.getInEmergency();
// Unless we are in an emergency, do not provide sensitive subscriber information
// to SUPL servers.
if (!isEmergency) {
mGnssNative.setAgpsSetId(type, "");
return;
}
int subId = SubscriptionManager.getDefaultDataSubscriptionId();
if (mGnssConfiguration.isActiveSimEmergencySuplEnabled() && isEmergency
&& mNetworkConnectivityHandler.getActiveSubId() >= 0) {
subId = mNetworkConnectivityHandler.getActiveSubId();
}
if (SubscriptionManager.isValidSubscriptionId(subId)) {
phone = phone.createForSubscriptionId(subId);
}
if ((flags & AGPS_REQUEST_SETID_IMSI) == AGPS_REQUEST_SETID_IMSI) {
setId = phone.getSubscriberId();
if (setId != null) {
// This means the framework has the SIM card.
type = AGPS_SETID_TYPE_IMSI;
}
} else if ((flags & AGPS_REQUEST_SETID_MSISDN) == AGPS_REQUEST_SETID_MSISDN) {
setId = phone.getLine1Number();
if (setId != null) {
// This means the framework has the SIM card.
type = AGPS_SETID_TYPE_MSISDN;
}
}
mGnssNative.setAgpsSetId(type, (setId == null) ? "" : setId);
"""
new_setid = """ // """ + MARKER + """: AXP/Divest. Never send IMSI or the phone number
// to a SUPL server, including during an emergency call. LOS 23 already
// skips this outside emergency. Control-plane A-GNSS is unchanged.
// flags is read so the unused-parameter check stays quiet.
if (flags != 0 && Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "ignoring SUPL set-id request flags=" + flags);
}
mGnssNative.setAgpsSetId(AGPS_SETID_TYPE_NONE, "");
"""
prov = gnss_prov.read_text()
if old_setid in prov:
gnss_prov.write_text(prov.replace(old_setid, new_setid, 1))
elif "ignoring SUPL set-id request flags" not in prov:
short = """ // """ + MARKER + """: AXP/Divest. Never send IMSI or the phone number
// to a SUPL server, including during an emergency call. LOS 23 already
// skips this outside emergency. Control-plane A-GNSS is unchanged.
mGnssNative.setAgpsSetId(AGPS_SETID_TYPE_NONE, "");
"""
if short not in prov:
sys.exit("setAgpsSetId block missing")
gnss_prov.write_text(prov.replace(short, new_setid, 1))
must(
loc_xml,
""" settings:controller="com.android.settings.location.LocationServicesPreferenceController"/>
</com.android.settingslib.widget.UntitledPreferenceCategory>
""",
""" settings:controller="com.android.settings.location.LocationServicesPreferenceController"/>
<!-- """
+ MARKER
+ """: default off, so carrier SUPL stays -->
<com.android.settingslib.RestrictedSwitchPreference
android:key="force_disable_supl"
android:title="@string/force_disable_supl_title"
android:summary="@string/force_disable_supl_summary"
settings:controller="com.android.settings.location.ForceDisableSuplPrefController"/>
</com.android.settingslib.widget.UntitledPreferenceCategory>
""",
already='android:key="force_disable_supl"',
)
must(
loc_xml,
""" settings:controller="com.android.settings.location.ForceDisableSuplPrefController"/>
</com.android.settingslib.widget.UntitledPreferenceCategory>
""",
""" settings:controller="com.android.settings.location.ForceDisableSuplPrefController"/>
<!-- """
+ MARKER
+ """: default off. On stops LPP, XTRA and IZat. Binaries stay. -->
<com.android.settingslib.RestrictedSwitchPreference
android:key="force_disable_location_assist"
android:title="@string/force_disable_location_assist_title"
android:summary="@string/force_disable_location_assist_summary"
settings:controller="com.android.settings.location.ForceDisableLocationAssistPrefController"/>
</com.android.settingslib.widget.UntitledPreferenceCategory>
""",
already='android:key="force_disable_location_assist"',
)
must(
strings,
""" <string name="location_services_preference_title">Location services</string>
""",
""" <string name="location_services_preference_title">Location services</string>
<!-- """
+ MARKER
+ """ -->
<string name="force_disable_supl_title">Force disable Secure User Plane Location (SUPL)</string>
<string name="force_disable_supl_summary">Always disable SUPL assisted location support regardless of carrier configuration or emergency call status. Does not disable control-plane A-GNSS. IMSI and the phone number are never sent to a SUPL server.</string>
""",
already="force_disable_supl_title",
)
must(
strings,
""" <string name="force_disable_supl_summary">Always disable SUPL assisted location support regardless of carrier configuration or emergency call status. Does not disable control-plane A-GNSS. IMSI and the phone number are never sent to a SUPL server.</string>
""",
""" <string name="force_disable_supl_summary">Always disable SUPL assisted location support regardless of carrier configuration or emergency call status. Does not disable control-plane A-GNSS. IMSI and the phone number are never sent to a SUPL server.</string>
<string name="force_disable_location_assist_title">Stop extra location assistance</string>
<string name="force_disable_location_assist_summary">Stops LTE positioning (LPP/LPPE), XTRA orbit downloads, and Qualcomm IZat. Satellite fixes keep working. The programs stay installed. SUPL has its own switch.</string>
""",
already="force_disable_location_assist_title",
)
if not controller.exists():
controller.write_text(
"""package com.android.settings.location;
import android.content.Context;
import android.os.Process;
import android.provider.Settings;
import com.android.settings.core.TogglePreferenceController;
/** """
+ MARKER
+ """ AXP/Graphene toggle. Default off. */
public class ForceDisableSuplPrefController extends TogglePreferenceController {
public ForceDisableSuplPrefController(Context ctx, String key) {
super(ctx, key);
}
@Override
public int getAvailabilityStatus() {
if (!Process.myUserHandle().isSystem()) {
return DISABLED_FOR_USER;
}
return AVAILABLE;
}
@Override
public boolean isChecked() {
return Settings.Global.getInt(
mContext.getContentResolver(),
Settings.Global.FORCE_DISABLE_SUPL,
Settings.Global.FORCE_DISABLE_SUPL_DEFAULT) == 1;
}
@Override
public boolean setChecked(boolean isChecked) {
return Settings.Global.putInt(
mContext.getContentResolver(),
Settings.Global.FORCE_DISABLE_SUPL,
isChecked ? 1 : 0);
}
@Override
public int getSliceHighlightMenuRes() {
return NO_RES;
}
}
"""
)
assist_controller = (
TREE
/ "packages/apps/Settings/src/com/android/settings/location/ForceDisableLocationAssistPrefController.java"
)
if not assist_controller.exists():
assist_controller.write_text(
"""package com.android.settings.location;
import android.content.Context;
import android.os.Process;
import android.provider.Settings;
import com.android.settings.core.TogglePreferenceController;
/** """
+ MARKER
+ """ Default off. Stops assistance; does not delete blobs. */
public class ForceDisableLocationAssistPrefController extends TogglePreferenceController {
public ForceDisableLocationAssistPrefController(Context ctx, String key) {
super(ctx, key);
}
@Override
public int getAvailabilityStatus() {
if (!Process.myUserHandle().isSystem()) {
return DISABLED_FOR_USER;
}
return AVAILABLE;
}
@Override
public boolean isChecked() {
return Settings.Global.getInt(
mContext.getContentResolver(),
Settings.Global.FORCE_DISABLE_LOCATION_ASSIST,
Settings.Global.FORCE_DISABLE_LOCATION_ASSIST_DEFAULT) == 1;
}
@Override
public boolean setChecked(boolean isChecked) {
return Settings.Global.putInt(
mContext.getContentResolver(),
Settings.Global.FORCE_DISABLE_LOCATION_ASSIST,
isChecked ? 1 : 0);
}
@Override
public int getSliceHighlightMenuRes() {
return NO_RES;
}
}
"""
)
must(
init_rc,
"""on property:persist.sys.xtra-daemon.enabled=*
restart loc_launcher
""",
"""# """
+ MARKER
+ """
# 1 stops loc_launcher (xtra-daemon, lowi, IZat). Binaries stay.
# The xtra property must not restart the service while that stop is on.
on property:persist.sys.axp.loc_privacy=1
stop loc_launcher
exec_background -- /system/bin/toybox killall xtra-daemon lowi-server
on property:persist.sys.axp.loc_privacy=0
start loc_launcher
on property:sys.boot_completed=1 && property:persist.sys.axp.loc_privacy=1
stop loc_launcher
exec_background -- /system/bin/toybox killall xtra-daemon lowi-server
on property:persist.sys.xtra-daemon.enabled=* && property:persist.sys.axp.loc_privacy=0
restart loc_launcher
""",
already="persist.sys.axp.loc_privacy",
)
raw = gps.read_bytes()
old_ntp = b"NTP_SERVER = time.google.com"
new_ntp = b"NTP_SERVER = 2.android.pool.ntp.org"
if new_ntp not in raw:
if old_ntp not in raw:
sys.exit("NTP_SERVER line missing")
raw = raw.replace(old_ntp, new_ntp, 1)
gps.write_bytes(raw)
print("supl privacy applied")
if __name__ == "__main__":
main()
@@ -0,0 +1,133 @@
#!/usr/bin/env python3
"""009: AXP Yama ptrace_scope, active at 2.
AXP LOS 22 init.rc sets /proc/sys/kernel/yama/ptrace_scope from
persist.native_debug (true -> 0, false -> 2). GOLDEN's kernel has Yama
off, so those lines do nothing there. This tree builds Yama, so init
writes 2 at boot. persist.native_debug=true still opens ptrace.
Writing that sysctl needs CAP_SYS_PTRACE. AOSP neverallows it for init;
the exception is the one in AXP's ptrace_scope sepolicy patch. The node
is labeled proc_security, which init may already read and write.
"""
from __future__ import annotations
import pathlib
import sys
TREE = pathlib.Path("/srv/rom/src/lineage")
MARKER = "sunfish-a16-009-yama-ptrace"
RC_BOOT_OLD = " write /proc/sys/kernel/randomize_va_space 2\n"
RC_BOOT_NEW = (
" write /proc/sys/kernel/randomize_va_space 2\n"
" # sunfish-a16-009-yama-ptrace: AXP scope 2 unless native debug is on.\n"
" write /proc/sys/kernel/yama/ptrace_scope 2\n"
)
RC_PROP_OLD = (
" write /proc/sys/net/ipv4/tcp_default_init_rwnd ${net.tcp_def_init_rwnd}\n"
)
RC_PROP_NEW = (
" write /proc/sys/net/ipv4/tcp_default_init_rwnd ${net.tcp_def_init_rwnd}\n"
"\n"
"on property:persist.native_debug=true\n"
" write /proc/sys/kernel/yama/ptrace_scope 0\n"
"\n"
"on property:persist.native_debug=false\n"
" write /proc/sys/kernel/yama/ptrace_scope 2\n"
)
GENFS_OLD = "genfscon proc /sys/kernel/usermodehelper u:object_r:usermodehelper:s0\n"
GENFS_NEW = (
"genfscon proc /sys/kernel/usermodehelper u:object_r:usermodehelper:s0\n"
"genfscon proc /sys/kernel/yama/ptrace_scope u:object_r:proc_security:s0\n"
)
PROP_OLD = "persist.debug. u:object_r:persist_debug_prop:s0\n"
PROP_NEW = (
"persist.debug. u:object_r:persist_debug_prop:s0\n"
"persist.native_debug u:object_r:system_prop:s0\n"
)
INIT_OLD = """allow init {
proc_security
}:file rw_file_perms;
"""
INIT_NEW = """allow init {
proc_security
}:file rw_file_perms;
# sunfish-a16-009-yama-ptrace: CAP_SYS_PTRACE to set ptrace_scope.
allow init self:global_capability_class_set sys_ptrace;
"""
NEVER_END = "} self:global_capability_class_set sys_ptrace;"
def must(path: pathlib.Path, old: str, new: str, already: str) -> None:
text = path.read_text()
if already in text:
return
if old not in text:
sys.exit(f"anchor missing in {path}")
path.write_text(text.replace(old, new, 1))
def loosen_sys_ptrace(path: pathlib.Path) -> None:
text = path.read_text()
idx = text.find(NEVER_END)
if idx < 0:
return
start = text.rfind("neverallow {", 0, idx)
if start < 0:
sys.exit(f"neverallow block missing in {path}")
block = text[start:idx]
if "\n -init\n" in block:
return
if "\n domain\n" not in block:
sys.exit(f"domain line missing in sys_ptrace neverallow {path}")
new_block = block.replace("\n domain\n", "\n domain\n -init\n", 1)
path.write_text(text[:start] + new_block + text[idx:])
def main() -> None:
rc = TREE / "system/core/rootdir/init.rc"
must(rc, RC_BOOT_OLD, RC_BOOT_NEW, MARKER)
must(rc, RC_PROP_OLD, RC_PROP_NEW, "on property:persist.native_debug=true")
se = TREE / "system/sepolicy"
must(
se / "private/init.te",
INIT_OLD,
INIT_NEW,
"allow init self:global_capability_class_set sys_ptrace;",
)
loosen_sys_ptrace(se / "private/domain.te")
must(se / "private/genfs_contexts", GENFS_OLD, GENFS_NEW, "yama/ptrace_scope")
must(se / "private/property_contexts", PROP_OLD, PROP_NEW, "persist.native_debug")
api = se / "prebuilts/api"
for domain in api.glob("*/private/domain.te"):
loosen_sys_ptrace(domain)
for genfs in api.glob("*/private/genfs_contexts"):
text = genfs.read_text()
if "yama/ptrace_scope" in text or GENFS_OLD not in text:
continue
genfs.write_text(text.replace(GENFS_OLD, GENFS_NEW, 1))
for props in api.glob("*/private/property_contexts"):
text = props.read_text()
if "persist.native_debug" in text or PROP_OLD not in text:
continue
props.write_text(text.replace(PROP_OLD, PROP_NEW, 1))
# Current policy must still exclude init, or the write is neverallowed.
live = (se / "private/domain.te").read_text()
end = live.find(NEVER_END)
start = live.rfind("neverallow {", 0, end)
if "\n -init\n" not in live[start:end]:
sys.exit("init still neverallowed from sys_ptrace")
print("yama ptrace_scope applied")
if __name__ == "__main__":
main()
@@ -69,12 +69,12 @@ guarantee). Do not relock the bootloader on this Magisk track.
## Branding
`TARGET_UNOFFICIAL_BUILD_ID=AXP-Resurection-Pro` is set at the **top** of
`device/google/sunfish/lineage_sunfish.mk` (before inheriting
`vendor/lineage` → `version.mk`). Helper:
`apply_009_unofficial_build_id.py`. **Pro** = full Magisk resurrection track
(not a future slim/minimal variant). Mirrors GOLDEN’s `…-sunfish-pro` /
releasetype `PRO` intent without patching `vendor/lineage/config/version.mk`.
Ship identity follows AXP/GOLDEN, not Lineage's unofficial channel.
GOLDEN (`22.2-2026.003-sunfish-pro`) reports `ro.lineage.releasetype=PRO`,
`ro.build.tags=release-keys`, and a version ending in `sunfish-pro`.
`TARGET_UNOFFICIAL_BUILD_ID` forces Lineage `UNOFFICIAL` and is not that
scheme. The next bacon sets `PRO` + `release-keys` + `…-sunfish-pro`.
**Pro** remains the full Magisk track (AXP PRO, not SLIM).
`branding.mk` adds `ro.axp.resurection.track=pro`. Ship scripts also publish
`AXP-Resurection-Pro-<ver>-<date>-sunfish.zip` as a hardlink/copy of the