SysUI: Move all sensor operations to the background thread

Registering / unregistering listeners for sensors now frequently takes several hundred
miliseconds. Work around this by moving all sensor operations off the main thread.

Change-Id: Ic3537889e1d6e045149942bebb0211afe1192033
Fixes: 64103634
Test: Turn on phone from AOD2. Verify no jank.
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index e5c729f..9c1cb4e 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -16,6 +16,7 @@
 
 import android.content.Context;
 import android.content.res.Configuration;
+import android.hardware.SensorManager;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -84,6 +85,7 @@
 import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerServiceImpl;
+import com.android.systemui.util.AsyncSensorManager;
 import com.android.systemui.util.leak.GarbageMonitor;
 import com.android.systemui.util.leak.LeakDetector;
 import com.android.systemui.util.leak.LeakReporter;
@@ -156,6 +158,9 @@
         mProviders.put(ActivityStarterDelegate.class, () ->
                 getDependency(ActivityStarter.class));
 
+        mProviders.put(AsyncSensorManager.class, () ->
+                new AsyncSensorManager(mContext.getSystemService(SensorManager.class)));
+
         mProviders.put(BluetoothController.class, () ->
                 new BluetoothControllerImpl(mContext, getDependency(BG_LOOPER)));
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
index e92ed2f..e4b405f 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
@@ -35,6 +35,7 @@
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.analytics.DataCollector;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.util.AsyncSensorManager;
 
 import java.io.PrintWriter;
 
@@ -87,7 +88,7 @@
 
     private FalsingManager(Context context) {
         mContext = context;
-        mSensorManager = mContext.getSystemService(SensorManager.class);
+        mSensorManager = Dependency.get(AsyncSensorManager.class);
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
         mDataCollector = DataCollector.getInstance(mContext);
         mHumanInteractionClassifier = HumanInteractionClassifier.getInstance(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 91ca571..cbdabf5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -24,10 +24,12 @@
 import android.os.Handler;
 
 import com.android.internal.hardware.AmbientDisplayConfiguration;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SystemUIApplication;
 import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.util.AsyncSensorManager;
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
 
@@ -39,7 +41,7 @@
     /** Creates a DozeMachine with its parts for {@code dozeService}. */
     public DozeMachine assembleMachine(DozeService dozeService) {
         Context context = dozeService;
-        SensorManager sensorManager = context.getSystemService(SensorManager.class);
+        SensorManager sensorManager = Dependency.get(AsyncSensorManager.class);
         AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
 
         DozeHost host = getHost(dozeService);
diff --git a/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java
new file mode 100644
index 0000000..a1cabff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.systemui.util;
+
+import android.hardware.HardwareBuffer;
+import android.hardware.Sensor;
+import android.hardware.SensorAdditionalInfo;
+import android.hardware.SensorDirectChannel;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.TriggerEventListener;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.MemoryFile;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * Wrapper around sensor manager that hides potential sources of latency.
+ *
+ * Offloads fetching (non-dynamic) sensors and (un)registering listeners onto a background thread
+ * without blocking. Note that this means registering listeners now always appears successful even
+ * if it is not.
+ */
+public class AsyncSensorManager extends SensorManager {
+
+    private static final String TAG = "AsyncSensorManager";
+
+    private final SensorManager mInner;
+    private final List<Sensor> mSensorCache;
+    private final HandlerThread mHandlerThread = new HandlerThread("async_sensor");
+    private final Handler mHandler;
+
+    public AsyncSensorManager(SensorManager inner) {
+        mInner = inner;
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        mSensorCache = mInner.getSensorList(Sensor.TYPE_ALL);
+    }
+
+    @Override
+    protected List<Sensor> getFullSensorList() {
+        return mSensorCache;
+    }
+
+    @Override
+    protected List<Sensor> getFullDynamicSensorList() {
+        return mInner.getDynamicSensorList(Sensor.TYPE_ALL);
+    }
+
+    @Override
+    protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor, int delayUs,
+            Handler handler, int maxReportLatencyUs, int reservedFlags) {
+        mHandler.post(() -> {
+            if (!mInner.registerListener(listener, sensor, delayUs, maxReportLatencyUs, handler)) {
+                Log.e(TAG, "Registering " + listener + " for " + sensor + " failed.");
+            }
+        });
+        return true;
+    }
+
+    @Override
+    protected boolean flushImpl(SensorEventListener listener) {
+        return mInner.flush(listener);
+    }
+
+    @Override
+    protected SensorDirectChannel createDirectChannelImpl(MemoryFile memoryFile,
+            HardwareBuffer hardwareBuffer) {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    protected void destroyDirectChannelImpl(SensorDirectChannel channel) {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    protected int configureDirectChannelImpl(SensorDirectChannel channel, Sensor s, int rate) {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    protected void registerDynamicSensorCallbackImpl(DynamicSensorCallback callback,
+            Handler handler) {
+        mHandler.post(() -> mInner.registerDynamicSensorCallback(callback, handler));
+    }
+
+    @Override
+    protected void unregisterDynamicSensorCallbackImpl(DynamicSensorCallback callback) {
+        mHandler.post(() -> mInner.unregisterDynamicSensorCallback(callback));
+    }
+
+    @Override
+    protected boolean requestTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) {
+        mHandler.post(() -> {
+            if (!mInner.requestTriggerSensor(listener, sensor)) {
+                Log.e(TAG, "Requesting " + listener + " for " + sensor + " failed.");
+            }
+        });
+        return true;
+    }
+
+    @Override
+    protected boolean cancelTriggerSensorImpl(TriggerEventListener listener, Sensor sensor,
+            boolean disable) {
+        Preconditions.checkArgument(disable);
+
+        mHandler.post(() -> {
+            if (!mInner.cancelTriggerSensor(listener, sensor)) {
+                Log.e(TAG, "Canceling " + listener + " for " + sensor + " failed.");
+            }
+        });
+        return true;
+    }
+
+    @Override
+    protected boolean initDataInjectionImpl(boolean enable) {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    protected boolean injectSensorDataImpl(Sensor sensor, float[] values, int accuracy,
+            long timestamp) {
+        throw new UnsupportedOperationException("not implemented");
+    }
+
+    @Override
+    protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) {
+        mHandler.post(() -> mInner.setOperationParameter(parameter));
+        return true;
+    }
+
+    @Override
+    protected void unregisterListenerImpl(SensorEventListener listener, Sensor sensor) {
+        mHandler.post(() -> mInner.unregisterListener(listener, sensor));
+    }
+}