Make TouchInteractionService direct boot aware

- Add directBootAware="true" to TouchInteractionService manifest component
- Add DeviceLockedInputConsumer which just sends a home intent on touch down

Test:
- Reboot
- Swipe up anywhere to get to bouncer (pin/password/pattern)
- Click "Emergency" to launch dialer while still in direct boot
- Swipe up from the nav bar to exit/bring up bouncer

Test:
- Lock screen
- Double press power to launch camera
- Swipe up from nav bar to exit/bring up bouncer

Bug: 125364936
Change-Id: I7a4cd2dc3a635daf4bb9a643a1e5251ca4e91e33
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 542a235..62d0500 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -38,7 +38,8 @@
 
         <service
             android:name="com.android.quickstep.TouchInteractionService"
-            android:permission="android.permission.STATUS_BAR_SERVICE" >
+            android:permission="android.permission.STATUS_BAR_SERVICE"
+            android:directBootAware="true" >
             <intent-filter>
                 <action android:name="android.intent.action.QUICKSTEP_SERVICE" />
             </intent-filter>
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java
new file mode 100644
index 0000000..047f34c
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 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.quickstep;
+
+import android.content.Context;
+import android.content.Intent;
+import android.view.MotionEvent;
+
+/**
+ * A dummy input consumer used when the device is still locked, e.g. from secure camera.
+ */
+public class DeviceLockedInputConsumer implements InputConsumer {
+
+    private final Context mContext;
+
+    public DeviceLockedInputConsumer(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public void onMotionEvent(MotionEvent ev) {
+        // For now, just start the home intent so user is prompted to unlock the device.
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mContext.startActivity(new Intent(Intent.ACTION_MAIN)
+                    .addCategory(Intent.CATEGORY_HOME)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+        }
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index ddf3ad5..cf50fc1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -23,13 +23,18 @@
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
+import android.app.KeyguardManager;
 import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.graphics.Region;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.Process;
 import android.util.Log;
 import android.util.Pair;
 import android.view.Choreographer;
@@ -37,6 +42,8 @@
 import android.view.MotionEvent;
 
 import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.systemui.shared.recents.IOverviewProxy;
@@ -49,6 +56,8 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Service connected by system-UI for handling touch interaction.
@@ -75,8 +84,10 @@
         public void onInitialize(Bundle bundle) {
             mISystemUiProxy = ISystemUiProxy.Stub
                     .asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
-            mRecentsModel.setSystemUiProxy(mISystemUiProxy);
-            mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
+            runWhenUserUnlocked(() -> {
+                mRecentsModel.setSystemUiProxy(mISystemUiProxy);
+                mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
+            });
 
             disposeEventHandlers();
             mInputEventReceiver = InputChannelCompat.fromBundle(bundle, KEY_EXTRA_INPUT_CHANNEL,
@@ -128,8 +139,10 @@
 
         public void onBind(ISystemUiProxy iSystemUiProxy) {
             mISystemUiProxy = iSystemUiProxy;
-            mRecentsModel.setSystemUiProxy(mISystemUiProxy);
-            mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
+            runWhenUserUnlocked(() -> {
+                mRecentsModel.setSystemUiProxy(mISystemUiProxy);
+                mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
+            });
 
             // On Bind is received before onInitialize which will dispose these handlers
             disposeEventHandlers();
@@ -138,7 +151,6 @@
                     TouchInteractionService.this::onInputEvent);
             mDeprecatedDispatcher = pair.first;
             mInputEventReceiver = pair.second;
-
         }
     };
 
@@ -148,6 +160,7 @@
         return sConnected;
     }
 
+    private KeyguardManager mKM;
     private ActivityManagerWrapper mAM;
     private RecentsModel mRecentsModel;
     private ISystemUiProxy mISystemUiProxy;
@@ -159,6 +172,17 @@
     private InputConsumerController mInputConsumer;
     private SwipeSharedState mSwipeSharedState;
 
+    private boolean mIsUserUnlocked;
+    private List<Runnable> mOnUserUnlockedCallbacks;
+    private BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
+                initWhenUserUnlocked();
+            }
+        }
+    };
+
     private InputConsumer mConsumer = InputConsumer.NO_OP;
     private Choreographer mMainChoreographer;
 
