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 |   4 +-
 3 files changed, 196 insertions(+), 1 deletion(-)
 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 aa6918451fc1..960d7ae2eba9 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2223,7 +2223,7 @@ public final class SystemServer implements Dumpable {
                 Slog.e(TAG, "Failure starting HardwarePropertiesManagerService", e);
             }
             t.traceEnd();
-          
+
             if (!isWatch) {
                 t.traceBegin("StartTwilightService");
                 mSystemServiceManager.startService(TwilightService.class);
@@ -3062,6 +3062,8 @@ public final class SystemServer implements Dumpable {
                 reportWtf("Triggering OdsignStatsLogger", e);
             }
             t.traceEnd();
+
+            com.android.server.ext.SystemServerExt.init(mSystemContext, mPackageManagerService);
         }, t);
 
         t.traceBegin("StartSystemUI");
