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

@@ -38,8 +38,23 @@ def backup(path: Path) -> None:
def patch_computer_engine() -> None:
path = TREE / "frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java"
text = path.read_text()
# AXP/EXTENDROM spoofs on user images (GOLDEN ro.debuggable=0). LOS stock
# isDebuggable() would disable that on lineage_sunfish-user.
deb_gate = """ public static boolean isMicrogSigned(AndroidPackage p) {
if (!isDebuggable()) {
return false;
}
"""
user_ok = """ public static boolean isMicrogSigned(AndroidPackage p) {
// %s: no isDebuggable() gate (AXP user / GOLDEN).
""" % MARKER
if MARKER in text:
print("ComputerEngine: already patched")
if deb_gate not in text:
print("ComputerEngine: already patched, user gate ok")
return
backup(path)
path.write_text(text.replace(deb_gate, user_ok, 1))
print("ComputerEngine: dropped isDebuggable gate")
return
backup(path)
@@ -78,9 +93,7 @@ def patch_computer_engine() -> None:
}"""
new_is = """ public static boolean isMicrogSigned(AndroidPackage p) {
if (!isDebuggable()) {
return false;
}
// SUNFISH_A16_005_SIGSPOOF: no isDebuggable() gate (AXP user / GOLDEN).
// Allowlist the following apps:
// * com.android.vending - microG Companion / patched Phonesky
@@ -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
@@ -30,7 +30,9 @@ while IFS= read -r rel || [[ -n "$rel" ]]; do
fail=$((fail + 1))
continue
fi
args=(-p1 --batch -d "$KERNEL_DIR")
# Never let GNU patch auto-answer "Assuming -R": the LOS base can already
# contain a patch's intended result. Reversing it is a regression, not OK.
args=(-p1 --batch --forward -d "$KERNEL_DIR")
if [[ "$DRY_RUN" -eq 1 ]]; then
args+=(--dry-run)
fi
@@ -6,8 +6,13 @@
# off; CONFIG_LSM already lists yama but SECURITY_YAMA was unset so the LSM
# never built.
#
# Does NOT apply high-offset CVE leftovers (nf_tables deliberately skipped) or
# CVE-2022-49980 (denylist until lock-scope port).
# Does NOT apply high-offset CVE leftovers (nf_tables deliberately skipped).
# CVE-2022-49980's one-liner stays on the denylist. The bacon applies
# ports/cve-2022-49980-lock-scope.patch after the CVE lists.
#
# USERFAULTFD: GOLDEN's running 4.14.355 config leaves it unset. LOS 23
# sunfish_defconfig turns it on. This kernel has no UFFDIO_MOVE (that ioctl
# is 6.1+), so ART only opens a fd and times out. Match GOLDEN.
#
# Usage: apply_uss5_1_defconfig.sh <kernel-tree>
set -euo pipefail
@@ -17,15 +22,14 @@ DEF="$K/arch/arm64/configs/sunfish_defconfig"
test -f "$DEF"
test -f "$K/security/yama/Kconfig"
for k in SECURITY_YAMA SLAB_FREELIST_RANDOM; do
for k in SECURITY_YAMA SLAB_FREELIST_RANDOM USERFAULTFD HAVE_ARCH_USERFAULTFD_MINOR; do
sed -i "/CONFIG_${k}/d" "$DEF"
# drop "# CONFIG_FOO is not set" lines too (sed above only matches CONFIG_FOO=)
sed -i "/# CONFIG_${k} is not set/d" "$DEF"
done
{
echo "CONFIG_SECURITY_YAMA=y"
echo "CONFIG_SLAB_FREELIST_RANDOM=y"
echo "# CONFIG_USERFAULTFD is not set"
} >> "$DEF"
echo "OK: US-S5-1 defconfig (YAMA + SLAB_FREELIST_RANDOM) on $K"
echo "OK: US-S5-1 defconfig (YAMA + SLAB_FREELIST_RANDOM, USERFAULTFD off) on $K"
@@ -1,6 +1,7 @@
# relative to AXP kernel_patches
# LAB 2026-10-01: alone ⇒ Google-logo hang (udc_lock recursive deadlock on
# msm-4.14; needs lock-scope companion, not one-liner). Do not re-apply.
# msm-4.14). Companion is ports/cve-2022-49980-lock-scope.patch (UAF fix
# plus uevents outside the lock). Do not apply the one-liner by itself.
CVE-2022-49980/ANY/0001.patch
CVE-2017-15832/qcacld-3.0/0001.patch
CVE-2021-29648/^5.11/0001.patch
@@ -0,0 +1,49 @@
From: Daniel Micay <danielmicay@gmail.com>
Subject: [PATCH] add vmalloc alloc_size attributes
Port of AXP/Graphene
0008-Graphene-Kernel_Hardening-allocsize/4.14/0007.patch onto the
Lineage msm-4.14 vmalloc.h. That header gained vmalloc_user_node_flags
and dropped the pgprot argument of __vmalloc, so the upstream hunk no
longer applies. The attribute is the same one AXP adds: alloc_size(1)
on the size argument.
Signed-off-by: Daniel Micay <danielmicay@gmail.com>
---
include/linux/vmalloc.h | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
--- a/include/linux/vmalloc.h
+++ b/include/linux/vmalloc.h
@@ -76,20 +76,20 @@
static inline unsigned long vmalloc_nr_pages(void) { return 0; }
#endif
-extern void *vmalloc(unsigned long size);
-extern void *vzalloc(unsigned long size);
-extern void *vmalloc_user(unsigned long size);
-extern void *vmalloc_node(unsigned long size, int node);
-extern void *vzalloc_node(unsigned long size, int node);
-extern void *vmalloc_user_node_flags(unsigned long size, int node, gfp_t flags);
-extern void *vmalloc_exec(unsigned long size);
-extern void *vmalloc_32(unsigned long size);
-extern void *vmalloc_32_user(unsigned long size);
-extern void *__vmalloc(unsigned long size, gfp_t gfp_mask);
+extern void *vmalloc(unsigned long size) __attribute__((alloc_size(1)));
+extern void *vzalloc(unsigned long size) __attribute__((alloc_size(1)));
+extern void *vmalloc_user(unsigned long size) __attribute__((alloc_size(1)));
+extern void *vmalloc_node(unsigned long size, int node) __attribute__((alloc_size(1)));
+extern void *vzalloc_node(unsigned long size, int node) __attribute__((alloc_size(1)));
+extern void *vmalloc_user_node_flags(unsigned long size, int node, gfp_t flags) __attribute__((alloc_size(1)));
+extern void *vmalloc_exec(unsigned long size) __attribute__((alloc_size(1)));
+extern void *vmalloc_32(unsigned long size) __attribute__((alloc_size(1)));
+extern void *vmalloc_32_user(unsigned long size) __attribute__((alloc_size(1)));
+extern void *__vmalloc(unsigned long size, gfp_t gfp_mask) __attribute__((alloc_size(1)));
extern void *__vmalloc_node_range(unsigned long size, unsigned long align,
unsigned long start, unsigned long end, gfp_t gfp_mask,
pgprot_t prot, unsigned long vm_flags, int node,
- const void *caller);
+ const void *caller) __attribute__((alloc_size(1)));
#ifndef CONFIG_MMU
extern void *__vmalloc_node_flags(unsigned long size, int node, gfp_t flags);
static inline void *__vmalloc_node_flags_caller(unsigned long size, int node,
@@ -0,0 +1,95 @@
From: Alan Stern <stern@rowland.harvard.edu>
Subject: [PATCH] USB: gadget: fix udc_lock scope around usb_udc_uevent
CVE-2022-49980 (2191c00855b0) takes udc_lock inside usb_udc_uevent().
On msm-4.14 that callback still runs synchronously from device_add()
and kobject_uevent() while the same thread already holds udc_lock, so
the mutex deadlocks and the phone hangs on the Google logo.
Linux 6.1 usb_add_gadget() / gadget_bind_driver() only hold udc_lock
around the list and the driver pointer. This keeps the 4.14 call
shape and moves those uevents outside the lock, then applies the
UAF fix. Do not apply 2191c00855b0 without this.
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
---
--- a/drivers/usb/gadget/udc/core.c
+++ b/drivers/usb/gadget/udc/core.c
@@ -1229,11 +1229,18 @@
mutex_lock(&udc_lock);
list_add_tail(&udc->list, &udc_list);
+ mutex_unlock(&udc_lock);
+ /*
+ * device_add() emits a uevent that calls usb_udc_uevent(), which
+ * takes udc_lock. Same for device_del() on the error path. v6.1
+ * usb_add_gadget() only holds the mutex around the list update.
+ */
ret = device_add(&udc->dev);
if (ret)
goto err_unlist_udc;
+ mutex_lock(&udc_lock);
usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED);
udc->vbus = true;
@@ -1247,9 +1254,11 @@
return 0;
err_del_udc:
+ mutex_unlock(&udc_lock);
device_del(&udc->dev);
err_unlist_udc:
+ mutex_lock(&udc_lock);
list_del(&udc->list);
mutex_unlock(&udc_lock);
@@ -1313,7 +1322,14 @@
dev_dbg(&udc->dev, "unregistering UDC driver [%s]\n",
udc->driver->function);
+ /*
+ * Caller holds udc_lock. usb_udc_uevent() takes it too, so the
+ * CHANGE uevent has to run outside the lock. Driver is still bound;
+ * the callback re-locks to read it.
+ */
+ mutex_unlock(&udc_lock);
kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE);
+ mutex_lock(&udc_lock);
usb_gadget_disconnect(udc->gadget);
udc->driver->disconnect(udc->gadget);
@@ -1383,7 +1399,10 @@
}
usb_udc_connect_control(udc);
+ /* Same deadlock as remove: uevent re-enters udc_lock. */
+ mutex_unlock(&udc_lock);
kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE);
+ mutex_lock(&udc_lock);
return 0;
err1:
if (ret != -EISNAM)
@@ -1611,13 +1630,14 @@
return ret;
}
- if (udc->driver) {
+ mutex_lock(&udc_lock);
+ if (udc->driver)
ret = add_uevent_var(env, "USB_UDC_DRIVER=%s",
udc->driver->function);
- if (ret) {
- dev_err(dev, "failed to add uevent USB_UDC_DRIVER\n");
- return ret;
- }
+ mutex_unlock(&udc_lock);
+ if (ret) {
+ dev_err(dev, "failed to add uevent USB_UDC_DRIVER\n");
+ return ret;
}
return 0;
@@ -0,0 +1,424 @@
From: Daniel Micay <danielmicay@gmail.com>
Subject: [PATCH] make sysctl constants read-only
Port of AXP/Graphene ro/4.14/0007.patch and ro/4.14/0034.patch.
Declarations still match; hunk context does not, and this tree has no
net/decnet/sysctl_net_decnet.c. Same __read_only (__ro_after_init)
annotations AXP applies, on the files that exist here.
Signed-off-by: Daniel Micay <danielmicay@gmail.com>
---
--- a/fs/pipe.c
+++ b/fs/pipe.c
@@ -53,7 +53,7 @@
/*
* Minimum pipe size, as required by POSIX
*/
-unsigned int pipe_min_size = PAGE_SIZE;
+unsigned int pipe_min_size __read_only = PAGE_SIZE;
/* Maximum allocatable pages per user. Hard limit is unset by default, soft
* matches default values.
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -121,48 +121,48 @@
/* Constants used for minimum and maximum */
#ifdef CONFIG_LOCKUP_DETECTOR
-static int sixty = 60;
+static int sixty __read_only = 60;
#endif
-static int __maybe_unused neg_one = -1;
-static int __maybe_unused neg_three = -3;
+static int __maybe_unused neg_one __read_only = -1;
+static int __maybe_unused neg_three __read_only = -3;
-static int zero;
-static int __maybe_unused one = 1;
-static int __maybe_unused two = 2;
-static int __maybe_unused three = 3;
-static int __maybe_unused four = 4;
+static int zero __read_only;
+static int __maybe_unused one __read_only = 1;
+static int __maybe_unused two __read_only = 2;
+static int __maybe_unused three __read_only = 3;
+static int __maybe_unused four __read_only = 4;
static int int_max = INT_MAX;
-static unsigned long zero_ul;
-static unsigned long one_ul = 1;
-static unsigned long long_max = LONG_MAX;
-static int one_hundred = 100;
-static int one_thousand = 1000;
+static unsigned long zero_ul __read_only;
+static unsigned long one_ul __read_only = 1;
+static unsigned long long_max __read_only = LONG_MAX;
+static int one_hundred __read_only = 100;
+static int one_thousand __read_only = 1000;
#ifdef CONFIG_SCHED_WALT
-static int two_million = 2000000;
+static int two_million __read_only = 2000000;
#endif
#ifdef CONFIG_PRINTK
-static int ten_thousand = 10000;
+static int ten_thousand __read_only = 10000;
#endif
#ifdef CONFIG_PERF_EVENTS
-static int six_hundred_forty_kb = 640 * 1024;
+static int six_hundred_forty_kb __read_only = 640 * 1024;
#endif
static int __maybe_unused two_hundred_million __read_only = 200000000;
static int two_hundred_fifty_five __read_only = 255;
/* this is needed for the proc_doulongvec_minmax of vm_dirty_bytes */
-static unsigned long dirty_bytes_min = 2 * PAGE_SIZE;
+static unsigned long dirty_bytes_min __read_only = 2 * PAGE_SIZE;
/* this is needed for the proc_dointvec_minmax for [fs_]overflow UID and GID */
-static int maxolduid = 65535;
-static int minolduid;
+static int maxolduid __read_only = 65535;
+static int minolduid __read_only;
-static int ngroups_max = NGROUPS_MAX;
+static int ngroups_max __read_only = NGROUPS_MAX;
static const int cap_last_cap = CAP_LAST_CAP;
/*this is needed for proc_doulongvec_minmax of sysctl_hung_task_timeout_secs */
#ifdef CONFIG_DETECT_HUNG_TASK
-static unsigned long hung_task_timeout_max = (LONG_MAX/HZ);
+static unsigned long hung_task_timeout_max __read_only = (LONG_MAX/HZ);
#endif
#ifdef CONFIG_INOTIFY_USER
@@ -332,19 +332,19 @@
};
#ifdef CONFIG_SCHED_DEBUG
-static int min_sched_granularity_ns = 100000; /* 100 usecs */
-static int max_sched_granularity_ns = NSEC_PER_SEC; /* 1 second */
-static int min_wakeup_granularity_ns; /* 0 usecs */
-static int max_wakeup_granularity_ns = NSEC_PER_SEC; /* 1 second */
+static int min_sched_granularity_ns __read_only = 100000; /* 100 usecs */
+static int max_sched_granularity_ns __read_only = NSEC_PER_SEC; /* 1 second */
+static int min_wakeup_granularity_ns __read_only; /* 0 usecs */
+static int max_wakeup_granularity_ns __read_only = NSEC_PER_SEC; /* 1 second */
#ifdef CONFIG_SMP
-static int min_sched_tunable_scaling = SCHED_TUNABLESCALING_NONE;
-static int max_sched_tunable_scaling = SCHED_TUNABLESCALING_END-1;
+static int min_sched_tunable_scaling __read_only = SCHED_TUNABLESCALING_NONE;
+static int max_sched_tunable_scaling __read_only = SCHED_TUNABLESCALING_END-1;
#endif /* CONFIG_SMP */
#endif /* CONFIG_SCHED_DEBUG */
#ifdef CONFIG_COMPACTION
-static int min_extfrag_threshold;
-static int max_extfrag_threshold = 1000;
+static int min_extfrag_threshold __read_only;
+static int max_extfrag_threshold __read_only = 1000;
#endif
#ifdef CONFIG_BPF_SYSCALL
--- a/ipc/mq_sysctl.c
+++ b/ipc/mq_sysctl.c
@@ -47,11 +47,11 @@
#define proc_mq_dointvec_minmax NULL
#endif
-static int msg_max_limit_min = MIN_MSGMAX;
-static int msg_max_limit_max = HARD_MSGMAX;
+static int msg_max_limit_min __read_only = MIN_MSGMAX;
+static int msg_max_limit_max __read_only = HARD_MSGMAX;
-static int msg_maxsize_limit_min = MIN_MSGSIZEMAX;
-static int msg_maxsize_limit_max = HARD_MSGSIZEMAX;
+static int msg_maxsize_limit_min __read_only = MIN_MSGSIZEMAX;
+static int msg_maxsize_limit_max __read_only = HARD_MSGSIZEMAX;
static struct ctl_table mq_sysctls[] = {
{
--- a/lib/test_sysctl.c
+++ b/lib/test_sysctl.c
@@ -36,8 +36,8 @@
#include <linux/delay.h>
#include <linux/vmalloc.h>
-static int i_zero;
-static int i_one_hundred = 100;
+static int i_zero __read_only;
+static int i_one_hundred __read_only = 100;
struct test_sysctl_data {
int int_0001;
--- a/net/ax25/sysctl_net_ax25.c
+++ b/net/ax25/sysctl_net_ax25.c
@@ -12,21 +12,21 @@
#include <linux/spinlock.h>
#include <net/ax25.h>
-static int min_ipdefmode[1], max_ipdefmode[] = {1};
-static int min_axdefmode[1], max_axdefmode[] = {1};
-static int min_backoff[1], max_backoff[] = {2};
-static int min_conmode[1], max_conmode[] = {2};
-static int min_window[] = {1}, max_window[] = {7};
-static int min_ewindow[] = {1}, max_ewindow[] = {63};
-static int min_t1[] = {1}, max_t1[] = {30000};
-static int min_t2[] = {1}, max_t2[] = {20000};
-static int min_t3[1], max_t3[] = {3600000};
-static int min_idle[1], max_idle[] = {65535000};
-static int min_n2[] = {1}, max_n2[] = {31};
-static int min_paclen[] = {1}, max_paclen[] = {512};
-static int min_proto[1], max_proto[] = { AX25_PROTO_MAX };
+static int min_ipdefmode[1], max_ipdefmode[] __read_only = {1};
+static int min_axdefmode[1], max_axdefmode[] __read_only = {1};
+static int min_backoff[1], max_backoff[] __read_only = {2};
+static int min_conmode[1], max_conmode[] __read_only = {2};
+static int min_window[] __read_only = {1}, max_window[] __read_only = {7};
+static int min_ewindow[] __read_only = {1}, max_ewindow[] __read_only = {63};
+static int min_t1[] __read_only = {1}, max_t1[] __read_only = {30000};
+static int min_t2[] __read_only = {1}, max_t2[] __read_only = {20000};
+static int min_t3[1], max_t3[] __read_only = {3600000};
+static int min_idle[1], max_idle[] __read_only = {65535000};
+static int min_n2[] __read_only = {1}, max_n2[] __read_only = {31};
+static int min_paclen[] __read_only = {1}, max_paclen[] __read_only = {512};
+static int min_proto[1], max_proto[] __read_only = { AX25_PROTO_MAX };
#ifdef CONFIG_AX25_DAMA_SLAVE
-static int min_ds_timeout[1], max_ds_timeout[] = {65535000};
+static int min_ds_timeout[1], max_ds_timeout[] __read_only = {65535000};
#endif
static const struct ctl_table ax25_param_table[] = {
--- a/net/core/sysctl_net_core.c
+++ b/net/core/sysctl_net_core.c
@@ -23,14 +23,14 @@
#include <net/busy_poll.h>
#include <net/pkt_sched.h>
-static int zero = 0;
-static int one = 1;
-static int two __maybe_unused = 2;
-static int min_sndbuf = SOCK_MIN_SNDBUF;
-static int min_rcvbuf = SOCK_MIN_RCVBUF;
-static int max_skb_frags = MAX_SKB_FRAGS;
-static long long_one __maybe_unused = 1;
-static long long_max __maybe_unused = LONG_MAX;
+static int zero __read_only = 0;
+static int one __read_only = 1;
+static int two __maybe_unused __read_only = 2;
+static int min_sndbuf __read_only = SOCK_MIN_SNDBUF;
+static int min_rcvbuf __read_only = SOCK_MIN_RCVBUF;
+static int max_skb_frags __read_only = MAX_SKB_FRAGS;
+static long long_one __maybe_unused __read_only = 1;
+static long long_max __maybe_unused __read_only = LONG_MAX;
static int net_msg_warn; /* Unused, but still a sysctl */
--- a/net/ipv4/sysctl_net_ipv4.c
+++ b/net/ipv4/sysctl_net_ipv4.c
@@ -28,33 +28,33 @@
#include <net/protocol.h>
#include <net/netevent.h>
-static int zero;
-static int one = 1;
-static int three = 3;
-static int four = 4;
-static int hundred = 100;
-static int thousand = 1000;
-static int gso_max_segs = GSO_MAX_SEGS;
-static int tcp_retr1_max = 255;
-static int ip_local_port_range_min[] = { 1, 1 };
-static int ip_local_port_range_max[] = { 65535, 65535 };
-static int tcp_adv_win_scale_min = -31;
-static int tcp_adv_win_scale_max = 31;
-static int tcp_min_snd_mss_min = TCP_MIN_SND_MSS;
-static int tcp_min_snd_mss_max = 65535;
+static int zero __read_only;
+static int one __read_only = 1;
+static int three __read_only = 3;
+static int four __read_only = 4;
+static int hundred __read_only = 100;
+static int thousand __read_only = 1000;
+static int gso_max_segs __read_only = GSO_MAX_SEGS;
+static int tcp_retr1_max __read_only = 255;
+static int ip_local_port_range_min[] __read_only = { 1, 1 };
+static int ip_local_port_range_max[] __read_only = { 65535, 65535 };
+static int tcp_adv_win_scale_min __read_only = -31;
+static int tcp_adv_win_scale_max __read_only = 31;
+static int tcp_min_snd_mss_min __read_only = TCP_MIN_SND_MSS;
+static int tcp_min_snd_mss_max __read_only = 65535;
static int ip_privileged_port_min;
-static int ip_privileged_port_max = 65535;
-static int ip_ttl_min = 1;
-static int ip_ttl_max = 255;
-static int tcp_syn_retries_min = 1;
-static int tcp_syn_retries_max = MAX_TCP_SYNCNT;
-static int ip_ping_group_range_min[] = { 0, 0 };
-static int ip_ping_group_range_max[] = { GID_T_MAX, GID_T_MAX };
-static int one_day_secs = 24 * 3600;
-static int tcp_delack_seg_min = TCP_DELACK_MIN;
-static int tcp_delack_seg_max = 60;
+static int ip_privileged_port_max __read_only = 65535;
+static int ip_ttl_min __read_only = 1;
+static int ip_ttl_max __read_only = 255;
+static int tcp_syn_retries_min __read_only = 1;
+static int tcp_syn_retries_max __read_only = MAX_TCP_SYNCNT;
+static int ip_ping_group_range_min[] __read_only = { 0, 0 };
+static int ip_ping_group_range_max[] __read_only = { GID_T_MAX, GID_T_MAX };
+static int one_day_secs __read_only = 24 * 3600;
+static int tcp_delack_seg_min __read_only = TCP_DELACK_MIN;
+static int tcp_delack_seg_max __read_only = 60;
static int tcp_use_userconfig_min;
-static int tcp_use_userconfig_max = 1;
+static int tcp_use_userconfig_max __read_only = 1;
/* obsolete */
static int sysctl_tcp_low_latency __read_mostly;
--- a/net/ipv6/sysctl_net_ipv6.c
+++ b/net/ipv6/sysctl_net_ipv6.c
@@ -22,9 +22,9 @@
#endif
static int zero;
-static int one = 1;
-static int auto_flowlabels_min;
-static int auto_flowlabels_max = IP6_AUTO_FLOW_LABEL_MAX;
+static int one __read_only = 1;
+static int auto_flowlabels_min __read_only;
+static int auto_flowlabels_max __read_only = IP6_AUTO_FLOW_LABEL_MAX;
static int proc_rt6_multipath_hash_policy(struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos)
--- a/net/netrom/sysctl_net_netrom.c
+++ b/net/netrom/sysctl_net_netrom.c
@@ -15,22 +15,22 @@
/*
* Values taken from NET/ROM documentation.
*/
-static int min_quality[] = {0}, max_quality[] = {255};
-static int min_obs[] = {0}, max_obs[] = {255};
-static int min_ttl[] = {0}, max_ttl[] = {255};
-static int min_t1[] = {5 * HZ};
-static int max_t1[] = {600 * HZ};
-static int min_n2[] = {2}, max_n2[] = {127};
-static int min_t2[] = {1 * HZ};
-static int max_t2[] = {60 * HZ};
-static int min_t4[] = {1 * HZ};
-static int max_t4[] = {1000 * HZ};
-static int min_window[] = {1}, max_window[] = {127};
-static int min_idle[] = {0 * HZ};
-static int max_idle[] = {65535 * HZ};
-static int min_route[] = {0}, max_route[] = {1};
-static int min_fails[] = {1}, max_fails[] = {10};
-static int min_reset[] = {0}, max_reset[] = {1};
+static int min_quality[] __read_only = {0}, max_quality[] __read_only = {255};
+static int min_obs[] __read_only = {0}, max_obs[] __read_only = {255};
+static int min_ttl[] __read_only = {0}, max_ttl[] __read_only = {255};
+static int min_t1[] __read_only = {5 * HZ};
+static int max_t1[] __read_only = {600 * HZ};
+static int min_n2[] __read_only = {2}, max_n2[] __read_only = {127};
+static int min_t2[] __read_only = {1 * HZ};
+static int max_t2[] __read_only = {60 * HZ};
+static int min_t4[] __read_only = {1 * HZ};
+static int max_t4[] __read_only = {1000 * HZ};
+static int min_window[] __read_only = {1}, max_window[] __read_only = {127};
+static int min_idle[] __read_only = {0 * HZ};
+static int max_idle[] __read_only = {65535 * HZ};
+static int min_route[] __read_only = {0}, max_route[] __read_only = {1};
+static int min_fails[] __read_only = {1}, max_fails[] __read_only = {10};
+static int min_reset[] __read_only = {0}, max_reset[] __read_only = {1};
static struct ctl_table_header *nr_table_header;
--- a/net/phonet/sysctl.c
+++ b/net/phonet/sysctl.c
@@ -35,8 +35,8 @@
#define DYNAMIC_PORT_MAX 0x7f
static DEFINE_SEQLOCK(local_port_range_lock);
-static int local_port_range_min[2] = {0, 0};
-static int local_port_range_max[2] = {1023, 1023};
+static int local_port_range_min[2] __read_only = {0, 0};
+static int local_port_range_max[2] __read_only = {1023, 1023};
static int local_port_range[2] = {DYNAMIC_PORT_MIN, DYNAMIC_PORT_MAX};
static struct ctl_table_header *phonet_table_hrd;
--- a/net/rose/sysctl_net_rose.c
+++ b/net/rose/sysctl_net_rose.c
@@ -12,15 +12,15 @@
#include <net/ax25.h>
#include <net/rose.h>
-static int min_timer[] = {1 * HZ};
-static int max_timer[] = {300 * HZ};
-static int min_idle[] = {0 * HZ};
-static int max_idle[] = {65535 * HZ};
-static int min_route[1], max_route[] = {1};
-static int min_ftimer[] = {60 * HZ};
-static int max_ftimer[] = {600 * HZ};
-static int min_maxvcs[] = {1}, max_maxvcs[] = {254};
-static int min_window[] = {1}, max_window[] = {7};
+static int min_timer[] __read_only = {1 * HZ};
+static int max_timer[] __read_only = {300 * HZ};
+static int min_idle[] __read_only = {0 * HZ};
+static int max_idle[] __read_only = {65535 * HZ};
+static int min_route[1], max_route[] __read_only = {1};
+static int min_ftimer[] __read_only = {60 * HZ};
+static int max_ftimer[] __read_only = {600 * HZ};
+static int min_maxvcs[] __read_only = {1}, max_maxvcs[] = {254};
+static int min_window[] __read_only = {1}, max_window[] = {7};
static struct ctl_table_header *rose_table_header;
--- a/net/sctp/sysctl.c
+++ b/net/sctp/sysctl.c
@@ -40,21 +40,21 @@
#include <net/sctp/sctp.h>
#include <linux/sysctl.h>
-static int zero = 0;
-static int one = 1;
-static int timer_max = 86400000; /* ms in one day */
-static int int_max = INT_MAX;
-static int sack_timer_min = 1;
-static int sack_timer_max = 500;
-static int addr_scope_max = SCTP_SCOPE_POLICY_MAX;
-static int rwnd_scale_max = 16;
-static int rto_alpha_min = 0;
-static int rto_beta_min = 0;
-static int rto_alpha_max = 1000;
-static int rto_beta_max = 1000;
+static int zero __read_only = 0;
+static int one __read_only = 1;
+static int timer_max __read_only = 86400000; /* ms in one day */
+static int int_max __read_only = INT_MAX;
+static int sack_timer_min __read_only = 1;
+static int sack_timer_max __read_only = 500;
+static int addr_scope_max __read_only = SCTP_SCOPE_POLICY_MAX;
+static int rwnd_scale_max __read_only = 16;
+static int rto_alpha_min __read_only = 0;
+static int rto_beta_min __read_only = 0;
+static int rto_alpha_max __read_only = 1000;
+static int rto_beta_max __read_only = 1000;
-static unsigned long max_autoclose_min = 0;
-static unsigned long max_autoclose_max =
+static unsigned long max_autoclose_min __read_only = 0;
+static unsigned long max_autoclose_max __read_only =
(MAX_SCHEDULE_TIMEOUT / HZ > UINT_MAX)
? UINT_MAX : MAX_SCHEDULE_TIMEOUT / HZ;
--- a/net/x25/sysctl_net_x25.c
+++ b/net/x25/sysctl_net_x25.c
@@ -13,8 +13,8 @@
#include <linux/init.h>
#include <net/x25.h>
-static int min_timer[] = { 1 * HZ };
-static int max_timer[] = { 300 * HZ };
+static int min_timer[] __read_only = { 1 * HZ };
+static int max_timer[] __read_only = { 300 * HZ };
static struct ctl_table_header *x25_table_header;
@@ -12,6 +12,9 @@
0008-Graphene-Kernel_Hardening-misc/4.14/0005.patch
0008-Graphene-Kernel_Hardening-misc/4.14/0010.patch
0008-Graphene-Kernel_Hardening-misc/4.14/0018.patch
# misc/0021 is already present in LOS @ 28f9290ae067 (KSYM_NAME_LEN=192).
# Applying it with GNU patch's reverse detection silently shrinks it to 128
# under CONFIG_CFI_CLANG, so it must remain omitted.
0008-Graphene-Kernel_Hardening-random/4.14/0002.patch
0008-Graphene-Kernel_Hardening-random/4.14/0007.patch
0008-Graphene-Kernel_Hardening-random/4.14/0012.patch
@@ -30,13 +30,19 @@ Stage1+combo4+CVE apply.
```text
zcat /proc/config.gz | grep CONFIG_SECURITY_YAMA=y
zcat /proc/config.gz | grep CONFIG_SLAB_FREELIST_RANDOM=y
cat /proc/sys/kernel/yama/ptrace_scope # expect 1 (default)
cat /proc/sys/kernel/yama/ptrace_scope # expect 2 (ship policy)
pidof magiskd # still up
```
The ship script now applies `apply_009_yama_ptrace.py`, so the current LAB
policy is `ptrace_scope=2`, not the kernel default `1`. Matching AXP's
`persist.native_debug` 0/2 toggle is a separate userspace change and must not
be mixed into the CVE-2022-49980 boot test.
## Out of scope here
- `CVE-2022-49980` lock-scope port (still denylist)
- The original `CVE-2022-49980/ANY/0001.patch` remains denylisted. The next
isolated bacon applies `ports/cve-2022-49980-lock-scope.patch` instead.
- nf_tables high-offset CVEs
- Stage1 slab FAIL patches (`0010`/`0018` still reject on this tree)
- `hardened_malloc` / bootloader lock
@@ -0,0 +1,414 @@
#!/usr/bin/env python3
"""012: Graphene hardened_malloc on LOS 23.2, the AXP GRAPHENE_MALLOC set.
Source of the integration is GrapheneOS platform_bionic branch 16:
64-bit libc uses hardened_malloc, 32-bit stays Scudo, and SurfaceFlinger
plus camera executables stay on Scudo. AXP documents that camera exception
as applying to every camera executable, which on sunfish is
/vendor/bin/hw/android.hardware.camera.provider@2.7-service-google rather
than the newer Pixel apex path.
Library pin: GrapheneOS/hardened_malloc 1d7fc7ffe0f8 (branch 14, 2025-04-03),
the newest branch that is not the Android 17 line. device_has_arm_mte is
removed because LOS soong has no such product variable and sunfish has no MTE.
Class region size is 2GiB, as DivestOS/AXP set it, because the sunfish
kernel has a 39-bit address space (GOLDEN: VA_BITS=39 with hardened_malloc).
system/core init.rc raises vm.max_map_count to 1048576, same as GrapheneOS 16.
frameworks/av no longer sets RLIMIT_AS on this tree, so that AXP/Pie hunk
does not apply.
"""
from __future__ import annotations
import os
import pathlib
import subprocess
import sys
MARKER = "SUNFISH_A16_012_HARDENED_MALLOC"
PIN = "1d7fc7ffe0f8068a32ab2ba33df7515e7bad3f5f"
REPO = "https://github.com/GrapheneOS/hardened_malloc.git"
REGION_48BIT = "-DCONFIG_CLASS_REGION_SIZE=34359738368"
REGION_39BIT = "-DCONFIG_CLASS_REGION_SIZE=2147483648"
HERE = pathlib.Path(__file__).resolve().parent
TREE = pathlib.Path(os.environ.get("ANDROID_BUILD_TOP", "/srv/rom/src/lineage"))
def must_replace(path: pathlib.Path, old: str, new: str, label: str) -> None:
text = path.read_text()
if new in text:
print(f"already applied: {path} ({label})")
return
if old not in text:
sys.exit(f"anchor missing for {label} in {path}")
path.write_text(text.replace(old, new, 1))
print(f"patched {label}: {path}")
def rewrite_region_comment(bp: pathlib.Path) -> None:
text = bp.read_text()
stale = f'{REGION_39BIT}", // 32GiB'
fresh = f'{REGION_39BIT}", // 2GiB, 39-bit VA (DivestOS/AXP)'
if stale not in text:
return
bp.write_text(text.replace(stale, fresh))
print(f"region comment: {bp}")
def ensure_library() -> None:
dest = TREE / "external/hardened_malloc"
if (dest / ".git").is_dir():
head = subprocess.check_output(
["git", "-C", dest, "rev-parse", "HEAD"], text=True
).strip()
bp_text = (dest / "Android.bp").read_text()
if head == PIN and MARKER in bp_text and REGION_39BIT in bp_text:
rewrite_region_comment(dest / "Android.bp")
print(f"hardened_malloc already at {PIN}")
return
subprocess.check_call(["git", "-C", dest, "fetch", "--depth", "1", "origin", PIN])
subprocess.check_call(["git", "-C", dest, "checkout", "--detach", PIN])
else:
if dest.exists():
sys.exit(f"{dest} exists and is not a git checkout")
dest.parent.mkdir(parents=True, exist_ok=True)
subprocess.check_call(
["git", "clone", "--depth", "1", REPO, str(dest)]
)
subprocess.check_call(["git", "-C", dest, "fetch", "--depth", "1", "origin", PIN])
subprocess.check_call(["git", "-C", dest, "checkout", "--detach", PIN])
bp = dest / "Android.bp"
text = bp.read_text()
mte = """ device_has_arm_mte: {
cflags: ["-DHAS_ARM_MTE", "-march=armv8-a+dotprod+memtag"]
},
"""
if mte in text:
text = text.replace(mte, "")
# sunfish msm-4.14 is CONFIG_ARM64_VA_BITS=39. The 32GiB default class
# region needs 48-bit VA and aborts every 64-bit process at boot.
# DivestOS/AXP Patch.sh: sed 's/34359738368/2147483648/' Android.bp.
text = text.replace(REGION_48BIT, REGION_39BIT)
text = text.replace(
f'{REGION_39BIT}", // 32GiB',
f'{REGION_39BIT}", // 2GiB, 39-bit VA (DivestOS/AXP)',
)
bp.write_text(text)
text = bp.read_text()
if REGION_39BIT not in text:
sys.exit(f"CONFIG_CLASS_REGION_SIZE is not 2GiB in {bp}")
if MARKER not in text:
text = text.replace(
"cc_library {\n",
f"cc_library {{\n // {MARKER}: sunfish has no MTE; LOS soong has no device_has_arm_mte.\n",
1,
)
bp.write_text(text)
head = subprocess.check_output(
["git", "-C", dest, "rev-parse", "HEAD"], text=True
).strip()
if head != PIN:
sys.exit(f"hardened_malloc HEAD {head} != {PIN}")
print(f"hardened_malloc {PIN}")
def patch_bionic() -> None:
wrapper = TREE / "bionic/libc/bionic/h_malloc_wrapper.cpp"
src = HERE / "h_malloc_wrapper.cpp"
if not wrapper.exists() or wrapper.read_text() != src.read_text():
wrapper.write_text(src.read_text())
print(f"installed {wrapper}")
must_replace(
TREE / "bionic/libc/Android.bp",
"""cc_defaults {
name: "libc_native_allocator_defaults",
whole_static_libs: [
"libscudo",
],
cflags: [
"-DUSE_SCUDO",
],
header_libs: ["gwp_asan_headers"],
product_variables: {
malloc_low_memory: {
cflags: ["-UUSE_SCUDO"],
whole_static_libs: [
"libjemalloc5",
"libc_jemalloc_wrapper",
],
exclude_static_libs: [
"libscudo",
],
},
},
}
""",
f"""cc_defaults {{
// {MARKER}
name: "libc_native_allocator_defaults",
header_libs: ["gwp_asan_headers"],
whole_static_libs: ["libscudo"],
// GrapheneOS 16: hardened_malloc on 64-bit, Scudo on 32-bit.
multilib: {{
lib32: {{
cflags: ["-DUSE_SCUDO"],
}},
lib64: {{
srcs: ["bionic/h_malloc_wrapper.cpp"],
cflags: [
"-DH_MALLOC_PREFIX",
"-DUSE_H_MALLOC",
"-DUSE_SCUDO",
],
whole_static_libs: [
"libhardened_malloc",
],
}},
}},
}}
""",
"allocator-defaults",
)
must_replace(
TREE / "bionic/libc/bionic/malloc_common.h",
"""#else // __has_feature(hwaddress_sanitizer)
#if defined(USE_SCUDO)
""",
f"""#else // __has_feature(hwaddress_sanitizer)
// {MARKER}
#ifdef __LP64__
#ifndef USE_H_MALLOC
#error missing USE_H_MALLOC
#endif
#include "h_malloc.h"
#define Malloc(function) h_ ## function
__BEGIN_DECLS
int h_malloc_info(int options, FILE* fp);
__END_DECLS
#if defined(USE_SCUDO)
#include "scudo.h"
void InitNativeAllocatorDispatch(libc_globals* globals);
#endif
#define BOTH_H_MALLOC_AND_SCUDO
#else // 32-bit
#if defined(USE_SCUDO)
""",
"malloc-header",
)
header = TREE / "bionic/libc/bionic/malloc_common.h"
text = header.read_text()
tail = """#define Malloc(function) je_ ## function
#endif
#endif
const MallocDispatch* NativeAllocatorDispatch();
"""
tail_new = """#define Malloc(function) je_ ## function
#endif
#endif // __LP64__
#endif // __has_feature(hwaddress_sanitizer)
const MallocDispatch* NativeAllocatorDispatch();
"""
if tail_new not in text:
if tail not in text:
sys.exit(f"header close anchor missing in {header}")
header.write_text(text.replace(tail, tail_new, 1))
must_replace(
TREE / "bionic/libc/bionic/malloc_common.cpp",
"""const MallocDispatch* NativeAllocatorDispatch() {
return &__libc_malloc_default_dispatch;
}
""",
f"""#if defined(BOTH_H_MALLOC_AND_SCUDO)
// {MARKER}: camera executables and surfaceflinger stay on Scudo.
#define ScudoMalloc(function) scudo_ ## function
static constexpr MallocDispatch __scudo_malloc_dispatch __attribute__((unused)) = {{
ScudoMalloc(calloc),
ScudoMalloc(free),
ScudoMalloc(mallinfo),
ScudoMalloc(malloc),
ScudoMalloc(malloc_usable_size),
ScudoMalloc(memalign),
ScudoMalloc(posix_memalign),
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
ScudoMalloc(pvalloc),
#endif
ScudoMalloc(realloc),
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
ScudoMalloc(valloc),
#endif
ScudoMalloc(malloc_iterate),
ScudoMalloc(malloc_disable),
ScudoMalloc(malloc_enable),
ScudoMalloc(mallopt),
ScudoMalloc(aligned_alloc),
ScudoMalloc(malloc_info),
}};
static const MallocDispatch* native_allocator_dispatch;
void InitNativeAllocatorDispatch(libc_globals* globals) {{
bool hardened_impl = true;
if (globals->flags & GLOBAL_FLAG_DISABLE_HARDENED_MALLOC) {{
hardened_impl = false;
}} else if (getenv("DISABLE_HARDENED_MALLOC") != nullptr) {{
hardened_impl = false;
}}
const MallocDispatch* table = hardened_impl ?
&__libc_malloc_default_dispatch :
&__scudo_malloc_dispatch;
if (!hardened_impl) {{
globals->malloc_dispatch_table = __scudo_malloc_dispatch;
globals->current_dispatch_table = &globals->malloc_dispatch_table;
globals->default_dispatch_table = &globals->malloc_dispatch_table;
}}
native_allocator_dispatch = table;
}}
const MallocDispatch* NativeAllocatorDispatch() {{
return native_allocator_dispatch;
}}
#else
const MallocDispatch* NativeAllocatorDispatch() {{
return &__libc_malloc_default_dispatch;
}}
#endif
""",
"dispatch",
)
must_replace(
TREE / "bionic/libc/bionic/malloc_common_dynamic.cpp",
"""void MallocInitImpl(libc_globals* globals) {
char prop[PROP_VALUE_MAX];
""",
f"""void MallocInitImpl(libc_globals* globals) {{
#if defined(BOTH_H_MALLOC_AND_SCUDO)
// {MARKER}
InitNativeAllocatorDispatch(globals);
#endif
char prop[PROP_VALUE_MAX];
""",
"malloc-init",
)
must_replace(
TREE / "bionic/libc/private/bionic_globals.h",
""" MallocDispatch malloc_dispatch_table;
};
""",
f""" MallocDispatch malloc_dispatch_table;
// {MARKER}
int flags;
}};
#define GLOBAL_FLAG_DISABLE_HARDENED_MALLOC 1
""",
"globals",
)
init = TREE / "bionic/libc/bionic/libc_init_dynamic.cpp"
text = init.read_text()
if '#include <string.h>' not in text:
if '#include <stdlib.h>' not in text:
sys.exit(f"stdlib include missing in {init}")
text = text.replace(
"#include <stdlib.h>\n",
"#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n",
1,
)
helper = f"""
// {MARKER}: GrapheneOS disables hardened_malloc for surfaceflinger.
// AXP expands the camera exception to every camera executable. sunfish runs
// /vendor/bin/hw/android.hardware.camera.provider@2.7-service-google.
static void init_hardened_malloc_exclusions(libc_globals* globals) {{
char exe_path[500];
ssize_t n = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
if (n <= 0) {{
globals->flags = 0;
return;
}}
exe_path[n] = '\\0';
if (strcmp(exe_path, "/system/bin/surfaceflinger") == 0 ||
strstr(exe_path, "camera") != nullptr) {{
globals->flags = GLOBAL_FLAG_DISABLE_HARDENED_MALLOC;
}} else {{
globals->flags = 0;
}}
}}
"""
# __libc_preinit_impl must stay noinline (stack protector guard setup).
noinline = "__attribute__((noinline))\n"
anchor = noinline + "static void __libc_preinit_impl() {"
misplaced = noinline + helper + "static void __libc_preinit_impl() {"
if misplaced in text:
text = text.replace(misplaced, helper + anchor, 1)
if "init_hardened_malloc_exclusions" not in text:
if anchor not in text:
sys.exit(f"preinit anchor missing in {init}")
text = text.replace(anchor, helper + anchor, 1)
if anchor not in text:
sys.exit(f"__libc_preinit_impl lost noinline in {init}")
old_mutate = " __libc_globals.mutate(__libc_init_malloc);\n"
new_mutate = """ __libc_globals.mutate([](libc_globals* globals) {
init_hardened_malloc_exclusions(globals);
__libc_init_malloc(globals);
});
"""
if "init_hardened_malloc_exclusions(globals)" not in text:
if old_mutate not in text:
sys.exit(f"mutate anchor missing in {init}")
text = text.replace(old_mutate, new_mutate, 1)
init.write_text(text)
print(f"patched exclusions: {init}")
def patch_init_rc() -> None:
must_replace(
TREE / "system/core/rootdir/init.rc",
" symlink /sys/class/block/${dev.mnt.rootdisk.data} /dev/sys/block/by-name/rootdisk\n",
" symlink /sys/class/block/${dev.mnt.rootdisk.data} /dev/sys/block/by-name/rootdisk\n\n"
f" # {MARKER}: GrapheneOS 16 vm.max_map_count for hardened_malloc\n"
" write /proc/sys/vm/max_map_count 1048576\n",
"max-map-count",
)
def main() -> None:
if not (TREE / "bionic/libc/Android.bp").is_file():
sys.exit(f"not an Android tree: {TREE}")
ensure_library()
patch_bionic()
patch_init_rc()
print(f"done: {MARKER}")
if __name__ == "__main__":
main()
@@ -0,0 +1,51 @@
#include <errno.h>
#include <malloc.h>
#include <sys/param.h>
#include <unistd.h>
#include <private/MallocXmlElem.h>
#include "h_malloc.h"
__BEGIN_DECLS
int h_malloc_info(int options, FILE* fp);
__END_DECLS
int h_malloc_info(int options, FILE* fp) {
if (options != 0) {
errno = EINVAL;
return -1;
}
fflush(fp);
int fd = fileno(fp);
MallocXmlElem root(fd, "malloc", "version=\"jemalloc-1\"");
// Dump all of the large allocations in the arenas.
for (size_t i = 0; i < h_mallinfo_narenas(); i++) {
struct mallinfo mi = h_mallinfo_arena_info(i);
if (mi.hblkhd != 0) {
MallocXmlElem arena_elem(fd, "heap", "nr=\"%d\"", i);
{
MallocXmlElem(fd, "allocated-large").Contents("%zu", mi.ordblks);
MallocXmlElem(fd, "allocated-huge").Contents("%zu", mi.uordblks);
MallocXmlElem(fd, "allocated-bins").Contents("%zu", mi.fsmblks);
size_t total = 0;
for (size_t j = 0; j < h_mallinfo_nbins(); j++) {
struct mallinfo mi = h_mallinfo_bin_info(i, j);
if (mi.ordblks != 0) {
MallocXmlElem bin_elem(fd, "bin", "nr=\"%d\"", j);
MallocXmlElem(fd, "allocated").Contents("%zu", mi.ordblks);
MallocXmlElem(fd, "nmalloc").Contents("%zu", mi.uordblks);
MallocXmlElem(fd, "ndalloc").Contents("%zu", mi.fordblks);
total += mi.ordblks;
}
}
MallocXmlElem(fd, "bins-total").Contents("%zu", total);
}
}
}
return 0;
}