@@ -170,10 +194,29 @@
     @Override
     public void onCreate() {
         super.onCreate();
+
+        // Initialize anything here that is needed in direct boot mode.
+        // Everything else should be initialized in initWhenUserUnlocked() below.
+        mKM = getSystemService(KeyguardManager.class);
+        mMainChoreographer = Choreographer.getInstance();
+        mOnUserUnlockedCallbacks = new ArrayList<>();
+
+        if (UserManagerCompat.getInstance(this).isUserUnlocked(Process.myUserHandle())) {
+            initWhenUserUnlocked();
+        } else {
+            mIsUserUnlocked = false;
+            registerReceiver(mUserUnlockedReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
+        }
+
+        sConnected = true;
+    }
+
+    private void initWhenUserUnlocked() {
+        mIsUserUnlocked = true;
+
         mAM = ActivityManagerWrapper.getInstance();
         mRecentsModel = RecentsModel.INSTANCE.get(this);
         mOverviewComponentObserver = new OverviewComponentObserver(this);
-        mMainChoreographer = Choreographer.getInstance();
 
         mOverviewCommandHelper = new OverviewCommandHelper(this, mOverviewComponentObserver);
         mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(this);
@@ -183,18 +226,34 @@
         mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
         mInputConsumer.registerInputConsumer();
 
-        sConnected = true;
+        for (Runnable callback : mOnUserUnlockedCallbacks) {
+            callback.run();
+        }
+        mOnUserUnlockedCallbacks.clear();
 
         // Temporarily disable model preload
         // new ModelPreload().start(this);
+
+        Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver);
+    }
+
+    private void runWhenUserUnlocked(Runnable callback) {
+        if (mIsUserUnlocked) {
+            callback.run();
+        } else {
+            mOnUserUnlockedCallbacks.add(callback);
+        }
     }
 
     @Override
     public void onDestroy() {
-        mInputConsumer.unregisterInputConsumer();
-        mOverviewComponentObserver.onDestroy();
+        if (mIsUserUnlocked) {
+            mInputConsumer.unregisterInputConsumer();
+            mOverviewComponentObserver.onDestroy();
+        }
         disposeEventHandlers();
         sConnected = false;
+        Utilities.unregisterReceiverSafely(this, mUserUnlockedReceiver);
         super.onDestroy();
     }
 
@@ -234,6 +293,13 @@
     }
 
     private InputConsumer newConsumer(boolean useSharedState, MotionEvent event) {
+        // TODO: this makes a binder call every touch down. we should move to a listener pattern.
+        if (mKM.isDeviceLocked()) {
+            // This handles apps launched in direct boot mode (e.g. dialer) as well as apps launched
+            // while device is locked even after exiting direct boot mode (e.g. camera).
+            return new DeviceLockedInputConsumer(this);
+        }
+
         RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
         if (!useSharedState) {
             mSwipeSharedState.clearAllState();
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 74fa447..7bb6071 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -19,12 +19,14 @@
 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
 import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
 
+import android.app.KeyguardManager;
 import android.content.ComponentName;
 import android.content.ContentProviderClient;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Handler;
+import android.os.Process;
 import android.util.Log;
 
 import com.android.launcher3.compat.LauncherAppsCompat;
@@ -66,6 +68,9 @@
     }
 
     private LauncherAppState(Context context) {
+        if (!UserManagerCompat.getInstance(context).isUserUnlocked(Process.myUserHandle())) {
+            throw new RuntimeException("LauncherAppState should not start in direct boot mode");
+        }
         if (getLocalProvider(context) == null) {
             throw new RuntimeException(
                     "Initializing LauncherAppState in the absence of LauncherProvider");
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index e5ab2d1..832e9c9 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -16,8 +16,12 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+
 import android.app.ActivityManager;
 import android.app.WallpaperManager;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -67,9 +71,6 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
-
 /**
  * Various utilities shared amongst the Launcher's classes.
  */
@@ -590,4 +591,10 @@
         outRect.set(viewLocationLeft, viewLocationTop, viewLocationLeft + rect.width(),
                 viewLocationTop + rect.height());
     }
+
+    public static void unregisterReceiverSafely(Context context, BroadcastReceiver receiver) {
+        try {
+            context.unregisterReceiver(receiver);
+        } catch (IllegalArgumentException e) { }
+    }
 }