Merge "Support launching home activity on secondary display"
diff --git a/Android.bp b/Android.bp
index faad6f3..9f93d39 100644
--- a/Android.bp
+++ b/Android.bp
@@ -152,9 +152,10 @@
         ":libcamera_client_framework_aidl",
         "core/java/android/hardware/IConsumerIrService.aidl",
         "core/java/android/hardware/ISerialManager.aidl",
+        "core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl",
+        "core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl",
         "core/java/android/hardware/biometrics/IBiometricService.aidl",
         "core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl",
-        "core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl",
         "core/java/android/hardware/biometrics/IBiometricServiceLockoutResetCallback.aidl",
         "core/java/android/hardware/display/IDisplayManager.aidl",
         "core/java/android/hardware/display/IDisplayManagerCallback.aidl",
diff --git a/api/current.txt b/api/current.txt
index 70ec35d..fe6b2a6 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -15963,7 +15963,10 @@
 package android.hardware.biometrics {
 
   public class BiometricManager {
-    method public boolean hasEnrolledBiometrics();
+    method public int canAuthenticate();
+    field public static final int ERROR_NONE = 0; // 0x0
+    field public static final int ERROR_NO_BIOMETRICS = 11; // 0xb
+    field public static final int ERROR_UNAVAILABLE = 1; // 0x1
   }
 
   public class BiometricPrompt {
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 67acfe9..16f6bda 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -21,13 +21,13 @@
 import android.annotation.UnsupportedAppUsage;
 import android.content.ComponentCallbacks2;
 import android.content.ComponentName;
-import android.content.Intent;
-import android.content.ContextWrapper;
 import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
 import android.content.res.Configuration;
 import android.os.Build;
-import android.os.RemoteException;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.util.Log;
 
 import java.io.FileDescriptor;
@@ -391,7 +391,7 @@
      * don't recreate until a future explicit call to
      * {@link Context#startService Context.startService(Intent)}.  The
      * service will not receive a {@link #onStartCommand(Intent, int, int)}
-     * call with a null Intent because it will not be re-started if there
+     * call with a null Intent because it will not be restarted if there
      * are no pending Intents to deliver.
      * 
      * <p>This mode makes sense for things that want to do some work as a
@@ -416,7 +416,7 @@
      * redelivery until the service calls {@link #stopSelf(int)} with the
      * start ID provided to {@link #onStartCommand}.  The
      * service will not receive a {@link #onStartCommand(Intent, int, int)}
-     * call with a null Intent because it will will only be re-started if
+     * call with a null Intent because it will only be restarted if
      * it is not finished processing all Intents sent to it (and any such
      * pending events will be delivered at the point of restart).
      */
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java
index 6150be3..2a64c2e 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -34,6 +34,13 @@
     //
 
     /**
+     * This was not added here since it would update BiometricPrompt API. But, is used in
+     * BiometricManager.
+     * @hide
+     */
+    int BIOMETRIC_ERROR_NONE = 0;
+
+    /**
      * The hardware is unavailable. Try again later.
      */
     int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1;
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index eea5f9b..b8739b9 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -17,16 +17,35 @@
 package android.hardware.biometrics;
 
 import static android.Manifest.permission.USE_BIOMETRIC;
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 
 import android.annotation.RequiresPermission;
 import android.content.Context;
 import android.os.RemoteException;
+import android.util.Slog;
 
 /**
  * A class that contains biometric utilities. For authentication, see {@link BiometricPrompt}.
  */
 public class BiometricManager {
 
+    private static final String TAG = "BiometricManager";
+
+    /**
+     * No error detected.
+     */
+    public static final int ERROR_NONE = BiometricConstants.BIOMETRIC_ERROR_NONE;
+
+    /**
+     * The hardware is unavailable. Try again later.
+     */
+    public static final int ERROR_UNAVAILABLE = BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
+
+    /**
+     * The user does not have any biometrics enrolled.
+     */
+    public static final int ERROR_NO_BIOMETRICS = BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS;
+
     private final Context mContext;
     private final IBiometricService mService;
 
@@ -41,16 +60,42 @@
     }
 
     /**
-     * Determine if there is at least one biometric enrolled.
+     * Determine if biometrics can be used. In other words, determine if {@link BiometricPrompt}
+     * can be expected to be shown (hardware available, templates enrolled, user-enabled).
      *
-     * @return true if at least one biometric is enrolled, false otherwise
+     * @return Returns {@link #ERROR_NO_BIOMETRICS} if the user does not have any enrolled, or
+     *     {@link #ERROR_UNAVAILABLE} if none are currently supported/enabled. Returns
+     *     {@link #ERROR_NONE} if a biometric can currently be used (enrolled and available).
      */
     @RequiresPermission(USE_BIOMETRIC)
-    public boolean hasEnrolledBiometrics() {
-        try {
-            return mService.hasEnrolledBiometrics(mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            return false;
+    public int canAuthenticate() {
+        if (mService != null) {
+            try {
+                return mService.canAuthenticate(mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            Slog.w(TAG, "hasEnrolledBiometrics(): Service not connected");
+            return ERROR_UNAVAILABLE;
+        }
+    }
+
+    /**
+     * Listens for changes to biometric eligibility on keyguard from user settings.
+     * @param callback
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback) {
+        if (mService != null) {
+            try {
+                mService.registerEnabledOnKeyguardCallback(callback);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            Slog.w(TAG, "registerEnabledOnKeyguardCallback(): Service not connected");
         }
     }
 }
diff --git a/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl b/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl
new file mode 100644
index 0000000..d22e7e2
--- /dev/null
+++ b/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2018 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 android.hardware.biometrics;
+
+import android.hardware.biometrics.BiometricSourceType;
+
+/**
+ * @hide
+ */
+oneway interface IBiometricEnabledOnKeyguardCallback {
+    void onChanged(in BiometricSourceType type, boolean enabled);
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl
index 67c9346..27d25b8 100644
--- a/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricPromptReceiver.aidl
@@ -15,9 +15,6 @@
  */
 package android.hardware.biometrics;
 
-import android.os.Bundle;
-import android.os.UserHandle;
-
 /**
  * Communication channel from the BiometricPrompt (SysUI) back to AuthenticationClient.
  * @hide
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index fd9d572..51e4ecb 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -17,6 +17,7 @@
 package android.hardware.biometrics;
 
 import android.os.Bundle;
+import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.biometrics.IBiometricPromptReceiver;
 import android.hardware.biometrics.IBiometricServiceReceiver;
 
@@ -37,6 +38,9 @@
     // Cancel authentication for the given sessionId
     void cancelAuthentication(IBinder token, String opPackageName);
 
-    // Returns true if the user has at least one enrolled biometric.
-    boolean hasEnrolledBiometrics(String opPackageName);
+    // Checks if biometrics can be used.
+    int canAuthenticate(String opPackageName);
+
+    // Register callback for when keyguard biometric eligibility changes.
+    void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback);
 }
\ No newline at end of file
diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
index 71abdd2..a6e3696 100644
--- a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
@@ -15,10 +15,6 @@
  */
 package android.hardware.biometrics;
 
-import android.hardware.biometrics.BiometricSourceType;
-import android.os.Bundle;
-import android.os.UserHandle;
-
 /**
  * Communication channel from the BiometricService back to BiometricPrompt.
  * @hide
diff --git a/core/java/android/hardware/face/IFaceServiceReceiver.aidl b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
index 16fb690..b88574b 100644
--- a/core/java/android/hardware/face/IFaceServiceReceiver.aidl
+++ b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
@@ -16,8 +16,6 @@
 package android.hardware.face;
 
 import android.hardware.face.Face;
-import android.os.Bundle;
-import android.os.UserHandle;
 
 /**
  * Communication channel from the FaceService back to FaceAuthenticationManager.
diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
index 370383f..cf1c94e 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
@@ -16,8 +16,6 @@
 package android.hardware.fingerprint;
 
 import android.hardware.fingerprint.Fingerprint;
-import android.os.Bundle;
-import android.os.UserHandle;
 
 /**
  * Communication channel from the FingerprintService back to FingerprintManager.
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 3bab87a..3e559d9 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -87,7 +87,6 @@
     void setEventDispatching(boolean enabled);
     void addWindowToken(IBinder token, int type, int displayId);
     void removeWindowToken(IBinder token, int displayId);
-    void setFocusedApp(IBinder token, boolean moveFocusNow);
     void prepareAppTransition(int transit, boolean alwaysKeepCurrent);
     int getPendingAppTransition();
     void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim,
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index d33ea0c..c1c86f0 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -158,6 +158,7 @@
     optional ScreenRotationAnimationProto screen_rotation_animation = 12;
     optional DisplayFramesProto display_frames = 13;
     optional int32 surface_size = 14;
+    optional string focused_app = 15;
 }
 
 /* represents DisplayFrames */
diff --git a/media/jni/OWNERS b/media/jni/OWNERS
new file mode 100644
index 0000000..bb91d4b
--- /dev/null
+++ b/media/jni/OWNERS
@@ -0,0 +1,2 @@
+# extra for MTP related files
+per-file android_mtp_*.cpp=marcone@google.com,jsharkey@android.com,jameswei@google.com,rmojumder@google.com
diff --git a/media/tests/MtpTests/OWNERS b/media/tests/MtpTests/OWNERS
new file mode 100644
index 0000000..1928ba8
--- /dev/null
+++ b/media/tests/MtpTests/OWNERS
@@ -0,0 +1,7 @@
+set noparent
+
+marcone@google.com
+jsharkey@android.com
+jameswei@google.com
+rmojumder@google.com
+
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index f1b53fe..c7685f8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -41,7 +41,6 @@
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -49,13 +48,14 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.database.ContentObserver;
+import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
 import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
 import android.media.AudioManager;
-import android.net.Uri;
 import android.os.BatteryManager;
 import android.os.CancellationSignal;
 import android.os.Handler;
@@ -250,51 +250,6 @@
     private static final int HW_UNAVAILABLE_TIMEOUT = 3000; // ms
     private static final int HW_UNAVAILABLE_RETRY_MAX = 3;
 
-    private class SettingObserver extends ContentObserver {
-        private final Uri FACE_UNLOCK_KEYGUARD_ENABLED =
-                Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED);
-
-        private final ContentResolver mContentResolver;
-
-        /**
-         * Creates a content observer.
-         *
-         * @param handler The handler to run {@link #onChange} on, or null if none.
-         */
-        public SettingObserver(Handler handler) {
-            super(handler);
-            mContentResolver = mContext.getContentResolver();
-            updateContentObserver();
-        }
-
-        public void updateContentObserver() {
-            mContentResolver.unregisterContentObserver(this);
-            mContentResolver.registerContentObserver(FACE_UNLOCK_KEYGUARD_ENABLED,
-                    false /* notifyForDescendents */,
-                    this,
-                    UserHandle.USER_CURRENT);
-
-            // Update the value immediately
-            onChange(true /* selfChange */, FACE_UNLOCK_KEYGUARD_ENABLED);
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            if (FACE_UNLOCK_KEYGUARD_ENABLED.equals(uri)) {
-                    mFaceSettingEnabledForUser =
-                            Settings.Secure.getIntForUser(
-                                    mContentResolver,
-                                    Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED,
-                                    1 /* default */,
-                                    UserHandle.USER_CURRENT) != 0;
-                    updateBiometricListeningState();
-            }
-        }
-    }
-
-    private final SettingObserver mSettingObserver;
-    private boolean mFaceSettingEnabledForUser;
-
     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
         @Override
         public void handleMessage(Message msg) {
@@ -400,6 +355,18 @@
         }
     };
 
+    private boolean mFaceSettingEnabledForUser;
+    private BiometricManager mBiometricManager;
+    private IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback =
+            new IBiometricEnabledOnKeyguardCallback.Stub() {
+        @Override
+        public void onChanged(BiometricSourceType type, boolean enabled) throws RemoteException {
+            if (type == BiometricSourceType.FACE) {
+                mFaceSettingEnabledForUser = enabled;
+            }
+        }
+    };
+
     private OnSubscriptionsChangedListener mSubscriptionListener =
             new OnSubscriptionsChangedListener() {
         @Override
@@ -1165,7 +1132,7 @@
     private CancellationSignal mFingerprintCancelSignal;
     private CancellationSignal mFaceCancelSignal;
     private FingerprintManager mFpm;
-    private FaceManager mFaceAuthenticationManager;
+    private FaceManager mFaceManager;
 
     /**
      * When we receive a
@@ -1434,7 +1401,6 @@
         mSubscriptionManager = SubscriptionManager.from(context);
         mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
         mStrongAuthTracker = new StrongAuthTracker(context);
-        mSettingObserver = new SettingObserver(mHandler);
 
         // Since device can't be un-provisioned, we only need to register a content observer
         // to update mDeviceProvisioned when we are...
@@ -1504,17 +1470,21 @@
         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
             mFpm = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
         }
-
         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
-            mFaceAuthenticationManager =
-                    (FaceManager) context.getSystemService(Context.FACE_SERVICE);
+            mFaceManager = (FaceManager) context.getSystemService(Context.FACE_SERVICE);
         }
+
+        if (mFpm != null || mFaceManager != null) {
+            mBiometricManager = context.getSystemService(BiometricManager.class);
+            mBiometricManager.registerEnabledOnKeyguardCallback(mBiometricEnabledCallback);
+        }
+
         updateBiometricListeningState();
         if (mFpm != null) {
             mFpm.addLockoutResetCallback(mFingerprintLockoutResetCallback);
         }
-        if (mFaceAuthenticationManager != null) {
-            mFaceAuthenticationManager.addLockoutResetCallback(mFaceLockoutResetCallback);
+        if (mFaceManager != null) {
+            mFaceManager.addLockoutResetCallback(mFaceLockoutResetCallback);
         }
 
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
@@ -1629,7 +1599,7 @@
                 mFaceCancelSignal.cancel();
             }
             mFaceCancelSignal = new CancellationSignal();
-            mFaceAuthenticationManager.authenticate(null, mFaceCancelSignal, 0,
+            mFaceManager.authenticate(null, mFaceCancelSignal, 0,
                     mFaceAuthenticationCallback, null);
             setFaceRunningState(BIOMETRIC_STATE_RUNNING);
         }
@@ -1641,9 +1611,9 @@
     }
 
     public boolean isUnlockWithFacePossible(int userId) {
-        return mFaceAuthenticationManager != null && mFaceAuthenticationManager.isHardwareDetected()
+        return mFaceManager != null && mFaceManager.isHardwareDetected()
                 && !isFaceDisabled(userId)
-                && mFaceAuthenticationManager.hasEnrolledTemplates(userId);
+                && mFaceManager.hasEnrolledTemplates(userId);
     }
 
     private void stopListeningForFingerprint() {
@@ -1765,7 +1735,6 @@
      * Handle {@link #MSG_USER_SWITCH_COMPLETE}
      */
     private void handleUserSwitchComplete(int userId) {
-        mSettingObserver.updateContentObserver();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -2437,7 +2406,7 @@
             pw.println("    strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
             pw.println("    trustManaged=" + getUserTrustIsManaged(userId));
         }
-        if (mFaceAuthenticationManager != null && mFaceAuthenticationManager.isHardwareDetected()) {
+        if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
             final int userId = ActivityManager.getCurrentUser();
             final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
             pw.println("  Face authentication state (user=" + userId + ")");
@@ -2449,6 +2418,7 @@
             pw.println("    possible=" + isUnlockWithFacePossible(userId));
             pw.println("    strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
             pw.println("    trustManaged=" + getUserTrustIsManaged(userId));
+            pw.println("    enabledByUser=" + mFaceSettingEnabledForUser);
         }
     }
 }
diff --git a/services/art-profile b/services/art-profile
index 3c60eee..328f8f7 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -2254,8 +2254,8 @@
 HPLcom/android/server/wm/DisplayContent;->lambda$new$8(Lcom/android/server/wm/DisplayContent;Lcom/android/server/wm/WindowState;)V
 HPLcom/android/server/wm/DisplayContent;->prepareSurfaces()V
 HPLcom/android/server/wm/DisplayContent;->resetAnimationBackgroundAnimator()V
-HPLcom/android/server/wm/DisplayContent;->setTouchExcludeRegion(Lcom/android/server/wm/Task;)V
 HPLcom/android/server/wm/DisplayContent;->skipTraverseChild(Lcom/android/server/wm/WindowContainer;)Z
+HPLcom/android/server/wm/DisplayContent;->updateTouchExcludeRegion()V
 HPLcom/android/server/wm/DockedStackDividerController;->isResizing()Z
 HPLcom/android/server/wm/DragDropController;->dragDropActiveLocked()Z
 HPLcom/android/server/wm/InputMonitor$UpdateInputForAllWindowsConsumer;->accept(Lcom/android/server/wm/WindowState;)V
diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java
index 6012823..f160fdf 100644
--- a/services/core/java/com/android/server/am/ActivityDisplay.java
+++ b/services/core/java/com/android/server/am/ActivityDisplay.java
@@ -928,6 +928,10 @@
                 && (mSupervisor.mService.mRunningVoice == null);
     }
 
+    void setFocusedApp(ActivityRecord r, boolean moveFocusNow) {
+        mWindowContainerController.setFocusedApp(r.appToken, moveFocusNow);
+    }
+
     /**
      * @return the stack currently above the {@param stack}.  Can be null if the {@param stack} is
      *         already top-most.
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 2ab9388..e936ea1 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -5415,7 +5415,7 @@
 
         // Do not sleep activities in this stack if we're marked as focused and the keyguard
         // is in the process of going away.
-        if (mStackSupervisor.getTopDisplayFocusedStack() == this
+        if (isFocusedStackOnDisplay()
                 && mStackSupervisor.getKeyguardController().isKeyguardGoingAway()) {
             return false;
         }
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 7f1e149..4e126d9 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3593,7 +3593,7 @@
                     stack.goToSleepIfPossible(false /* shuttingDown */);
                 } else {
                     stack.awakeFromSleepingLocked();
-                    if (isTopDisplayFocusedStack(stack) && !getKeyguardController()
+                    if (stack.isFocusedStackOnDisplay() && !getKeyguardController()
                             .isKeyguardOrAodShowing(display.mDisplayId)) {
                         // If the keyguard is unlocked - resume immediately.
                         // It is possible that the display will not be awake at the time we
diff --git a/services/core/java/com/android/server/am/ActivityTaskManagerService.java b/services/core/java/com/android/server/am/ActivityTaskManagerService.java
index bb940cb..432c4cf 100644
--- a/services/core/java/com/android/server/am/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityTaskManagerService.java
@@ -4845,8 +4845,7 @@
         updateResumedAppTrace(r);
         mLastResumedActivity = r;
 
-        // TODO(b/111361570): Support multiple focused apps in WM
-        mWindowManager.setFocusedApp(r.appToken, true);
+        r.getDisplay().setFocusedApp(r, true);
 
         applyUpdateLockStateLocked(r);
         applyUpdateVrModeLocked(r);
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 87cf9c4..5eca489 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -17,13 +17,20 @@
 package com.android.server.biometrics;
 
 import static android.Manifest.permission.USE_BIOMETRIC;
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.Manifest.permission.USE_FINGERPRINT;
 
+import android.app.ActivityManager;
 import android.app.AppOpsManager;
+import android.app.UserSwitchObserver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.database.ContentObserver;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.biometrics.IBiometricPromptReceiver;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceReceiver;
@@ -31,8 +38,10 @@
 import android.hardware.face.IFaceService;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.IFingerprintService;
+import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.DeadObjectException;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -40,12 +49,14 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.Pair;
 import android.util.Slog;
 
 import com.android.internal.R;
 import com.android.server.SystemService;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * System service that arbitrates the modality for BiometricPrompt to use.
@@ -87,6 +98,8 @@
     private final boolean mHasFeatureFingerprint;
     private final boolean mHasFeatureIris;
     private final boolean mHasFeatureFace;
+    private final SettingObserver mSettingObserver;
+    private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks;
 
     private IFingerprintService mFingerprintService;
     private IFaceService mFaceService;
@@ -120,6 +133,107 @@
         }
     }
 
+    private final class SettingObserver extends ContentObserver {
+        private final Uri FACE_UNLOCK_KEYGUARD_ENABLED =
+                Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED);
+        private final Uri FACE_UNLOCK_APP_ENABLED =
+                Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_APP_ENABLED);
+
+        private final ContentResolver mContentResolver;
+        private boolean mFaceEnabledOnKeyguard;
+        private boolean mFaceEnabledForApps;
+
+        /**
+         * Creates a content observer.
+         *
+         * @param handler The handler to run {@link #onChange} on, or null if none.
+         */
+        SettingObserver(Handler handler) {
+            super(handler);
+            mContentResolver = getContext().getContentResolver();
+            updateContentObserver();
+        }
+
+        void updateContentObserver() {
+            mContentResolver.unregisterContentObserver(this);
+            mContentResolver.registerContentObserver(FACE_UNLOCK_KEYGUARD_ENABLED,
+                    false /* notifyForDescendents */,
+                    this /* observer */,
+                    UserHandle.USER_CURRENT);
+            mContentResolver.registerContentObserver(FACE_UNLOCK_APP_ENABLED,
+                    false /* notifyForDescendents */,
+                    this /* observer */,
+                    UserHandle.USER_CURRENT);
+
+            // Update the value immediately
+            onChange(true /* selfChange */, FACE_UNLOCK_KEYGUARD_ENABLED);
+            onChange(true /* selfChange */, FACE_UNLOCK_APP_ENABLED);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            if (FACE_UNLOCK_KEYGUARD_ENABLED.equals(uri)) {
+                mFaceEnabledOnKeyguard =
+                        Settings.Secure.getIntForUser(
+                                mContentResolver,
+                                Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED,
+                                1 /* default */,
+                                UserHandle.USER_CURRENT) != 0;
+
+                List<EnabledOnKeyguardCallback> callbacks = mEnabledOnKeyguardCallbacks;
+                for (int i = 0; i < callbacks.size(); i++) {
+                    callbacks.get(i).notify(BiometricSourceType.FACE, mFaceEnabledOnKeyguard);
+                }
+            } else if (FACE_UNLOCK_APP_ENABLED.equals(uri)) {
+                mFaceEnabledForApps =
+                        Settings.Secure.getIntForUser(
+                                mContentResolver,
+                                Settings.Secure.FACE_UNLOCK_APP_ENABLED,
+                                1 /* default */,
+                                UserHandle.USER_CURRENT) != 0;
+            }
+        }
+
+        boolean getFaceEnabledOnKeyguard() {
+            return mFaceEnabledOnKeyguard;
+        }
+
+        boolean getFaceEnabledForApps() {
+            return mFaceEnabledForApps;
+        }
+    }
+
+    private final class EnabledOnKeyguardCallback implements IBinder.DeathRecipient {
+
+        private final IBiometricEnabledOnKeyguardCallback mCallback;
+
+        EnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback) {
+            mCallback = callback;
+            try {
+                mCallback.asBinder().linkToDeath(EnabledOnKeyguardCallback.this, 0);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Unable to linkToDeath", e);
+            }
+        }
+
+        void notify(BiometricSourceType sourceType, boolean enabled) {
+            try {
+                mCallback.onChanged(sourceType, enabled);
+            } catch (DeadObjectException e) {
+                Slog.w(TAG, "Death while invoking notify", e);
+                mEnabledOnKeyguardCallbacks.remove(this);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to invoke onChanged", e);
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            Slog.e(TAG, "Enabled callback binder died");
+            mEnabledOnKeyguardCallbacks.remove(this);
+        }
+    }
+
     /**
      * This is just a pass-through service that wraps Fingerprint, Iris, Face services. This service
      * should not carry any state. The reality is we need to keep a tiny amount of state so that
@@ -131,7 +245,7 @@
         public void authenticate(IBinder token, long sessionId, int userId,
                 IBiometricServiceReceiver receiver, int flags, String opPackageName,
                 Bundle bundle, IBiometricPromptReceiver dialogReceiver) throws RemoteException {
-            // Check the USE_BIOMETRIC permission here. In the BiometricService, check do the
+            // Check the USE_BIOMETRIC permission here. In the BiometricServiceBase, check do the
             // AppOps and foreground check.
             checkPermission();
 
@@ -146,8 +260,38 @@
             final int callingUserId = UserHandle.getCallingUserId();
 
             mHandler.post(() -> {
-                mCurrentModality = checkAndGetBiometricModality(receiver);
+                final Pair<Integer, Integer> result = checkAndGetBiometricModality();
+                final int modality = result.first;
+                final int error = result.second;
 
+                // Check for errors, notify callback, and return
+                if (error != BiometricConstants.BIOMETRIC_ERROR_NONE) {
+                    try {
+                        final String hardwareUnavailable =
+                                getContext().getString(R.string.biometric_error_hw_unavailable);
+                        switch (error) {
+                            case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT:
+                                receiver.onError(0 /* deviceId */, error, hardwareUnavailable);
+                                break;
+                            case BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE:
+                                receiver.onError(0 /* deviceId */, error, hardwareUnavailable);
+                                break;
+                            case BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS:
+                                receiver.onError(0 /* deviceId */, error,
+                                        getErrorString(modality, error, 0 /* vendorCode */));
+                                break;
+                            default:
+                                Slog.e(TAG, "Unhandled error");
+                                break;
+                        }
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Unable to send error", e);
+                    }
+                    return;
+                }
+
+                // Actually start authentication
+                mCurrentModality = modality;
                 try {
                     // No polymorphism :(
                     if (mCurrentModality == BIOMETRIC_FINGERPRINT) {
@@ -157,18 +301,9 @@
                     } else if (mCurrentModality == BIOMETRIC_IRIS) {
                         Slog.w(TAG, "Unsupported modality");
                     } else if (mCurrentModality == BIOMETRIC_FACE) {
-                        // If the user disabled face for apps, return ERROR_HW_UNAVAILABLE
-                        if (isFaceEnabledForApps()) {
-                            receiver.onError(0 /* deviceId */,
-                                    BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
-                                    FaceManager.getErrorString(getContext(),
-                                            BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
-                                            0 /* vendorCode */));
-                        } else {
-                            mFaceService.authenticateFromService(true /* requireConfirmation */,
-                                    token, sessionId, userId, receiver, flags, opPackageName,
-                                    bundle, dialogReceiver, callingUid, callingPid, callingUserId);
-                        }
+                        mFaceService.authenticateFromService(true /* requireConfirmation */,
+                                token, sessionId, userId, receiver, flags, opPackageName,
+                                bundle, dialogReceiver, callingUid, callingPid, callingUserId);
                     } else {
                         Slog.w(TAG, "Unsupported modality");
                     }
@@ -178,15 +313,6 @@
             });
         }
 
-        private boolean isFaceEnabledForApps() {
-            // TODO: maybe cache this and eliminate duplicated code with KeyguardUpdateMonitor
-            return Settings.Secure.getIntForUser(
-                    getContext().getContentResolver(),
-                    Settings.Secure.FACE_UNLOCK_APP_ENABLED,
-                    1 /* default */,
-                    UserHandle.USER_CURRENT) == 0;
-        }
-
         @Override // Binder call
         public void cancelAuthentication(IBinder token, String opPackageName)
                 throws RemoteException {
@@ -221,31 +347,46 @@
         }
 
         @Override // Binder call
-        public boolean hasEnrolledBiometrics(String opPackageName) {
+        public int canAuthenticate(String opPackageName) {
             checkPermission();
-
-            if (mAppOps.noteOp(AppOpsManager.OP_USE_BIOMETRIC, Binder.getCallingUid(),
-                    opPackageName) != AppOpsManager.MODE_ALLOWED) {
-                Slog.w(TAG, "Rejecting " + opPackageName + "; permission denied");
-                throw new SecurityException("Permission denied");
-            }
+            checkAppOp(opPackageName, Binder.getCallingUid());
 
             final long ident = Binder.clearCallingIdentity();
-            boolean hasEnrolled = false;
+            int error;
             try {
-                // Note: On devices with multi-modal authentication, the selection logic will need
-                // to be updated.
-                for (int i = 0; i < mAuthenticators.size(); i++) {
-                    if (mAuthenticators.get(i).getAuthenticator().hasEnrolledTemplates()) {
-                        hasEnrolled = true;
-                        break;
-                    }
-                }
+                final Pair<Integer, Integer> result = checkAndGetBiometricModality();
+                error = result.second;
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
-            return hasEnrolled;
+            return error;
         }
+
+        @Override
+        public void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback)
+                throws RemoteException {
+            checkInternalPermission();
+            mEnabledOnKeyguardCallbacks.add(new EnabledOnKeyguardCallback(callback));
+            try {
+                callback.onChanged(BiometricSourceType.FACE,
+                        mSettingObserver.getFaceEnabledOnKeyguard());
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Remote exception", e);
+            }
+        }
+    }
+
+    private void checkAppOp(String opPackageName, int callingUid) {
+        if (mAppOps.noteOp(AppOpsManager.OP_USE_BIOMETRIC, callingUid,
+                opPackageName) != AppOpsManager.MODE_ALLOWED) {
+            Slog.w(TAG, "Rejecting " + opPackageName + "; permission denied");
+            throw new SecurityException("Permission denied");
+        }
+    }
+
+    private void checkInternalPermission() {
+        getContext().enforceCallingPermission(USE_BIOMETRIC_INTERNAL,
+                "Must have MANAGE_BIOMETRIC permission");
     }
 
     private void checkPermission() {
@@ -270,11 +411,26 @@
 
         mAppOps = context.getSystemService(AppOpsManager.class);
         mHandler = new Handler(Looper.getMainLooper());
+        mEnabledOnKeyguardCallbacks = new ArrayList<>();
+        mSettingObserver = new SettingObserver(mHandler);
 
         final PackageManager pm = context.getPackageManager();
         mHasFeatureFingerprint = pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
         mHasFeatureIris = pm.hasSystemFeature(PackageManager.FEATURE_IRIS);
         mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE);
+
+        try {
+            ActivityManager.getService().registerUserSwitchObserver(
+                    new UserSwitchObserver() {
+                        @Override
+                        public void onUserSwitchComplete(int newUserId) {
+                            mSettingObserver.updateContentObserver();
+                        }
+                    }, BiometricService.class.getName()
+            );
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to register user switch observer", e);
+        }
     }
 
     @Override
@@ -305,65 +461,91 @@
      * Checks if there are any available biometrics, and returns the modality. This method also
      * returns errors through the callback (no biometric feature, hardware not detected, no
      * templates enrolled, etc). This service must not start authentication if errors are sent.
+     *
+     * @Returns A pair [Modality, Error] with Modality being one of {@link #BIOMETRIC_NONE},
+     * {@link #BIOMETRIC_FINGERPRINT}, {@link #BIOMETRIC_IRIS}, {@link #BIOMETRIC_FACE}
+     * and the error containing one of the {@link BiometricConstants} errors.
      */
-    private int checkAndGetBiometricModality(IBiometricServiceReceiver receiver) {
+    private Pair<Integer, Integer> checkAndGetBiometricModality() {
         int modality = BIOMETRIC_NONE;
-        final String hardwareUnavailable =
-                getContext().getString(R.string.biometric_error_hw_unavailable);
 
         // No biometric features, send error
         if (mAuthenticators.isEmpty()) {
-            try {
-                receiver.onError(0 /* deviceId */,
-                        BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT,
-                        hardwareUnavailable);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Unable to send error", e);
-            }
-            return BIOMETRIC_NONE;
+            return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT);
         }
 
-        // Find first authenticator that's both detected and enrolled
+        // Assuming that authenticators are listed in priority-order, the rest of this function
+        // will go through and find the first authenticator that's available, enrolled, and enabled.
+        // The tricky part is returning the correct error. Error strings that are modality-specific
+        // should also respect the priority-order.
+
+        // Find first authenticator that's detected, enrolled, and enabled.
         boolean isHardwareDetected = false;
         boolean hasTemplatesEnrolled = false;
+        boolean enabledForApps = false;
+
+        int firstHwAvailable = BIOMETRIC_NONE;
         for (int i = 0; i < mAuthenticators.size(); i++) {
-            int featureId = mAuthenticators.get(i).getType();
+            modality = mAuthenticators.get(i).getType();
             BiometricAuthenticator authenticator = mAuthenticators.get(i).getAuthenticator();
             if (authenticator.isHardwareDetected()) {
                 isHardwareDetected = true;
+                if (firstHwAvailable == BIOMETRIC_NONE) {
+                    // Store the first one since we want to return the error in correct priority
+                    // order.
+                    firstHwAvailable = modality;
+                }
                 if (authenticator.hasEnrolledTemplates()) {
                     hasTemplatesEnrolled = true;
-                    modality = featureId;
-                    break;
+                    if (isEnabledForApp(modality)) {
+                        enabledForApps = true;
+                        break;
+                    }
                 }
             }
         }
 
         // Check error conditions
         if (!isHardwareDetected) {
-            try {
-                receiver.onError(0 /* deviceId */,
-                        BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
-                        hardwareUnavailable);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Unable to send error", e);
-            }
-            return BIOMETRIC_NONE;
-        }
-        if (!hasTemplatesEnrolled) {
-            try {
-                receiver.onError(0 /* deviceId */,
-                        BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS,
-                        FaceManager.getErrorString(getContext(),
-                                BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS,
-                                0 /* vendorCode */));
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Unable to send error", e);
-            }
-            return BIOMETRIC_NONE;
+            return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
+        } else if (!hasTemplatesEnrolled) {
+            // Return the modality here so the correct error string can be sent. This error is
+            // preferred over !enabledForApps
+            return new Pair<>(firstHwAvailable, BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS);
+        } else if (!enabledForApps) {
+            return new Pair<>(BIOMETRIC_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
         }
 
-        return modality;
+        return new Pair<>(modality, BiometricConstants.BIOMETRIC_ERROR_NONE);
+    }
+
+    private boolean isEnabledForApp(int modality) {
+        switch(modality) {
+            case BIOMETRIC_FINGERPRINT:
+                return true;
+            case BIOMETRIC_IRIS:
+                return true;
+            case BIOMETRIC_FACE:
+                return mSettingObserver.getFaceEnabledForApps();
+            default:
+                Slog.w(TAG, "Unsupported modality: " + modality);
+                return false;
+        }
+    }
+
+    private String getErrorString(int type, int error, int vendorCode) {
+        switch (type) {
+            case BIOMETRIC_FINGERPRINT:
+                return FingerprintManager.getErrorString(getContext(), error, vendorCode);
+            case BIOMETRIC_IRIS:
+                Slog.w(TAG, "Modality not supported");
+                return null; // not supported
+            case BIOMETRIC_FACE:
+                return FaceManager.getErrorString(getContext(), error, vendorCode);
+            default:
+                Slog.w(TAG, "Unable to get error string for modality: " + type);
+                return null;
+        }
     }
 
     private BiometricAuthenticator getAuthenticator(int type) {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 5bd095d..4913e8b 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -209,7 +209,8 @@
     private static native void nativeSetInputDispatchMode(long ptr, boolean enabled, boolean frozen);
     private static native void nativeSetSystemUiVisibility(long ptr, int visibility);
     private static native void nativeSetFocusedApplication(long ptr,
-            InputApplicationHandle application);
+            int displayId, InputApplicationHandle application);
+    private static native void nativeSetFocusedDisplay(long ptr, int displayId);
     private static native boolean nativeTransferTouchFocus(long ptr,
             InputChannel fromChannel, InputChannel toChannel);
     private static native void nativeSetPointerSpeed(long ptr, int speed);
@@ -1431,21 +1432,27 @@
         }
     }
 
-    public void setInputWindows(InputWindowHandle[] windowHandles,
-            InputWindowHandle focusedWindowHandle, int displayId) {
-        final IWindow newFocusedWindow =
-            focusedWindowHandle != null ? focusedWindowHandle.clientWindow : null;
-        if (mFocusedWindow != newFocusedWindow) {
-            mFocusedWindow = newFocusedWindow;
-            if (mFocusedWindowHasCapture) {
-                setPointerCapture(false);
-            }
-        }
+    public void setInputWindows(InputWindowHandle[] windowHandles, int displayId) {
         nativeSetInputWindows(mPtr, windowHandles, displayId);
     }
 
-    public void setFocusedApplication(InputApplicationHandle application) {
-        nativeSetFocusedApplication(mPtr, application);
+    public void setFocusedApplication(int displayId, InputApplicationHandle application) {
+        nativeSetFocusedApplication(mPtr, displayId, application);
+    }
+
+    public void setFocusedWindow(InputWindowHandle focusedWindowHandle) {
+        final IWindow newFocusedWindow =
+            focusedWindowHandle != null ? focusedWindowHandle.clientWindow : null;
+        if (mFocusedWindow != newFocusedWindow) {
+            if (mFocusedWindowHasCapture) {
+                setPointerCapture(false);
+            }
+            mFocusedWindow = newFocusedWindow;
+        }
+    }
+
+    public void setFocusedDisplay(int displayId) {
+        nativeSetFocusedDisplay(mPtr, displayId);
     }
 
     @Override
@@ -1460,16 +1467,18 @@
             return;
         }
         setPointerCapture(enabled);
-        try {
-            mFocusedWindow.dispatchPointerCaptureChanged(enabled);
-        } catch (RemoteException ex) {
-            /* ignore */
-        }
     }
 
     private void setPointerCapture(boolean enabled) {
-        mFocusedWindowHasCapture = enabled;
-        nativeSetPointerCapture(mPtr, enabled);
+        if (mFocusedWindowHasCapture != enabled) {
+            mFocusedWindowHasCapture = enabled;
+            try {
+                mFocusedWindow.dispatchPointerCaptureChanged(enabled);
+            } catch (RemoteException ex) {
+                /* ignore */
+            }
+            nativeSetPointerCapture(mPtr, enabled);
+        }
     }
 
     public void setInputDispatchMode(boolean enabled, boolean frozen) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index f8d7fb6..21bf488 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4775,6 +4775,7 @@
             pf.bottom = df.bottom = of.bottom = displayFrames.mUnrestricted.bottom;
             // ...with content insets above the nav bar
             cf.bottom = vf.bottom = displayFrames.mStable.bottom;
+            // TODO (b/111364446): Support showing IME on non-default displays
             if (mStatusBar != null && mFocusedWindow == mStatusBar && canReceiveInput(mStatusBar)) {
                 // The status bar forces the navigation bar while it's visible. Make sure the IME
                 // avoids the navigation bar in that case.
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index e449111..74922f6 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1043,7 +1043,8 @@
                 // Do not send the windows if there is no current focus as
                 // the window manager is still looking for where to put it.
                 // We will do the work when we get a focus change callback.
-                if (mService.mCurrentFocus == null) {
+                // TODO(b/112273690): Support multiple displays
+                if (mService.getDefaultDisplayContentLocked().mCurrentFocus == null) {
                     return;
                 }
 
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 36280dd..330c54c 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -407,8 +407,7 @@
                 if (mService.mAppTransition.getAppTransition()
                         == WindowManager.TRANSIT_TASK_OPEN_BEHIND) {
                     // We're launchingBehind, add the launching activity to mOpeningApps.
-                    final WindowState win =
-                            mService.getDefaultDisplayContentLocked().findFocusedWindow();
+                    final WindowState win = mContainer.getDisplayContent().findFocusedWindow();
                     if (win != null) {
                         final AppWindowToken focusedToken = win.mAppToken;
                         if (focusedToken != null) {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index fc76102..e57fea3 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -679,11 +679,12 @@
         removed = true;
         stopFreezingScreen(true, true);
 
-        if (mService.mFocusedApp == this) {
-            if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Removing focused app token:" + this);
-            mService.mFocusedApp = null;
+        final DisplayContent dc = getDisplayContent();
+        if (dc.mFocusedApp == this) {
+            if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Removing focused app token:" + this
+                   + " displayId=" + dc.getDisplayId());
+            dc.setFocusedApp(null);
             mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
-            getDisplayContent().getInputMonitor().setFocusedAppLw(null);
         }
 
         if (!delayed) {
@@ -1064,6 +1065,18 @@
         }
     }
 
+    @Override
+    void onDisplayChanged(DisplayContent dc) {
+        DisplayContent prevDc = mDisplayContent;
+        super.onDisplayChanged(dc);
+        if (prevDc != null && prevDc.mFocusedApp == this) {
+            prevDc.setFocusedApp(null);
+            if (dc.getTopStack().getTopChild().getTopChild() == this) {
+                dc.setFocusedApp(this);
+            }
+        }
+    }
+
     /**
      * Freezes the task bounds. The size of this task reported the app will be fixed to the bounds
      * freezed by {@link Task#prepareFreezingBounds} until {@link #unfreezeBounds} gets called, even
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a762fe9..730e422 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -70,6 +70,7 @@
 import static com.android.server.wm.DisplayContentProto.DISPLAY_INFO;
 import static com.android.server.wm.DisplayContentProto.DOCKED_STACK_DIVIDER_CONTROLLER;
 import static com.android.server.wm.DisplayContentProto.DPI;
+import static com.android.server.wm.DisplayContentProto.FOCUSED_APP;
 import static com.android.server.wm.DisplayContentProto.ID;
 import static com.android.server.wm.DisplayContentProto.IME_WINDOWS;
 import static com.android.server.wm.DisplayContentProto.PINNED_STACK_CONTROLLER;
@@ -98,12 +99,17 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.CUSTOM_SCREEN_ROTATION;
+import static com.android.server.wm.WindowManagerService.H.REPORT_FOCUS_CHANGE;
+import static com.android.server.wm.WindowManagerService.H.REPORT_LOSING_FOCUS;
 import static com.android.server.wm.WindowManagerService.H.SEND_NEW_CONFIGURATION;
 import static com.android.server.wm.WindowManagerService.H.UPDATE_DOCKED_STACK_DIVIDER;
 import static com.android.server.wm.WindowManagerService.H.WINDOW_HIDE_TIMEOUT;
 import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
 import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
 import static com.android.server.wm.WindowManagerService.SEAMLESS_ROTATION_TIMEOUT_DURATION;
+import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
+import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_REMOVING_FOCUS;
+import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_ASSIGN_LAYERS;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
 import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE;
 import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
@@ -371,6 +377,36 @@
     private final SurfaceSession mSession = new SurfaceSession();
 
     /**
+     * Window that is currently interacting with the user. This window is responsible for receiving
+     * key events and pointer events from the user.
+     */
+    WindowState mCurrentFocus = null;
+
+    /**
+     * The last focused window that we've notified the client that the focus is changed.
+     */
+    WindowState mLastFocus = null;
+
+    /**
+     * Windows that have lost input focus and are waiting for the new focus window to be displayed
+     * before they are told about this.
+     */
+    ArrayList<WindowState> mLosingFocus = new ArrayList<>();
+
+    /**
+     * The foreground app of this display. Windows below this app cannot be the focused window. If
+     * the user taps on the area outside of the task of the focused app, we will notify AM about the
+     * new task the user wants to interact with.
+     */
+    AppWindowToken mFocusedApp = null;
+
+    /** Windows added since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */
+    final ArrayList<WindowState> mWinAddedSinceNullFocus = new ArrayList<>();
+
+    /** Windows removed since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */
+    final ArrayList<WindowState> mWinRemovedSinceNullFocus = new ArrayList<>();
+
+    /**
      * We organize all top-level Surfaces in to the following layers.
      * mOverlayLayer contains a few Surfaces which are always on top of others
      * and omitted from Screen-Magnification, for example the strict mode flash or
@@ -466,7 +502,7 @@
     };
 
     private final ToBooleanFunction<WindowState> mFindFocusedWindow = w -> {
-        final AppWindowToken focusedApp = mService.mFocusedApp;
+        final AppWindowToken focusedApp = mFocusedApp;
         if (DEBUG_FOCUS) Slog.v(TAG_WM, "Looking for focus: " + w
                 + ", flags=" + w.mAttrs.flags + ", canReceive=" + w.canReceiveKeys());
 
@@ -625,8 +661,6 @@
         final boolean obscuredChanged = w.mObscured !=
                 mTmpApplySurfaceChangesTransactionState.obscured;
         final RootWindowContainer root = mService.mRoot;
-        // Only used if default window
-        final boolean someoneLosingFocus = !mService.mLosingFocus.isEmpty();
 
         // Update effect.
         w.mObscured = mTmpApplySurfaceChangesTransactionState.obscured;
@@ -717,9 +751,8 @@
             }
         }
 
-        if (isDefaultDisplay && someoneLosingFocus && w == mService.mCurrentFocus
-                && w.isDisplayedLw()) {
-            mTmpApplySurfaceChangesTransactionState.focusDisplayed = true;
+        if (!mLosingFocus.isEmpty() && w.isFocused() && w.isDisplayedLw()) {
+            mService.mH.obtainMessage(REPORT_LOSING_FOCUS, this).sendToTarget();
         }
 
         w.updateResizingWindowIfNeeded();
@@ -2070,22 +2103,22 @@
         return null;
     }
 
-    void setTouchExcludeRegion(Task focusedTask) {
-        // The provided task is the task on this display with focus, so if WindowManagerService's
-        // focused app is not on this display, focusedTask will be null.
+    void updateTouchExcludeRegion() {
+        final Task focusedTask = (mFocusedApp != null ? mFocusedApp.getTask() : null);
         if (focusedTask == null) {
             mTouchExcludeRegion.setEmpty();
         } else {
             mTouchExcludeRegion.set(mBaseDisplayRect);
             final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
             mTmpRect2.setEmpty();
-            for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+            for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0;
+                    --stackNdx) {
                 final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
                 stack.setTouchExcludeRegion(focusedTask, delta, mTouchExcludeRegion,
                         mDisplayFrames.mContent, mTmpRect2);
             }
             // If we removed the focused task above, add it back and only leave its
-            // outside touch area in the exclusion. TapDectector is not interested in
+            // outside touch area in the exclusion. TapDetector is not interested in
             // any touch inside the focused task itself.
             if (!mTmpRect2.isEmpty()) {
                 mTouchExcludeRegion.op(mTmpRect2, Region.Op.UNION);
@@ -2096,12 +2129,7 @@
             // events to be intercepted and used to change focus. This would likely cause a
             // disappearance of the input method.
             mInputMethodWindow.getTouchableRegion(mTmpRegion);
-            if (mInputMethodWindow.getDisplayId() == mDisplayId) {
-                mTouchExcludeRegion.op(mTmpRegion, Op.UNION);
-            } else {
-                // IME is on a different display, so we need to update its tap detector.
-                setTouchExcludeRegion(null /* focusedTask */);
-            }
+            mTouchExcludeRegion.op(mTmpRegion, Op.UNION);
         }
         for (int i = mTapExcludedWindows.size() - 1; i >= 0; i--) {
             final WindowState win = mTapExcludedWindows.get(i);
@@ -2372,6 +2400,9 @@
         }
         mDisplayFrames.writeToProto(proto, DISPLAY_FRAMES);
         proto.write(SURFACE_SIZE, mSurfaceSize);
+        if (mFocusedApp != null) {
+            mFocusedApp.writeNameToProto(proto, FOCUSED_APP);
+        }
         proto.end(token);
     }
 
@@ -2412,6 +2443,27 @@
         pw.print(prefix);
         pw.print("mDeferredRotationPauseCount="); pw.println(mDeferredRotationPauseCount);
 
+        pw.print("  mCurrentFocus="); pw.println(mCurrentFocus);
+        if (mLastFocus != mCurrentFocus) {
+            pw.print("  mLastFocus="); pw.println(mLastFocus);
+        }
+        if (mLosingFocus.size() > 0) {
+            pw.println();
+            pw.println("  Windows losing focus:");
+            for (int i = mLosingFocus.size() - 1; i >= 0; i--) {
+                final WindowState w = mLosingFocus.get(i);
+                pw.print("  Losing #"); pw.print(i); pw.print(' ');
+                pw.print(w);
+                if (dumpAll) {
+                    pw.println(":");
+                    w.dump(pw, "    ", true);
+                } else {
+                    pw.println();
+                }
+            }
+        }
+        pw.print("  mFocusedApp="); pw.println(mFocusedApp);
+
         pw.println();
         pw.println(prefix + "Application tokens in top down Z order:");
         for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
@@ -2543,6 +2595,132 @@
         return mTmpWindow;
     }
 
+
+    /**
+     * Update the focused window and make some adjustments if the focus has changed.
+     *
+     * @param mode Indicates the situation we are in. Possible modes are:
+     *             {@link WindowManagerService#UPDATE_FOCUS_NORMAL},
+     *             {@link WindowManagerService#UPDATE_FOCUS_PLACING_SURFACES},
+     *             {@link WindowManagerService#UPDATE_FOCUS_WILL_PLACE_SURFACES},
+     *             {@link WindowManagerService#UPDATE_FOCUS_REMOVING_FOCUS}
+     * @param updateInputWindows Whether to sync the window information to the input module.
+     * @return {@code true} if the focused window has changed.
+     */
+    boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows, boolean focusFound) {
+        final WindowState newFocus = findFocusedWindow();
+        if (mCurrentFocus == newFocus) {
+            return false;
+        }
+        mService.mH.obtainMessage(REPORT_FOCUS_CHANGE, this).sendToTarget();
+        boolean imWindowChanged = false;
+        // TODO (b/111080190): Multi-Session IME
+        if (!focusFound) {
+            final WindowState imWindow = mInputMethodWindow;
+            if (imWindow != null) {
+                final WindowState prevTarget = mService.mInputMethodTarget;
+
+                final WindowState newTarget = computeImeTarget(true /* updateImeTarget*/);
+                imWindowChanged = prevTarget != newTarget;
+
+                if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
+                        && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) {
+                    assignWindowLayers(false /* setLayoutNeeded */);
+                }
+            }
+        }
+
+        if (imWindowChanged) {
+            mService.mWindowsChanged = true;
+            setLayoutNeeded();
+        }
+
+        if (DEBUG_FOCUS_LIGHT || mService.localLOGV) Slog.v(TAG_WM, "Changing focus from "
+                + mCurrentFocus + " to " + newFocus + " displayId=" + getDisplayId()
+                + " Callers=" + Debug.getCallers(4));
+        final WindowState oldFocus = mCurrentFocus;
+        mCurrentFocus = newFocus;
+        mLosingFocus.remove(newFocus);
+
+        if (newFocus != null) {
+            mWinAddedSinceNullFocus.clear();
+            mWinRemovedSinceNullFocus.clear();
+
+            if (newFocus.canReceiveKeys()) {
+                // Displaying a window implicitly causes dispatching to be unpaused.
+                // This is to protect against bugs if someone pauses dispatching but
+                // forgets to resume.
+                newFocus.mToken.paused = false;
+            }
+        }
+
+        // System UI is only shown on the default display.
+        int focusChanged = isDefaultDisplay
+                ? mService.mPolicy.focusChangedLw(oldFocus, newFocus) : 0;
+
+        if (imWindowChanged && oldFocus != mInputMethodWindow) {
+            // Focus of the input method window changed. Perform layout if needed.
+            if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
+                performLayout(true /*initial*/,  updateInputWindows);
+                focusChanged &= ~FINISH_LAYOUT_REDO_LAYOUT;
+            } else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) {
+                // Client will do the layout, but we need to assign layers
+                // for handleNewWindowLocked() below.
+                assignWindowLayers(false /* setLayoutNeeded */);
+            }
+        }
+
+        if ((focusChanged & FINISH_LAYOUT_REDO_LAYOUT) != 0) {
+            // The change in focus caused us to need to do a layout.  Okay.
+            setLayoutNeeded();
+            if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
+                performLayout(true /*initial*/, updateInputWindows);
+            } else if (mode == UPDATE_FOCUS_REMOVING_FOCUS) {
+                mService.mRoot.performSurfacePlacement(false);
+            }
+        }
+
+        if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) {
+            // If we defer assigning layers, then the caller is responsible for doing this part.
+            getInputMonitor().setInputFocusLw(newFocus, updateInputWindows);
+        }
+
+        adjustForImeIfNeeded();
+
+        // We may need to schedule some toast windows to be removed. The toasts for an app that
+        // does not have input focus are removed within a timeout to prevent apps to redress
+        // other apps' UI.
+        scheduleToastWindowsTimeoutIfNeededLocked(oldFocus, newFocus);
+
+        if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
+            pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;
+        }
+        return true;
+    }
+
+    /**
+     * Set the new focused app to this display.
+     *
+     * @param newFocus the new focused AppWindowToken.
+     * @return true if the focused app is changed.
+     */
+    boolean setFocusedApp(AppWindowToken newFocus) {
+        if (newFocus != null) {
+            final DisplayContent appDisplay = newFocus.getDisplayContent();
+            if (appDisplay != this) {
+                throw new IllegalStateException(newFocus + " is not on " + getName()
+                        + " but " + ((appDisplay != null) ? appDisplay.getName() : "none"));
+            }
+        }
+        if (mFocusedApp == newFocus) {
+            return false;
+        }
+        mFocusedApp = newFocus;
+        getInputMonitor().setFocusedAppLw(newFocus);
+        updateTouchExcludeRegion();
+        return true;
+    }
+
     /** Updates the layer assignment of windows on this display. */
     void assignWindowLayers(boolean setLayoutNeeded) {
         Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "assignWindowLayers");
@@ -2906,8 +3084,8 @@
 
         if (DEBUG_INPUT_METHOD) {
             Slog.i(TAG_WM, "Desired input method target: " + imFocus);
-            Slog.i(TAG_WM, "Current focus: " + mService.mCurrentFocus);
-            Slog.i(TAG_WM, "Last focus: " + mService.mLastFocus);
+            Slog.i(TAG_WM, "Current focus: " + mCurrentFocus + " displayId=" + mDisplayId);
+            Slog.i(TAG_WM, "Last focus: " + mLastFocus + " displayId=" + mDisplayId);
         }
 
         if (DEBUG_INPUT_METHOD) {
@@ -2975,7 +3153,7 @@
     }
 
     // TODO: Super crazy long method that should be broken down...
-    boolean applySurfaceChangesTransaction(boolean recoveringMemory) {
+    void applySurfaceChangesTransaction(boolean recoveringMemory) {
 
         final int dw = mDisplayInfo.logicalWidth;
         final int dh = mDisplayInfo.logicalHeight;
@@ -3059,8 +3237,6 @@
             // can now be shown.
             atoken.updateAllDrawn();
         }
-
-        return mTmpApplySurfaceChangesTransactionState.focusDisplayed;
     }
 
     private void updateBounds() {
@@ -3314,7 +3490,6 @@
         boolean displayHasContent;
         boolean obscured;
         boolean syswin;
-        boolean focusDisplayed;
         float preferredRefreshRate;
         int preferredModeId;
 
@@ -3322,7 +3497,6 @@
             displayHasContent = false;
             obscured = false;
             syswin = false;
-            focusDisplayed = false;
             preferredRefreshRate = 0;
             preferredModeId = 0;
         }
diff --git a/services/core/java/com/android/server/wm/DisplayWindowController.java b/services/core/java/com/android/server/wm/DisplayWindowController.java
index 76b6dbe..ab87759 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowController.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowController.java
@@ -17,11 +17,14 @@
 package com.android.server.wm;
 
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 
 import android.content.res.Configuration;
 import android.os.Binder;
+import android.os.IBinder;
 import android.util.Slog;
 import android.view.Display;
 
@@ -124,6 +127,42 @@
         }
     }
 
+    /**
+     * Sets a focused app on this display.
+     *
+     * @param token Specifies which app should be focused.
+     * @param moveFocusNow Specifies if we should update the focused window immediately.
+     */
+    public void setFocusedApp(IBinder token, boolean moveFocusNow) {
+        synchronized (mWindowMap) {
+            if (mContainer == null) {
+                if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "setFocusedApp: could not find displayId="
+                        + mDisplayId);
+                return;
+            }
+            final AppWindowToken newFocus;
+            if (token == null) {
+                if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Clearing focused app, displayId="
+                        + mDisplayId);
+                newFocus = null;
+            } else {
+                newFocus = mRoot.getAppWindowToken(token);
+                if (newFocus == null) {
+                    Slog.w(TAG_WM, "Attempted to set focus to non-existing app token: " + token
+                            + ", displayId=" + mDisplayId);
+                }
+                if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Set focused app to: " + newFocus
+                        + " moveFocusNow=" + moveFocusNow + " displayId=" + mDisplayId);
+            }
+
+            final boolean changed = mContainer.setFocusedApp(newFocus);
+            if (moveFocusNow && changed) {
+                mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
+                        true /*updateInputWindows*/);
+            }
+        }
+    }
+
     @Override
     public String toString() {
         return "{DisplayWindowController displayId=" + mDisplayId + "}";
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index ef3a770..15f6938 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -64,7 +64,6 @@
     // Array of window handles to provide to the input dispatcher.
     private InputWindowHandle[] mInputWindowHandles;
     private int mInputWindowHandleCount;
-    private InputWindowHandle mFocusedInputWindowHandle;
 
     private boolean mDisableWallpaperTouchEvents;
     private final Rect mTmpRect = new Rect();
@@ -229,16 +228,12 @@
                     + child + ", " + inputWindowHandle);
         }
         addInputWindowHandle(inputWindowHandle);
-        if (hasFocus) {
-            mFocusedInputWindowHandle = inputWindowHandle;
-        }
     }
 
     private void clearInputWindowHandlesLw() {
         while (mInputWindowHandleCount != 0) {
             mInputWindowHandles[--mInputWindowHandleCount] = null;
         }
-        mFocusedInputWindowHandle = null;
     }
 
     void setUpdateInputWindowsNeededLw() {
@@ -325,13 +320,13 @@
     public void setFocusedAppLw(AppWindowToken newApp) {
         // Focused app has changed.
         if (newApp == null) {
-            mService.mInputManager.setFocusedApplication(null);
+            mService.mInputManager.setFocusedApplication(mDisplayId, null);
         } else {
             final InputApplicationHandle handle = newApp.mInputApplicationHandle;
             handle.name = newApp.toString();
             handle.dispatchingTimeoutNanos = newApp.mInputDispatchingTimeoutNanos;
 
-            mService.mInputManager.setFocusedApplication(handle);
+            mService.mInputManager.setFocusedApplication(mDisplayId, handle);
         }
     }
 
@@ -370,8 +365,7 @@
     void onRemoved() {
         // If DisplayContent removed, we need find a way to remove window handles of this display
         // from InputDispatcher, so pass an empty InputWindowHandles to remove them.
-        mService.mInputManager.setInputWindows(mInputWindowHandles, mFocusedInputWindowHandle,
-                mDisplayId);
+        mService.mInputManager.setInputWindows(mInputWindowHandles, mDisplayId);
     }
 
     private final class UpdateInputForAllWindowsConsumer implements Consumer<WindowState> {
@@ -414,8 +408,7 @@
             }
 
             // Send windows to native code.
-            mService.mInputManager.setInputWindows(mInputWindowHandles, mFocusedInputWindowHandle,
-                    mDisplayId);
+            mService.mInputManager.setInputWindows(mInputWindowHandles, mDisplayId);
 
             clearInputWindowHandlesLw();
 
@@ -435,7 +428,6 @@
             final int flags = w.mAttrs.flags;
             final int privateFlags = w.mAttrs.privateFlags;
             final int type = w.mAttrs.type;
-            // TODO(b/111361570): multi-display focus, one focus window per display.
             final boolean hasFocus = w.isFocused();
             final boolean isVisible = w.isVisibleLw();
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index a9571be..3fef87d 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -17,19 +17,20 @@
 package com.android.server.wm;
 
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE;
 import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
 
-import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.RootWindowContainerProto.DISPLAYS;
 import static com.android.server.wm.RootWindowContainerProto.WINDOWS;
 import static com.android.server.wm.RootWindowContainerProto.WINDOW_CONTAINER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_KEEP_SCREEN_ON;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
@@ -41,9 +42,9 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_KEEP_SCREEN_ON;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.H.REPORT_LOSING_FOCUS;
 import static com.android.server.wm.WindowManagerService.H.SEND_NEW_CONFIGURATION;
 import static com.android.server.wm.WindowManagerService.H.WINDOW_FREEZE_TIMEOUT;
+import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
 import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_NONE;
@@ -124,6 +125,10 @@
 
     private String mCloseSystemDialogsReason;
 
+    // The ID of the display which is responsible for receiving display-unspecified key and pointer
+    // events.
+    private int mTopFocusedDisplayId = INVALID_DISPLAY;
+
     // Only a seperate transaction until we seperate the apply surface changes
     // transaction from the global transaction.
     private final SurfaceControl.Transaction mDisplayTransaction = new SurfaceControl.Transaction();
@@ -153,23 +158,40 @@
         mWallpaperController = new WallpaperController(mService);
     }
 
-    WindowState computeFocusedWindow() {
-        // While the keyguard is showing, we must focus anything besides the main display.
-        // Otherwise we risk input not going to the keyguard when the user expects it to.
-        final boolean forceDefaultDisplay = mService.isKeyguardShowingAndNotOccluded();
-
+    boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
+        boolean changed = false;
+        int topFocusedDisplayId = INVALID_DISPLAY;
         for (int i = mChildren.size() - 1; i >= 0; i--) {
             final DisplayContent dc = mChildren.get(i);
-            final WindowState win = dc.findFocusedWindow();
-            if (win != null) {
-                if (forceDefaultDisplay && !dc.isDefaultDisplay) {
-                    EventLog.writeEvent(0x534e4554, "71786287", win.mOwnerUid, "");
-                    continue;
-                }
-                return win;
+            changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows,
+                    topFocusedDisplayId != INVALID_DISPLAY /* focusFound */);
+            if (topFocusedDisplayId == INVALID_DISPLAY && dc.mCurrentFocus != null) {
+                topFocusedDisplayId = dc.getDisplayId();
             }
         }
-        return null;
+        if (topFocusedDisplayId == INVALID_DISPLAY) {
+            topFocusedDisplayId = DEFAULT_DISPLAY;
+        }
+        if (mTopFocusedDisplayId != topFocusedDisplayId) {
+            mTopFocusedDisplayId = topFocusedDisplayId;
+            mService.mInputManager.setFocusedDisplay(topFocusedDisplayId);
+            if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "New topFocusedDisplayId="
+                    + topFocusedDisplayId);
+        }
+        final WindowState topFocusedWindow = getTopFocusedDisplayContent().mCurrentFocus;
+        mService.mInputManager.setFocusedWindow(
+                topFocusedWindow != null ? topFocusedWindow.mInputWindowHandle : null);
+        return changed;
+    }
+
+    DisplayContent getTopFocusedDisplayContent() {
+        return getDisplayContent(mTopFocusedDisplayId == INVALID_DISPLAY
+                ? DEFAULT_DISPLAY : mTopFocusedDisplayId);
+    }
+
+    @Override
+    void onChildPositionChanged() {
+        mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /* updateInputWindows */);
     }
 
     DisplayContent getDisplayContent(int displayId) {
@@ -636,7 +658,6 @@
             if (mService.updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES,
                     false /*updateInputWindows*/)) {
                 updateInputWindowsNeeded = true;
-                defaultDisplay.pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;
             }
         }
 
@@ -646,7 +667,7 @@
                     defaultDisplay.pendingLayoutChanges);
         }
 
-        final ArraySet<DisplayContent> touchExcludeRegionUpdateDisplays = handleResizingWindows();
+        handleResizingWindows();
 
         if (DEBUG_ORIENTATION && mService.mDisplayFrozen) Slog.v(TAG,
                 "With display frozen, orientationChangeComplete=" + mOrientationChangeComplete);
@@ -765,17 +786,7 @@
                 dc.getInputMonitor().updateInputWindowsLw(false /*force*/);
             });
         }
-        mService.setFocusTaskRegionLocked(null);
-        if (touchExcludeRegionUpdateDisplays != null) {
-            final DisplayContent focusedDc = mService.mFocusedApp != null
-                    ? mService.mFocusedApp.getDisplayContent() : null;
-            for (DisplayContent dc : touchExcludeRegionUpdateDisplays) {
-                // The focused DisplayContent was recalcuated in setFocusTaskRegionLocked
-                if (focusedDc != dc) {
-                    dc.setTouchExcludeRegion(null /* focusedTask */);
-                }
-            }
-        }
+        forAllDisplays(DisplayContent::updateTouchExcludeRegion);
 
         // Check to see if we are now in a state where the screen should
         // be enabled, because the window obscured flags have changed.
@@ -808,16 +819,10 @@
                     mService.getDefaultDisplayRotation());
         }
 
-        boolean focusDisplayed = false;
-
         final int count = mChildren.size();
         for (int j = 0; j < count; ++j) {
             final DisplayContent dc = mChildren.get(j);
-            focusDisplayed |= dc.applySurfaceChangesTransaction(recoveringMemory);
-        }
-
-        if (focusDisplayed) {
-            mService.mH.sendEmptyMessage(REPORT_LOSING_FOCUS);
+            dc.applySurfaceChangesTransaction(recoveringMemory);
         }
 
         // Give the display manager a chance to adjust properties like display rotation if it needs
@@ -828,12 +833,8 @@
 
     /**
      * Handles resizing windows during surface placement.
-     *
-     * @return A set of any DisplayContent whose touch exclude region needs to be recalculated due
-     *         to a tap-exclude window resizing, or null if no such DisplayContents were found.
      */
-    private ArraySet<DisplayContent> handleResizingWindows() {
-        ArraySet<DisplayContent> touchExcludeRegionUpdateSet = null;
+    private void handleResizingWindows() {
         for (int i = mService.mResizingWindows.size() - 1; i >= 0; i--) {
             WindowState win = mService.mResizingWindows.get(i);
             if (win.mAppFreezing) {
@@ -842,15 +843,7 @@
             }
             win.reportResized();
             mService.mResizingWindows.remove(i);
-            if (WindowManagerService.excludeWindowTypeFromTapOutTask(win.mAttrs.type)) {
-                final DisplayContent dc = win.getDisplayContent();
-                if (touchExcludeRegionUpdateSet == null) {
-                    touchExcludeRegionUpdateSet = new ArraySet<>();
-                }
-                touchExcludeRegionUpdateSet.add(dc);
-            }
         }
-        return touchExcludeRegionUpdateSet;
     }
 
     /**
@@ -1004,6 +997,10 @@
         }
     }
 
+    void dumpTopFocusedDisplayId(PrintWriter pw) {
+        pw.print("  mTopFocusedDisplayId="); pw.println(mTopFocusedDisplayId);
+    }
+
     void dumpLayoutNeededDisplayIds(PrintWriter pw) {
         if (!isLayoutNeeded()) {
             return;
diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java
index 33416f6..b7e37b2 100644
--- a/services/core/java/com/android/server/wm/TaskPositioningController.java
+++ b/services/core/java/com/android/server/wm/TaskPositioningController.java
@@ -131,9 +131,9 @@
         // of the app, it may not have focus since there might be other windows
         // on top (eg. a dialog window).
         WindowState transferFocusFromWin = win;
-        if (mService.mCurrentFocus != null && mService.mCurrentFocus != win
-                && mService.mCurrentFocus.mAppToken == win.mAppToken) {
-            transferFocusFromWin = mService.mCurrentFocus;
+        if (displayContent.mCurrentFocus != null && displayContent.mCurrentFocus != win
+                && displayContent.mCurrentFocus.mAppToken == win.mAppToken) {
+            transferFocusFromWin = displayContent.mCurrentFocus;
         }
         if (!mInputManager.transferTouchFocus(
                 transferFocusFromWin.mInputChannel, mTaskPositioner.mServerChannel)) {
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
index f1e1592..52f8510 100644
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
@@ -19,12 +19,12 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.input.InputManager;
+import android.os.Handler;
 import android.view.MotionEvent;
 import android.view.WindowManagerPolicyConstants.PointerEventListener;
 
 import com.android.server.wm.WindowManagerService.H;
 
-import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.PointerIcon.TYPE_NOT_SPECIFIED;
 import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
 import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
@@ -36,6 +36,8 @@
     final private Region mTouchExcludeRegion = new Region();
     private final WindowManagerService mService;
     private final DisplayContent mDisplayContent;
+    private final Handler mHandler;
+    private final Runnable mMoveDisplayToTop;
     private final Rect mTmpRect = new Rect();
     private int mPointerIconType = TYPE_NOT_SPECIFIED;
 
@@ -43,6 +45,13 @@
             DisplayContent displayContent) {
         mService = service;
         mDisplayContent = displayContent;
+        mHandler = new Handler(mService.mH.getLooper());
+        mMoveDisplayToTop = () -> {
+            synchronized (mService.mWindowMap) {
+                mDisplayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP,
+                        mDisplayContent, true /* includingParents */);
+            }
+        };
     }
 
     @Override
@@ -61,6 +70,7 @@
                         mService.mTaskPositioningController.handleTapOutsideTask(
                                 mDisplayContent, x, y);
                     }
+                    mHandler.post(mMoveDisplayToTop);
                 }
             }
             break;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 4883f97..46999a2 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -273,6 +273,7 @@
             parent.mTreeWeight += child.mTreeWeight;
             parent = parent.getParent();
         }
+        onChildPositionChanged();
     }
 
     /**
@@ -298,6 +299,7 @@
             parent.mTreeWeight -= child.mTreeWeight;
             parent = parent.getParent();
         }
+        onChildPositionChanged();
     }
 
     /**
@@ -455,9 +457,15 @@
                 mChildren.remove(child);
                 mChildren.add(position, child);
         }
+        onChildPositionChanged();
     }
 
     /**
+     * Notify that a child's position has changed. Possible changes are adding or removing a child.
+     */
+    void onChildPositionChanged() { }
+
+    /**
      * Update override configuration and recalculate full config.
      * @see #mOverrideConfiguration
      * @see #mFullConfiguration
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 942e47b..fc33a4f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -499,12 +499,6 @@
     final ArrayList<WindowState> mDestroyPreservedSurface = new ArrayList<>();
 
     /**
-     * Windows that have lost input focus and are waiting for the new
-     * focus window to be displayed before they are told about this.
-     */
-    ArrayList<WindowState> mLosingFocus = new ArrayList<>();
-
-    /**
      * This is set when we have run out of memory, and will either be an empty
      * list or contain windows that need to be force removed.
      */
@@ -639,14 +633,6 @@
      */
     final Handler mAnimationHandler = new Handler(AnimationThread.getHandler().getLooper());
 
-    WindowState mCurrentFocus = null;
-    WindowState mLastFocus = null;
-
-    /** Windows added since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */
-    private final ArrayList<WindowState> mWinAddedSinceNullFocus = new ArrayList<>();
-    /** Windows removed since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */
-    private final ArrayList<WindowState> mWinRemovedSinceNullFocus = new ArrayList<>();
-
     /** This just indicates the window the input method is on top of, not
      * necessarily the window its input is going to. */
     WindowState mInputMethodTarget = null;
@@ -721,9 +707,6 @@
         }
     }
 
-    // TODO: Move to RootWindowContainer
-    AppWindowToken mFocusedApp = null;
-
     PowerManager mPowerManager;
     PowerManagerInternal mPowerManagerInternal;
 
@@ -1371,8 +1354,8 @@
                 // the screen after the activity goes away.
                 if (addToastWindowRequiresToken
                         || (attrs.flags & LayoutParams.FLAG_NOT_FOCUSABLE) == 0
-                        || mCurrentFocus == null
-                        || mCurrentFocus.mOwnerUid != callingUid) {
+                        || displayContent.mCurrentFocus == null
+                        || displayContent.mCurrentFocus.mOwnerUid != callingUid) {
                     mH.sendMessageDelayed(
                             mH.obtainMessage(H.WINDOW_HIDE_TIMEOUT, win),
                             win.mAttrs.hideTimeoutMilliseconds);
@@ -1382,8 +1365,8 @@
             // From now on, no exceptions or errors allowed!
 
             res = WindowManagerGlobal.ADD_OKAY;
-            if (mCurrentFocus == null) {
-                mWinAddedSinceNullFocus.add(win);
+            if (displayContent.mCurrentFocus == null) {
+                displayContent.mWinAddedSinceNullFocus.add(win);
             }
 
             if (excludeWindowTypeFromTapOutTask(type)) {
@@ -1504,7 +1487,7 @@
             win.getParent().assignChildLayers();
 
             if (focusChanged) {
-                displayContent.getInputMonitor().setInputFocusLw(mCurrentFocus,
+                displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus,
                         false /*updateInputWindows*/);
             }
             displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
@@ -1679,8 +1662,9 @@
 
         win.resetAppOpsState();
 
-        if (mCurrentFocus == null) {
-            mWinRemovedSinceNullFocus.add(win);
+        final DisplayContent dc = win.getDisplayContent();
+        if (dc.mCurrentFocus == null) {
+            dc.mWinRemovedSinceNullFocus.add(win);
         }
         mPendingRemove.remove(win);
         mResizingWindows.remove(win);
@@ -1716,7 +1700,6 @@
             atoken.postWindowRemoveStartingWindowCleanup(win);
         }
 
-        final DisplayContent dc = win.getDisplayContent();
         if (win.mAttrs.type == TYPE_WALLPAPER) {
             dc.mWallpaperController.clearLastWallpaperTimeoutTime();
             dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
@@ -1978,9 +1961,9 @@
             boolean imMayMove = (flagChanges & (FLAG_ALT_FOCUSABLE_IM | FLAG_NOT_FOCUSABLE)) != 0
                     || becameVisible;
             final boolean isDefaultDisplay = win.isDefaultDisplay();
-            boolean focusMayChange = isDefaultDisplay && (win.mViewVisibility != viewVisibility
+            boolean focusMayChange = win.mViewVisibility != viewVisibility
                     || ((flagChanges & FLAG_NOT_FOCUSABLE) != 0)
-                    || (!win.mRelayoutCalled));
+                    || (!win.mRelayoutCalled);
 
             boolean wallpaperMayMove = win.mViewVisibility != viewVisibility
                     && (win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0;
@@ -2025,8 +2008,7 @@
                 }
                 result |= RELAYOUT_RES_SURFACE_CHANGED;
                 if (!win.mWillReplaceWindow) {
-                    focusMayChange = tryStartExitingAnimation(win, winAnimator, isDefaultDisplay,
-                            focusMayChange);
+                    focusMayChange = tryStartExitingAnimation(win, winAnimator, focusMayChange);
                 }
             }
 
@@ -2051,7 +2033,7 @@
                     return 0;
                 }
                 if ((result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
-                    focusMayChange = isDefaultDisplay;
+                    focusMayChange = true;
                 }
                 final DisplayContent displayContent = win.getDisplayContent();
                 if (win.mAttrs.type == TYPE_INPUT_METHOD
@@ -2199,7 +2181,7 @@
     }
 
     private boolean tryStartExitingAnimation(WindowState win, WindowStateAnimator winAnimator,
-            boolean isDefaultDisplay, boolean focusMayChange) {
+            boolean focusMayChange) {
         // Try starting an animation; if there isn't one, we
         // can destroy the surface right away.
         int transit = WindowManagerPolicy.TRANSIT_EXIT;
@@ -2207,7 +2189,7 @@
             transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
         }
         if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) {
-            focusMayChange = isDefaultDisplay;
+            focusMayChange = true;
             win.mAnimatingExit = true;
         } else if (win.isAnimating()) {
             // Currently in a hide animation... turn this into
@@ -2504,57 +2486,6 @@
         }
     }
 
-    void setFocusTaskRegionLocked(AppWindowToken previousFocus) {
-        final Task focusedTask = mFocusedApp != null ? mFocusedApp.getTask() : null;
-        final Task previousTask = previousFocus != null ? previousFocus.getTask() : null;
-        final DisplayContent focusedDisplayContent =
-                focusedTask != null ? focusedTask.getDisplayContent() : null;
-        final DisplayContent previousDisplayContent =
-                previousTask != null ? previousTask.getDisplayContent() : null;
-        if (previousDisplayContent != null && previousDisplayContent != focusedDisplayContent) {
-            previousDisplayContent.setTouchExcludeRegion(null);
-        }
-        if (focusedDisplayContent != null) {
-            focusedDisplayContent.setTouchExcludeRegion(focusedTask);
-        }
-    }
-
-    @Override
-    public void setFocusedApp(IBinder token, boolean moveFocusNow) {
-        if (!checkCallingPermission(MANAGE_APP_TOKENS, "setFocusedApp()")) {
-            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
-        }
-
-        synchronized(mWindowMap) {
-            final AppWindowToken newFocus;
-            if (token == null) {
-                if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Clearing focused app, was " + mFocusedApp);
-                newFocus = null;
-            } else {
-                newFocus = mRoot.getAppWindowToken(token);
-                if (newFocus == null) {
-                    Slog.w(TAG_WM, "Attempted to set focus to non-existing app token: " + token);
-                }
-                if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "Set focused app to: " + newFocus
-                        + " old focus=" + mFocusedApp + " moveFocusNow=" + moveFocusNow);
-            }
-
-            final boolean changed = mFocusedApp != newFocus;
-            if (changed) {
-                AppWindowToken prev = mFocusedApp;
-                mFocusedApp = newFocus;
-                mFocusedApp.getDisplayContent().getInputMonitor().setFocusedAppLw(newFocus);
-                setFocusTaskRegionLocked(prev);
-            }
-
-            if (moveFocusNow && changed) {
-                final long origId = Binder.clearCallingIdentity();
-                updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
-                Binder.restoreCallingIdentity(origId);
-            }
-        }
-    }
-
     @Override
     public void prepareAppTransition(@TransitionType int transit, boolean alwaysKeepCurrent) {
         prepareAppTransition(transit, alwaysKeepCurrent, 0 /* flags */, false /* forceOverride */);
@@ -4395,7 +4326,8 @@
     }
 
     private WindowState getFocusedWindowLocked() {
-        return mCurrentFocus;
+        // Return the focused window in the focused display.
+        return mRoot.getTopFocusedDisplayContent().mCurrentFocus;
     }
 
     TaskStack getImeFocusStackLocked() {
@@ -4403,8 +4335,11 @@
         // Also don't use mInputMethodTarget's stack, because some window with FLAG_NOT_FOCUSABLE
         // and FLAG_ALT_FOCUSABLE_IM flags both set might be set to IME target so they're moved
         // to make room for IME, but the window is not the focused window that's taking input.
-        return (mFocusedApp != null && mFocusedApp.getTask() != null) ?
-                mFocusedApp.getTask().mStack : null;
+        // TODO (b/111080190): Consider the case of multiple IMEs on multi-display.
+        final DisplayContent topFocusedDisplay = mRoot.getTopFocusedDisplayContent();
+        final AppWindowToken focusedApp = topFocusedDisplay.mFocusedApp;
+        return (focusedApp != null && focusedApp.getTask() != null)
+                ? focusedApp.getTask().mStack : null;
     }
 
     public boolean detectSafeMode() {
@@ -4601,6 +4536,7 @@
             }
             switch (msg.what) {
                 case REPORT_FOCUS_CHANGE: {
+                    final DisplayContent displayContent = (DisplayContent) msg.obj;
                     WindowState lastFocus;
                     WindowState newFocus;
 
@@ -4608,24 +4544,22 @@
 
                     synchronized(mWindowMap) {
                         // TODO(multidisplay): Accessibility supported only of default desiplay.
-                        if (mAccessibilityController != null && getDefaultDisplayContentLocked()
-                                .getDisplayId() == DEFAULT_DISPLAY) {
+                        if (mAccessibilityController != null && displayContent.isDefaultDisplay) {
                             accessibilityController = mAccessibilityController;
                         }
 
-                        lastFocus = mLastFocus;
-                        newFocus = mCurrentFocus;
+                        lastFocus = displayContent.mLastFocus;
+                        newFocus = displayContent.mCurrentFocus;
                         if (lastFocus == newFocus) {
                             // Focus is not changing, so nothing to do.
                             return;
                         }
-                        mLastFocus = newFocus;
+                        displayContent.mLastFocus = newFocus;
                         if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Focus moving from " + lastFocus +
-                                " to " + newFocus);
-                        if (newFocus != null && lastFocus != null
-                                && !newFocus.isDisplayedLw()) {
-                            //Slog.i(TAG_WM, "Delaying loss of focus...");
-                            mLosingFocus.add(lastFocus);
+                                " to " + newFocus + " displayId=" + displayContent.getDisplayId());
+                        if (newFocus != null && lastFocus != null && !newFocus.isDisplayedLw()) {
+                            if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Delaying loss of focus...");
+                            displayContent.mLosingFocus.add(lastFocus);
                             lastFocus = null;
                         }
                     }
@@ -4636,8 +4570,6 @@
                         accessibilityController.onWindowFocusChangedNotLocked();
                     }
 
-                    //System.out.println("Changing focus from " + lastFocus
-                    //                   + " to " + newFocus);
                     if (newFocus != null) {
                         if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Gaining focus: " + newFocus);
                         newFocus.reportFocusChangedSerialized(true, mInTouchMode);
@@ -4651,15 +4583,16 @@
                 } break;
 
                 case REPORT_LOSING_FOCUS: {
+                    final DisplayContent displayContent = (DisplayContent) msg.obj;
                     ArrayList<WindowState> losers;
 
                     synchronized(mWindowMap) {
-                        losers = mLosingFocus;
-                        mLosingFocus = new ArrayList<WindowState>();
+                        losers = displayContent.mLosingFocus;
+                        displayContent.mLosingFocus = new ArrayList<>();
                     }
 
                     final int N = losers.size();
-                    for (int i=0; i<N; i++) {
+                    for (int i = 0; i < N; i++) {
                         if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Losing delayed focus: " +
                                 losers.get(i));
                         losers.get(i).reportFocusChangedSerialized(false, mInTouchMode);
@@ -5553,89 +5486,11 @@
         }
     }
 
-    // TODO: Move to DisplayContent
     boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
-        WindowState newFocus = mRoot.computeFocusedWindow();
-        if (mCurrentFocus != newFocus) {
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus");
-            // This check makes sure that we don't already have the focus
-            // change message pending.
-            mH.removeMessages(H.REPORT_FOCUS_CHANGE);
-            mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE);
-            final DisplayContent displayContent = (newFocus != null) ? newFocus.getDisplayContent()
-                    : getDefaultDisplayContentLocked();
-            boolean imWindowChanged = false;
-            if (displayContent.mInputMethodWindow != null) {
-                final WindowState prevTarget = mInputMethodTarget;
-
-                final WindowState newTarget =
-                        displayContent.computeImeTarget(true /* updateImeTarget*/);
-                imWindowChanged = prevTarget != newTarget;
-
-                if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
-                        && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) {
-                    displayContent.assignWindowLayers(false /* setLayoutNeeded */);
-                }
-            }
-
-            if (imWindowChanged) {
-                mWindowsChanged = true;
-                displayContent.setLayoutNeeded();
-                newFocus = mRoot.computeFocusedWindow();
-            }
-
-            if (DEBUG_FOCUS_LIGHT || localLOGV) Slog.v(TAG_WM, "Changing focus from " +
-                    mCurrentFocus + " to " + newFocus + " Callers=" + Debug.getCallers(4));
-            final WindowState oldFocus = mCurrentFocus;
-            mCurrentFocus = newFocus;
-            mLosingFocus.remove(newFocus);
-
-            if (mCurrentFocus != null) {
-                mWinAddedSinceNullFocus.clear();
-                mWinRemovedSinceNullFocus.clear();
-            }
-
-            int focusChanged = mPolicy.focusChangedLw(oldFocus, newFocus);
-
-            if (imWindowChanged && oldFocus != displayContent.mInputMethodWindow) {
-                // Focus of the input method window changed. Perform layout if needed.
-                if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
-                    displayContent.performLayout(true /*initial*/,  updateInputWindows);
-                    focusChanged &= ~FINISH_LAYOUT_REDO_LAYOUT;
-                } else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) {
-                    // Client will do the layout, but we need to assign layers
-                    // for handleNewWindowLocked() below.
-                    displayContent.assignWindowLayers(false /* setLayoutNeeded */);
-                }
-            }
-
-            if ((focusChanged & FINISH_LAYOUT_REDO_LAYOUT) != 0) {
-                // The change in focus caused us to need to do a layout.  Okay.
-                displayContent.setLayoutNeeded();
-                if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
-                    displayContent.performLayout(true /*initial*/, updateInputWindows);
-                } else if (mode == UPDATE_FOCUS_REMOVING_FOCUS) {
-                    mRoot.performSurfacePlacement(false);
-                }
-            }
-
-            if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) {
-                // If we defer assigning layers, then the caller is responsible for
-                // doing this part.
-                displayContent.getInputMonitor().setInputFocusLw(mCurrentFocus, updateInputWindows);
-            }
-
-            displayContent.adjustForImeIfNeeded();
-
-            // We may need to schedule some toast windows to be removed. The toasts for an app that
-            // does not have input focus are removed within a timeout to prevent apps to redress
-            // other apps' UI.
-            displayContent.scheduleToastWindowsTimeoutIfNeededLocked(oldFocus, newFocus);
-
-            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-            return true;
-        }
-        return false;
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus");
+        boolean changed = mRoot.updateFocusedWindowLocked(mode, updateInputWindows);
+        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        return changed;
     }
 
     void startFreezingDisplayLocked(int exitAnim, int enterAnim) {
@@ -6197,11 +6052,12 @@
     void writeToProtoLocked(ProtoOutputStream proto, boolean trim) {
         mPolicy.writeToProto(proto, POLICY);
         mRoot.writeToProto(proto, ROOT_WINDOW_CONTAINER, trim);
-        if (mCurrentFocus != null) {
-            mCurrentFocus.writeIdentifierToProto(proto, FOCUSED_WINDOW);
+        final DisplayContent topFocusedDisplayContent = mRoot.getTopFocusedDisplayContent();
+        if (topFocusedDisplayContent.mCurrentFocus != null) {
+            topFocusedDisplayContent.mCurrentFocus.writeIdentifierToProto(proto, FOCUSED_WINDOW);
         }
-        if (mFocusedApp != null) {
-            mFocusedApp.writeNameToProto(proto, FOCUSED_APP);
+        if (topFocusedDisplayContent.mFocusedApp != null) {
+            topFocusedDisplayContent.mFocusedApp.writeNameToProto(proto, FOCUSED_APP);
         }
         final WindowState imeWindow = mRoot.getCurrentInputMethodWindow();
         if (imeWindow != null) {
@@ -6299,23 +6155,6 @@
                 }
             }
         }
-        if (mLosingFocus.size() > 0) {
-            pw.println();
-            pw.println("  Windows losing focus:");
-            for (int i=mLosingFocus.size()-1; i>=0; i--) {
-                WindowState w = mLosingFocus.get(i);
-                if (windows == null || windows.contains(w)) {
-                    pw.print("  Losing #"); pw.print(i); pw.print(' ');
-                            pw.print(w);
-                    if (dumpAll) {
-                        pw.println(":");
-                        w.dump(pw, "    ", true);
-                    } else {
-                        pw.println();
-                    }
-                }
-            }
-        }
         if (mResizingWindows.size() > 0) {
             pw.println();
             pw.println("  Windows waiting to resize:");
@@ -6344,11 +6183,7 @@
         pw.println();
         pw.print("  mGlobalConfiguration="); pw.println(mRoot.getConfiguration());
         pw.print("  mHasPermanentDpad="); pw.println(mHasPermanentDpad);
-        pw.print("  mCurrentFocus="); pw.println(mCurrentFocus);
-        if (mLastFocus != mCurrentFocus) {
-            pw.print("  mLastFocus="); pw.println(mLastFocus);
-        }
-        pw.print("  mFocusedApp="); pw.println(mFocusedApp);
+        mRoot.dumpTopFocusedDisplayId(pw);
         if (mInputMethodTarget != null) {
             pw.print("  mInputMethodTarget="); pw.println(mInputMethodTarget);
         }
@@ -6479,11 +6314,17 @@
         if (reason != null) {
             pw.println("  Reason: " + reason);
         }
-        if (!mWinAddedSinceNullFocus.isEmpty()) {
-            pw.println("  Windows added since null focus: " + mWinAddedSinceNullFocus);
-        }
-        if (!mWinRemovedSinceNullFocus.isEmpty()) {
-            pw.println("  Windows removed since null focus: " + mWinRemovedSinceNullFocus);
+        for (int i = mRoot.getChildCount() - 1; i >= 0; i--) {
+            final DisplayContent dc = mRoot.getChildAt(i);
+            final int displayId = dc.getDisplayId();
+            if (!dc.mWinAddedSinceNullFocus.isEmpty()) {
+                pw.println("  Windows added in display #" + displayId + " since null focus: "
+                        + dc.mWinAddedSinceNullFocus);
+            }
+            if (!dc.mWinRemovedSinceNullFocus.isEmpty()) {
+                pw.println("  Windows removed in display #" + displayId + " since null focus: "
+                        + dc.mWinRemovedSinceNullFocus);
+            }
         }
         pw.println();
         dumpWindowsNoHeaderLocked(pw, true, null);
@@ -6818,9 +6659,9 @@
     @Override
     public void setDockedStackDividerTouchRegion(Rect touchRegion) {
         synchronized (mWindowMap) {
-            getDefaultDisplayContentLocked().getDockedDividerController()
-                    .setTouchRegion(touchRegion);
-            setFocusTaskRegionLocked(null);
+            final DisplayContent dc = getDefaultDisplayContentLocked();
+            dc.getDockedDividerController().setTouchRegion(touchRegion);
+            dc.updateTouchExcludeRegion();
         }
     }
 
@@ -7373,7 +7214,14 @@
         @Override
         public boolean isUidFocused(int uid) {
             synchronized (mWindowMap) {
-                return mCurrentFocus != null ? uid == mCurrentFocus.getOwningUid() : false;
+                for (int i = mRoot.getChildCount() - 1; i >= 0; i--) {
+                    final DisplayContent displayContent = mRoot.getChildAt(i);
+                    if (displayContent.mCurrentFocus != null
+                            && uid == displayContent.mCurrentFocus.getOwningUid()) {
+                        return true;
+                    }
+                }
+                return false;
             }
         }
 
@@ -7396,8 +7244,9 @@
                 // press home.  Sometimes the IME won't go down.)
                 // Would be nice to fix this more correctly, but it's
                 // way at the end of a release, and this should be good enough.
-                if (mCurrentFocus != null && mCurrentFocus.mSession.mUid == uid
-                        && mCurrentFocus.mSession.mPid == pid) {
+                final WindowState currentFocus = mRoot.getTopFocusedDisplayContent().mCurrentFocus;
+                if (currentFocus != null && currentFocus.mSession.mUid == uid
+                        && currentFocus.mSession.mPid == pid) {
                     return true;
                 }
             }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a4bac31..8276952 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1833,7 +1833,7 @@
         if (startingWindow && DEBUG_STARTING_WINDOW) Slog.d(TAG_WM,
                 "Starting window removed " + this);
 
-        if (localLOGV || DEBUG_FOCUS || DEBUG_FOCUS_LIGHT && this == mService.mCurrentFocus)
+        if (localLOGV || DEBUG_FOCUS || DEBUG_FOCUS_LIGHT && isFocused())
             Slog.v(TAG_WM, "Remove " + this + " client="
                         + Integer.toHexString(System.identityHashCode(mClient.asBinder()))
                         + ", surfaceController=" + mWinAnimator.mSurfaceController + " Callers="
@@ -1945,7 +1945,7 @@
             if (wasVisible && mService.updateOrientationFromAppTokensLocked(displayId)) {
                 mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
             }
-            mService.updateFocusedWindowLocked(mService.mCurrentFocus == this
+            mService.updateFocusedWindowLocked(isFocused()
                             ? UPDATE_FOCUS_REMOVING_FOCUS
                             : UPDATE_FOCUS_NORMAL,
                     true /*updateInputWindows*/);
@@ -2180,7 +2180,7 @@
             mPolicyVisibility = mPolicyVisibilityAfterAnim;
             if (!mPolicyVisibility) {
                 mWinAnimator.hide("checkPolicyVisibilityChange");
-                if (mService.mCurrentFocus == this) {
+                if (isFocused()) {
                     if (DEBUG_FOCUS_LIGHT) Slog.i(TAG,
                             "setAnimationLocked: setting mFocusMayChange true");
                     mService.mFocusMayChange = true;
@@ -2482,6 +2482,7 @@
             }
         }
         mPolicyVisibilityAfterAnim = false;
+        final boolean isFocused = isFocused();
         if (!doAnimation) {
             if (DEBUG_VISIBILITY) Slog.v(TAG, "Policy visibility false: " + this);
             mPolicyVisibility = false;
@@ -2489,7 +2490,7 @@
             // for it to be displayed before enabling the display, that
             // we allow the display to be enabled now.
             mService.enableScreenIfNeededLocked();
-            if (mService.mCurrentFocus == this) {
+            if (isFocused) {
                 if (DEBUG_FOCUS_LIGHT) Slog.i(TAG,
                         "WindowState.hideLw: setting mFocusMayChange true");
                 mService.mFocusMayChange = true;
@@ -2498,7 +2499,7 @@
         if (requestAnim) {
             mService.scheduleAnimationLocked();
         }
-        if (mService.mCurrentFocus == this) {
+        if (isFocused) {
             mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /* updateImWindows */);
         }
         return true;
@@ -2994,10 +2995,8 @@
         }
     }
 
-    public boolean isFocused() {
-        synchronized(mService.mWindowMap) {
-            return mService.mCurrentFocus == this;
-        }
+    boolean isFocused() {
+        return getDisplayContent().mCurrentFocus == this;
     }
 
     @Override
@@ -4435,7 +4434,12 @@
         @Override
         public boolean isFocused() {
             final WindowState outer = mOuter.get();
-            return outer != null && outer.isFocused();
+            if (outer != null) {
+                synchronized (outer.mService.mWindowMap) {
+                    return outer.isFocused();
+                }
+            }
+            return false;
         }
     }
 
@@ -4663,10 +4667,7 @@
 
         mTapExcludeRegionHolder.updateRegion(regionId, left, top, width, height);
         // Trigger touch exclude region update on current display.
-        final boolean isAppFocusedOnDisplay = mService.mFocusedApp != null
-                && mService.mFocusedApp.getDisplayContent() == currentDisplay;
-        currentDisplay.setTouchExcludeRegion(isAppFocusedOnDisplay ? mService.mFocusedApp.getTask()
-                : null);
+        currentDisplay.updateTouchExcludeRegion();
     }
 
     /** Union the region with current tap exclude region that this window provides. */
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 080a3a2..24ac07a 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -763,6 +763,7 @@
 
     private void processApplicationsAnimatingInPlace(int transit) {
         if (transit == TRANSIT_TASK_IN_PLACE) {
+            // TODO (b/111362605): non-default-display transition.
             // Find the focused window
             final WindowState win = mService.getDefaultDisplayContentLocked().findFocusedWindow();
             if (win != null) {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index fefd305..0cf79b6 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -255,6 +255,7 @@
         super.removeImmediately();
     }
 
+    @Override
     void onDisplayChanged(DisplayContent dc) {
         dc.reParentWindowToken(this);
         mDisplayContent = dc;
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index e2888cc..d8ac561 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -221,7 +221,8 @@
     status_t unregisterInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel);
 
     void setInputWindows(JNIEnv* env, jobjectArray windowHandleObjArray, int32_t displayId);
-    void setFocusedApplication(JNIEnv* env, jobject applicationHandleObj);
+    void setFocusedApplication(JNIEnv* env, int32_t displayId, jobject applicationHandleObj);
+    void setFocusedDisplay(JNIEnv* env, int32_t displayId);
     void setInputDispatchMode(bool enabled, bool frozen);
     void setSystemUiVisibility(int32_t visibility);
     void setPointerSpeed(int32_t speed);
@@ -771,10 +772,15 @@
     }
 }
 
-void NativeInputManager::setFocusedApplication(JNIEnv* env, jobject applicationHandleObj) {
+void NativeInputManager::setFocusedApplication(JNIEnv* env, int32_t displayId,
+        jobject applicationHandleObj) {
     sp<InputApplicationHandle> applicationHandle =
             android_server_InputApplicationHandle_getHandle(env, applicationHandleObj);
-    mInputManager->getDispatcher()->setFocusedApplication(applicationHandle);
+    mInputManager->getDispatcher()->setFocusedApplication(displayId, applicationHandle);
+}
+
+void NativeInputManager::setFocusedDisplay(JNIEnv* env, int32_t displayId) {
+    mInputManager->getDispatcher()->setFocusedDisplay(displayId);
 }
 
 void NativeInputManager::setInputDispatchMode(bool enabled, bool frozen) {
@@ -1413,10 +1419,17 @@
 }
 
 static void nativeSetFocusedApplication(JNIEnv* env, jclass /* clazz */,
-        jlong ptr, jobject applicationHandleObj) {
+        jlong ptr, jint displayId, jobject applicationHandleObj) {
     NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
 
-    im->setFocusedApplication(env, applicationHandleObj);
+    im->setFocusedApplication(env, displayId, applicationHandleObj);
+}
+
+static void nativeSetFocusedDisplay(JNIEnv* env, jclass /* clazz */,
+        jlong ptr, jint displayId) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    im->setFocusedDisplay(env, displayId);
 }
 
 static void nativeSetPointerCapture(JNIEnv* env, jclass /* clazz */, jlong ptr,
@@ -1638,8 +1651,10 @@
             (void*) nativeToggleCapsLock },
     { "nativeSetInputWindows", "(J[Lcom/android/server/input/InputWindowHandle;I)V",
             (void*) nativeSetInputWindows },
-    { "nativeSetFocusedApplication", "(JLcom/android/server/input/InputApplicationHandle;)V",
+    { "nativeSetFocusedApplication", "(JILcom/android/server/input/InputApplicationHandle;)V",
             (void*) nativeSetFocusedApplication },
+    { "nativeSetFocusedDisplay", "(JI)V",
+            (void*) nativeSetFocusedDisplay },
     { "nativeSetPointerCapture", "(JZ)V",
             (void*) nativeSetPointerCapture },
     { "nativeSetInputDispatchMode", "(JZZ)V",
diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java
index 77a3e21..6ba7d94 100644
--- a/services/net/java/android/net/dhcp/DhcpPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpPacket.java
@@ -1,5 +1,8 @@
 package android.net.dhcp;
 
+import static android.net.util.NetworkConstants.IPV4_MAX_MTU;
+import static android.net.util.NetworkConstants.IPV4_MIN_MTU;
+
 import android.annotation.Nullable;
 import android.net.DhcpResults;
 import android.net.LinkAddress;
@@ -381,6 +384,26 @@
     }
 
     /**
+     * Returns whether a parameter is included in the parameter request list option of this packet.
+     *
+     * <p>If there is no parameter request list option in the packet, false is returned.
+     *
+     * @param paramId ID of the parameter, such as {@link #DHCP_MTU} or {@link #DHCP_HOST_NAME}.
+     */
+    public boolean hasRequestedParam(byte paramId) {
+        if (mRequestedParams == null) {
+            return false;
+        }
+
+        for (byte reqParam : mRequestedParams) {
+            if (reqParam == paramId) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Creates a new L3 packet (including IP header) containing the
      * DHCP udp packet.  This method relies upon the delegated method
      * finishPacket() to insert the per-packet contents.
@@ -696,7 +719,11 @@
         addTlv(buf, DHCP_ROUTER, mGateways);
         addTlv(buf, DHCP_DNS_SERVER, mDnsServers);
         addTlv(buf, DHCP_DOMAIN_NAME, mDomainName);
+        addTlv(buf, DHCP_HOST_NAME, mHostName);
         addTlv(buf, DHCP_VENDOR_INFO, mVendorInfo);
+        if (mMtu != null && Short.toUnsignedInt(mMtu) >= IPV4_MIN_MTU) {
+            addTlv(buf, DHCP_MTU, mMtu);
+        }
     }
 
     /**
@@ -1259,7 +1286,8 @@
         boolean broadcast, Inet4Address serverIpAddr, Inet4Address relayIp,
         Inet4Address yourIp, byte[] mac, Integer timeout, Inet4Address netMask,
         Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers,
-        Inet4Address dhcpServerIdentifier, String domainName, boolean metered) {
+        Inet4Address dhcpServerIdentifier, String domainName, String hostname, boolean metered,
+        short mtu) {
         DhcpPacket pkt = new DhcpOfferPacket(
                 transactionId, (short) 0, broadcast, serverIpAddr, relayIp,
                 INADDR_ANY /* clientIp */, yourIp, mac);
@@ -1267,9 +1295,11 @@
         pkt.mDnsServers = dnsServers;
         pkt.mLeaseTime = timeout;
         pkt.mDomainName = domainName;
+        pkt.mHostName = hostname;
         pkt.mServerIdentifier = dhcpServerIdentifier;
         pkt.mSubnetMask = netMask;
         pkt.mBroadcastAddress = bcAddr;
+        pkt.mMtu = mtu;
         if (metered) {
             pkt.mVendorInfo = VENDOR_INFO_ANDROID_METERED;
         }
@@ -1283,7 +1313,8 @@
         boolean broadcast, Inet4Address serverIpAddr, Inet4Address relayIp, Inet4Address yourIp,
         Inet4Address requestClientIp, byte[] mac, Integer timeout, Inet4Address netMask,
         Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers,
-        Inet4Address dhcpServerIdentifier, String domainName, boolean metered) {
+        Inet4Address dhcpServerIdentifier, String domainName, String hostname, boolean metered,
+        short mtu) {
         DhcpPacket pkt = new DhcpAckPacket(
                 transactionId, (short) 0, broadcast, serverIpAddr, relayIp, requestClientIp, yourIp,
                 mac);
@@ -1291,9 +1322,11 @@
         pkt.mDnsServers = dnsServers;
         pkt.mLeaseTime = timeout;
         pkt.mDomainName = domainName;
+        pkt.mHostName = hostname;
         pkt.mSubnetMask = netMask;
         pkt.mServerIdentifier = dhcpServerIdentifier;
         pkt.mBroadcastAddress = bcAddr;
+        pkt.mMtu = mtu;
         if (metered) {
             pkt.mVendorInfo = VENDOR_INFO_ANDROID_METERED;
         }
diff --git a/services/net/java/android/net/dhcp/DhcpServer.java b/services/net/java/android/net/dhcp/DhcpServer.java
index 2b3d577..cee6fa9 100644
--- a/services/net/java/android/net/dhcp/DhcpServer.java
+++ b/services/net/java/android/net/dhcp/DhcpServer.java
@@ -20,6 +20,7 @@
 import static android.net.NetworkUtils.getPrefixMaskAsInet4Address;
 import static android.net.TrafficStats.TAG_SYSTEM_DHCP_SERVER;
 import static android.net.dhcp.DhcpPacket.DHCP_CLIENT;
+import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME;
 import static android.net.dhcp.DhcpPacket.DHCP_SERVER;
 import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP;
 import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
@@ -46,6 +47,7 @@
 import android.os.SystemClock;
 import android.system.ErrnoException;
 import android.system.Os;
+import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.HexDump;
@@ -350,6 +352,19 @@
         return isEmpty(request.mClientIp) && (request.mBroadcast || isEmpty(lease.getNetAddr()));
     }
 
+    /**
+     * Get the hostname from a lease if non-empty and requested in the incoming request.
+     * @param request The incoming request.
+     * @return The hostname, or null if not requested or empty.
+     */
+    @Nullable
+    private static String getHostnameIfRequested(@NonNull DhcpPacket request,
+            @NonNull DhcpLease lease) {
+        return request.hasRequestedParam(DHCP_HOST_NAME) && !TextUtils.isEmpty(lease.getHostname())
+                ? lease.getHostname()
+                : null;
+    }
+
     private boolean transmitOffer(@NonNull DhcpPacket request, @NonNull DhcpLease lease,
             @NonNull MacAddress clientMac) {
         final boolean broadcastFlag = getBroadcastFlag(request, lease);
@@ -358,12 +373,14 @@
                 getPrefixMaskAsInet4Address(mServingParams.serverAddr.getPrefixLength());
         final Inet4Address broadcastAddr = getBroadcastAddress(
                 mServingParams.getServerInet4Addr(), mServingParams.serverAddr.getPrefixLength());
+        final String hostname = getHostnameIfRequested(request, lease);
         final ByteBuffer offerPacket = DhcpPacket.buildOfferPacket(
                 ENCAP_BOOTP, request.mTransId, broadcastFlag, mServingParams.getServerInet4Addr(),
                 request.mRelayIp, lease.getNetAddr(), request.mClientMac, timeout, prefixMask,
                 broadcastAddr, new ArrayList<>(mServingParams.defaultRouters),
                 new ArrayList<>(mServingParams.dnsServers),
-                mServingParams.getServerInet4Addr(), null /* domainName */, mServingParams.metered);
+                mServingParams.getServerInet4Addr(), null /* domainName */, hostname,
+                mServingParams.metered, (short) mServingParams.linkMtu);
 
         return transmitOfferOrAckPacket(offerPacket, request, lease, clientMac, broadcastFlag);
     }
@@ -374,13 +391,15 @@
         // transmitOffer above
         final boolean broadcastFlag = getBroadcastFlag(request, lease);
         final int timeout = getLeaseTimeout(lease);
+        final String hostname = getHostnameIfRequested(request, lease);
         final ByteBuffer ackPacket = DhcpPacket.buildAckPacket(ENCAP_BOOTP, request.mTransId,
                 broadcastFlag, mServingParams.getServerInet4Addr(), request.mRelayIp,
                 lease.getNetAddr(), request.mClientIp, request.mClientMac, timeout,
                 mServingParams.getPrefixMaskAsAddress(), mServingParams.getBroadcastAddress(),
                 new ArrayList<>(mServingParams.defaultRouters),
                 new ArrayList<>(mServingParams.dnsServers),
-                mServingParams.getServerInet4Addr(), null /* domainName */, mServingParams.metered);
+                mServingParams.getServerInet4Addr(), null /* domainName */, hostname,
+                mServingParams.metered, (short) mServingParams.linkMtu);
 
         return transmitOfferOrAckPacket(ackPacket, request, lease, clientMac, broadcastFlag);
     }
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
index 0345a81..3d0bdc9 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
@@ -232,6 +232,7 @@
         doReturn(displaySleeping).when(display).isSleeping();
         doReturn(keyguardShowing).when(keyguard).isKeyguardOrAodShowing(anyInt());
 
+        doReturn(isFocusedStack).when(stack).isFocusedStackOnDisplay();
         doReturn(isFocusedStack ? stack : null).when(display).getFocusedStack();
         mSupervisor.applySleepTokensLocked(true);
         verify(stack, times(expectWakeFromSleep ? 1 : 0)).awakeFromSleepingLocked();
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
index ab814ee..5fcd2aa 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
@@ -38,6 +38,7 @@
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 
 import android.content.pm.ActivityInfo;
 import android.os.UserHandle;
@@ -71,8 +72,8 @@
 
         setupActivityTaskManagerService();
         mDefaultDisplay = mSupervisor.getDefaultDisplay();
-        mStack = mDefaultDisplay.createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD,
-                true /* onTop */);
+        mStack = spy(mDefaultDisplay.createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD,
+                true /* onTop */));
         mTask = new TaskBuilder(mSupervisor).setStack(mStack).build();
     }
 
@@ -720,7 +721,7 @@
         doReturn(display).when(mSupervisor).getActivityDisplay(anyInt());
         doReturn(keyguardGoingAway).when(keyguardController).isKeyguardGoingAway();
         doReturn(displaySleeping).when(display).isSleeping();
-        doReturn(focusedStack ? mStack : null).when(mSupervisor).getTopDisplayFocusedStack();
+        doReturn(focusedStack).when(mStack).isFocusedStackOnDisplay();
 
         assertEquals(expected, mStack.shouldSleepActivities());
     }
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index b330304..96c5fde 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -32,6 +32,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
 
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 
 import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertEquals;
@@ -303,7 +304,8 @@
                 createTapEvent(dm0.widthPixels / 2, dm0.heightPixels / 2, false));
 
         // Check focus is on primary display.
-        assertEquals(sWm.mCurrentFocus, dc0.findFocusedWindow());
+        assertEquals(sWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus,
+                dc0.findFocusedWindow());
 
         // Tap on secondary display
         DisplayMetrics dm1 = dc1.getDisplayMetrics();
@@ -313,7 +315,8 @@
                 createTapEvent(dm1.widthPixels / 2, dm1.heightPixels / 2, false));
 
         // Check focus is on secondary.
-        assertEquals(sWm.mCurrentFocus, dc1.findFocusedWindow());
+        assertEquals(sWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus,
+                dc1.findFocusedWindow());
     }
 
     @Test
@@ -321,34 +324,29 @@
         // Create a focusable window and check that focus is calculated correctly
         final WindowState window1 =
                 createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "window1");
-        assertEquals(window1, sWm.mRoot.computeFocusedWindow());
+        updateFocusedWindow();
+        assertTrue(window1.isFocused());
+        assertEquals(window1, sWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus);
 
         // Check that a new display doesn't affect focus
         final DisplayContent dc = createNewDisplay();
-        assertEquals(window1, sWm.mRoot.computeFocusedWindow());
+        updateFocusedWindow();
+        assertTrue(window1.isFocused());
+        assertEquals(window1, sWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus);
 
         // Add a window to the second display, and it should be focused
         final WindowState window2 = createWindow(null, TYPE_BASE_APPLICATION, dc, "window2");
-        assertEquals(window2, sWm.mRoot.computeFocusedWindow());
+        updateFocusedWindow();
+        assertTrue(window1.isFocused());
+        assertTrue(window2.isFocused());
+        assertEquals(window2, sWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus);
 
         // Move the first window to the to including parents, and make sure focus is updated
         window1.getParent().positionChildAt(POSITION_TOP, window1, true);
-        assertEquals(window1, sWm.mRoot.computeFocusedWindow());
-    }
-
-    @Test
-    public void testKeyguard_preventsSecondaryDisplayFocus() throws Exception {
-        final WindowState keyguard = createWindow(null, TYPE_STATUS_BAR,
-                sWm.getDefaultDisplayContentLocked(), "keyguard");
-        assertEquals(keyguard, sWm.mRoot.computeFocusedWindow());
-
-        // Add a window to a second display, and it should be focused
-        final DisplayContent dc = createNewDisplay();
-        final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win");
-        assertEquals(win, sWm.mRoot.computeFocusedWindow());
-
-        mWmRule.getWindowManagerPolicy().keyguardShowingAndNotOccluded = true;
-        assertEquals(keyguard, sWm.mRoot.computeFocusedWindow());
+        updateFocusedWindow();
+        assertTrue(window1.isFocused());
+        assertTrue(window2.isFocused());
+        assertEquals(window1, sWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus);
     }
 
     /**
@@ -590,6 +588,12 @@
         assertEquals(displayContent.mBaseDisplayDensity, expectedBaseDensity);
     }
 
+    private void updateFocusedWindow() {
+        synchronized (sWm.mWindowMap) {
+            sWm.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false);
+        }
+    }
+
     /**
      * Create DisplayContent that does not update display base/initial values from device to keep
      * the values set by test.
diff --git a/startop/iorap/Android.bp b/startop/iorap/Android.bp
new file mode 100644
index 0000000..91f6aac
--- /dev/null
+++ b/startop/iorap/Android.bp
@@ -0,0 +1,51 @@
+// Copyright (C) 2018 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.
+
+java_library_static {
+  name: "libiorap-java",
+
+  aidl: {
+    include_dirs: [
+      "system/iorap/binder",
+    ],
+  },
+
+  srcs: [
+      ":iorap-aidl",
+      "**/*.java",
+  ],
+}
+
+android_test {
+    name: "libiorap-java-tests",
+    srcs: ["tests/src/**/*.kt"],
+
+    static_libs: [
+      // non-test dependencies
+      "libiorap-java",
+      // test android dependencies
+      "platform-test-annotations",
+      "android-support-test",
+      // test framework dependencies
+      "mockito-target-inline-minus-junit4",
+      "truth-prebuilt",
+    ],
+
+    //sdk_version: "current",
+    //certificate: "platform",
+
+    libs: ["android.test.base"],
+
+    test_suites: ["device-tests"],
+}
diff --git a/startop/iorap/AndroidManifest.xml b/startop/iorap/AndroidManifest.xml
new file mode 100644
index 0000000..8e5fe97
--- /dev/null
+++ b/startop/iorap/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+<!--suppress AndroidUnknownAttribute -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.google.android.startop.iorap.tests"
+    android:sharedUserId="com.google.android.startop.iorap.tests"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <!--suppress AndroidDomInspection -->
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.google.android.startop.iorap.tests" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+</manifest>
diff --git a/startop/iorap/src/com/google/android/startop/iorap/ActivityHintEvent.java b/startop/iorap/src/com/google/android/startop/iorap/ActivityHintEvent.java
new file mode 100644
index 0000000..1d38f4c
--- /dev/null
+++ b/startop/iorap/src/com/google/android/startop/iorap/ActivityHintEvent.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2018 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.google.android.startop.iorap;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Provide a hint to iorapd that an activity has transitioned state.<br /><br />
+ *
+ * Knowledge of when an activity starts/stops can be used by iorapd to increase system
+ * performance (e.g. by launching perfetto tracing to record an io profile, or by
+ * playing back an ioprofile via readahead) over the long run.<br /><br />
+ *
+ * /@see com.google.android.startop.iorap.IIorap#onActivityHintEvent<br /><br />
+ *
+ * Once an activity hint is in {@link #TYPE_STARTED} it must transition to another type.
+ * All other states could be terminal, see below: <br /><br />
+ *
+ * <pre>
+ *
+ *          ┌──────────────────────────────────────┐
+ *          │                                      ▼
+ *        ┌─────────┐     ╔════════════════╗     ╔═══════════╗
+ *    ──▶ │ STARTED │ ──▶ ║   COMPLETED    ║ ──▶ ║ CANCELLED ║
+ *        └─────────┘     ╚════════════════╝     ╚═══════════╝
+ *                          │
+ *                          │
+ *                          ▼
+ *                        ╔════════════════╗
+ *                        ║ POST_COMPLETED ║
+ *                        ╚════════════════╝
+ *
+ * </pre> <!-- system/iorap/docs/binder/ActivityHint.dot -->
+ *
+ * @hide
+ */
+public class ActivityHintEvent implements Parcelable {
+
+    public static final int TYPE_STARTED = 0;
+    public static final int TYPE_CANCELLED = 1;
+    public static final int TYPE_COMPLETED = 2;
+    public static final int TYPE_POST_COMPLETED = 3;
+    private static final int TYPE_MAX = TYPE_POST_COMPLETED;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "TYPE_" }, value = {
+            TYPE_STARTED,
+            TYPE_CANCELLED,
+            TYPE_COMPLETED,
+            TYPE_POST_COMPLETED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Type {}
+
+    @Type public final int type;
+    public final ActivityInfo activityInfo;
+
+    public ActivityHintEvent(@Type int type, ActivityInfo activityInfo) {
+        this.type = type;
+        this.activityInfo = activityInfo;
+        checkConstructorArguments();
+    }
+
+    private void checkConstructorArguments() {
+        CheckHelpers.checkTypeInRange(type, TYPE_MAX);
+        Objects.requireNonNull(activityInfo, "activityInfo");
+    }
+
+    @Override
+    public String toString() {
+        return String.format("{type: %d, activityInfo: %s}", type, activityInfo);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        } else if (other instanceof ActivityHintEvent) {
+            return equals((ActivityHintEvent) other);
+        }
+        return false;
+    }
+
+    private boolean equals(ActivityHintEvent other) {
+        return type == other.type &&
+                Objects.equals(activityInfo, other.activityInfo);
+    }
+
+    //<editor-fold desc="Binder boilerplate">
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(type);
+        activityInfo.writeToParcel(out, flags);
+    }
+
+    private ActivityHintEvent(Parcel in) {
+        this.type = in.readInt();
+        this.activityInfo = ActivityInfo.CREATOR.createFromParcel(in);
+        checkConstructorArguments();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<ActivityHintEvent> CREATOR
+            = new Parcelable.Creator<ActivityHintEvent>() {
+        public ActivityHintEvent createFromParcel(Parcel in) {
+            return new ActivityHintEvent(in);
+        }
+
+        public ActivityHintEvent[] newArray(int size) {
+            return new ActivityHintEvent[size];
+        }
+    };
+    //</editor-fold>
+}
diff --git a/startop/iorap/src/com/google/android/startop/iorap/ActivityInfo.java b/startop/iorap/src/com/google/android/startop/iorap/ActivityInfo.java
new file mode 100644
index 0000000..f47a42c
--- /dev/null
+++ b/startop/iorap/src/com/google/android/startop/iorap/ActivityInfo.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2018 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.google.android.startop.iorap;
+
+import java.util.Objects;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+/**
+ * Provide minimal information for launched activities to iorap.<br /><br />
+ *
+ * This uniquely identifies a system-wide activity by providing the {@link #packageName} and
+ * {@link #activityName}.
+ *
+ * @see ActivityHintEvent
+ * @see AppIntentEvent
+ *
+ * @hide
+ */
+public class ActivityInfo implements Parcelable {
+
+    /** The name of the package, for example {@code com.android.calculator}. */
+    public final String packageName;
+    /** The name of the activity, for example {@code .activities.activity.MainActivity} */
+    public final String activityName;
+
+    public ActivityInfo(String packageName, String activityName) {
+        this.packageName = packageName;
+        this.activityName = activityName;
+
+        checkConstructorArguments();
+    }
+
+    private void checkConstructorArguments() {
+        Objects.requireNonNull(packageName, "packageName");
+        Objects.requireNonNull(activityName, "activityName");
+    }
+
+    @Override
+    public String toString() {
+        return String.format("{packageName: %s, activityName: %s}", packageName, activityName);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        } else if (other instanceof ActivityInfo) {
+            return equals((ActivityInfo) other);
+        }
+        return false;
+    }
+
+    private boolean equals(ActivityInfo other) {
+        return Objects.equals(packageName, other.packageName) &&
+                Objects.equals(activityName, other.activityName);
+    }
+
+    //<editor-fold desc="Binder boilerplate">
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(packageName);
+        out.writeString(activityName);
+    }
+
+    private ActivityInfo(Parcel in) {
+        packageName = in.readString();
+        activityName = in.readString();
+
+        checkConstructorArguments();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<ActivityInfo> CREATOR
+            = new Parcelable.Creator<ActivityInfo>() {
+        public ActivityInfo createFromParcel(Parcel in) {
+            return new ActivityInfo(in);
+        }
+
+        public ActivityInfo[] newArray(int size) {
+            return new ActivityInfo[size];
+        }
+    };
+    //</editor-fold>
+}
diff --git a/startop/iorap/src/com/google/android/startop/iorap/AppIntentEvent.java b/startop/iorap/src/com/google/android/startop/iorap/AppIntentEvent.java
new file mode 100644
index 0000000..1cd37b5
--- /dev/null
+++ b/startop/iorap/src/com/google/android/startop/iorap/AppIntentEvent.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2018 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.google.android.startop.iorap;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Notifications for iorapd specifying when a system-wide intent defaults change.<br /><br />
+ *
+ * Intent defaults provide a mechanism for an app to register itself as an automatic handler.
+ * For example the camera app might be registered as the default handler for
+ * {@link android.provider.MediaStore#INTENT_ACTION_STILL_IMAGE_CAMERA} intent. Subsequently,
+ * if an arbitrary other app requests for a still image camera photo to be taken, the system
+ * will launch the respective default camera app to be launched to handle that request.<br /><br />
+ *
+ * In some cases iorapd might need to know default intents, e.g. for boot-time pinning of
+ * applications that resolve from the default intent. If the application would now be resolved
+ * differently, iorapd would unpin the old application and pin the new application.<br /><br />
+ *
+ * @hide
+ */
+public class AppIntentEvent implements Parcelable {
+
+    /** @see android.content.Intent#CATEGORY_DEFAULT */
+    public static final int TYPE_DEFAULT_INTENT_CHANGED = 0;
+    private static final int TYPE_MAX = 0;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "TYPE_" }, value = {
+            TYPE_DEFAULT_INTENT_CHANGED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Type {}
+
+    @Type public final int type;
+
+    public final ActivityInfo oldActivityInfo;
+    public final ActivityInfo newActivityInfo;
+
+    // TODO: Probably need the corresponding action here as well.
+
+    public static AppIntentEvent createDefaultIntentChanged(ActivityInfo oldActivityInfo,
+            ActivityInfo newActivityInfo) {
+        return new AppIntentEvent(TYPE_DEFAULT_INTENT_CHANGED, oldActivityInfo,
+                newActivityInfo);
+    }
+
+    private AppIntentEvent(@Type int type, ActivityInfo oldActivityInfo,
+            ActivityInfo newActivityInfo) {
+        this.type = type;
+        this.oldActivityInfo = oldActivityInfo;
+        this.newActivityInfo = newActivityInfo;
+
+        checkConstructorArguments();
+    }
+
+    private void checkConstructorArguments() {
+        CheckHelpers.checkTypeInRange(type, TYPE_MAX);
+        Objects.requireNonNull(oldActivityInfo, "oldActivityInfo");
+        Objects.requireNonNull(oldActivityInfo, "newActivityInfo");
+    }
+
+    @Override
+    public String toString() {
+        return String.format("{oldActivityInfo: %s, newActivityInfo: %s}", oldActivityInfo,
+                newActivityInfo);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        } else if (other instanceof AppIntentEvent) {
+            return equals((AppIntentEvent) other);
+        }
+        return false;
+    }
+
+    private boolean equals(AppIntentEvent other) {
+        return type == other.type &&
+                Objects.equals(oldActivityInfo, other.oldActivityInfo) &&
+                Objects.equals(newActivityInfo, other.newActivityInfo);
+    }
+
+    //<editor-fold desc="Binder boilerplate">
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(type);
+        oldActivityInfo.writeToParcel(out, flags);
+        newActivityInfo.writeToParcel(out, flags);
+    }
+
+    private AppIntentEvent(Parcel in) {
+        this.type = in.readInt();
+        this.oldActivityInfo = ActivityInfo.CREATOR.createFromParcel(in);
+        this.newActivityInfo = ActivityInfo.CREATOR.createFromParcel(in);
+
+        checkConstructorArguments();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<AppIntentEvent> CREATOR
+            = new Parcelable.Creator<AppIntentEvent>() {
+        public AppIntentEvent createFromParcel(Parcel in) {
+            return new AppIntentEvent(in);
+        }
+
+        public AppIntentEvent[] newArray(int size) {
+            return new AppIntentEvent[size];
+        }
+    };
+    //</editor-fold>
+}
diff --git a/startop/iorap/src/com/google/android/startop/iorap/CheckHelpers.java b/startop/iorap/src/com/google/android/startop/iorap/CheckHelpers.java
new file mode 100644
index 0000000..34aedd7
--- /dev/null
+++ b/startop/iorap/src/com/google/android/startop/iorap/CheckHelpers.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 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.google.android.startop.iorap;
+
+/**
+ * Convenience short-hand to throw {@link IllegalAccessException} when the arguments
+ * are out-of-range.
+ */
+public class CheckHelpers {
+    /** @throws IllegalAccessException if {@param type} is not in {@code [0..maxValue]} */
+    public static void checkTypeInRange(int type, int maxValue) {
+        if (type < 0) {
+            throw new IllegalArgumentException(
+                    String.format("type must be non-negative (value=%d)", type));
+        }
+        if (type > maxValue) {
+            throw new IllegalArgumentException(
+                    String.format("type out of range (value=%d, max=%d)", type, maxValue));
+        }
+    }
+
+    /** @throws IllegalAccessException if {@param state} is not in {@code [0..maxValue]} */
+    public static void checkStateInRange(int state, int maxValue) {
+        if (state < 0) {
+            throw new IllegalArgumentException(
+                    String.format("state must be non-negative (value=%d)", state));
+        }
+        if (state > maxValue) {
+            throw new IllegalArgumentException(
+                    String.format("state out of range (value=%d, max=%d)", state, maxValue));
+        }
+    }
+}
diff --git a/startop/iorap/src/com/google/android/startop/iorap/PackageEvent.java b/startop/iorap/src/com/google/android/startop/iorap/PackageEvent.java
new file mode 100644
index 0000000..aa4eea7
--- /dev/null
+++ b/startop/iorap/src/com/google/android/startop/iorap/PackageEvent.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2018 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.google.android.startop.iorap;
+
+import android.annotation.NonNull;
+import android.os.Parcelable;
+import android.os.Parcel;
+import android.net.Uri;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Forward package manager events to iorapd. <br /><br />
+ *
+ * Knowing when packages are modified by the system are a useful tidbit to help with performance:
+ * for example when a package is replaced, it could be a hint used to invalidate any collected
+ * io profiles used for prefetching or pinning.
+ *
+ * @hide
+ */
+public class PackageEvent implements Parcelable {
+
+    /** @see android.content.Intent#ACTION_PACKAGE_REPLACED */
+    public static final int TYPE_REPLACED = 0;
+    private static final int TYPE_MAX = 0;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "TYPE_" }, value = {
+            TYPE_REPLACED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Type {}
+
+    @Type public final int type;
+
+    /** The path that a package is installed in, for example {@code /data/app/.../base.apk}. */
+    public final Uri packageUri;
+    /** The name of the package, for example {@code com.android.calculator}. */
+    public final String packageName;
+
+    @NonNull
+    public static PackageEvent createReplaced(Uri packageUri, String packageName) {
+        return new PackageEvent(TYPE_REPLACED, packageUri, packageName);
+    }
+
+    private PackageEvent(@Type int type, Uri packageUri, String packageName) {
+        this.type = type;
+        this.packageUri = packageUri;
+        this.packageName = packageName;
+
+        checkConstructorArguments();
+    }
+
+    private void checkConstructorArguments() {
+        CheckHelpers.checkTypeInRange(type, TYPE_MAX);
+        Objects.requireNonNull(packageUri, "packageUri");
+        Objects.requireNonNull(packageName, "packageName");
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        } else if (other instanceof PackageEvent) {
+            return equals((PackageEvent) other);
+        }
+        return false;
+    }
+
+    private boolean equals(PackageEvent other) {
+        return type == other.type &&
+                Objects.equals(packageUri, other.packageUri) &&
+                Objects.equals(packageName, other.packageName);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("{packageUri: %s, packageName: %s}", packageUri, packageName);
+    }
+
+    //<editor-fold desc="Binder boilerplate">
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(type);
+        packageUri.writeToParcel(out, flags);
+        out.writeString(packageName);
+    }
+
+    private PackageEvent(Parcel in) {
+        this.type = in.readInt();
+        this.packageUri = Uri.CREATOR.createFromParcel(in);
+        this.packageName = in.readString();
+
+        checkConstructorArguments();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<PackageEvent> CREATOR
+            = new Parcelable.Creator<PackageEvent>() {
+        public PackageEvent createFromParcel(Parcel in) {
+            return new PackageEvent(in);
+        }
+
+        public PackageEvent[] newArray(int size) {
+            return new PackageEvent[size];
+        }
+    };
+    //</editor-fold>
+}
diff --git a/startop/iorap/src/com/google/android/startop/iorap/RequestId.java b/startop/iorap/src/com/google/android/startop/iorap/RequestId.java
new file mode 100644
index 0000000..2c79319
--- /dev/null
+++ b/startop/iorap/src/com/google/android/startop/iorap/RequestId.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2018 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.google.android.startop.iorap;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+import android.annotation.NonNull;
+
+/**
+ * Uniquely identify an {@link com.google.android.startop.iorap.IIorap} method invocation,
+ * used for asynchronous callbacks by the server. <br /><br />
+ *
+ * As all system server binder calls must be {@code oneway}, this means all invocations
+ * into {@link com.google.android.startop.iorap.IIorap} are non-blocking. The request ID
+ * exists to associate all calls with their respective callbacks in
+ * {@link com.google.android.startop.iorap.ITaskListener}.
+ *
+ * @see com.google.android.startop.iorap.IIorap
+ *
+ * @hide
+ */
+public class RequestId implements Parcelable {
+
+    public final long requestId;
+
+    private static Object mLock = new Object();
+    private static long mNextRequestId = 0;
+
+    /**
+     * Create a monotonically increasing request ID.<br /><br />
+     *
+     * It is invalid to re-use the same request ID for multiple method calls on
+     * {@link com.google.android.startop.iorap.IIorap}; a new request ID must be created
+     * each time.
+     */
+    @NonNull public static RequestId nextValueForSequence() {
+        long currentRequestId;
+        synchronized (mLock) {
+            currentRequestId = mNextRequestId;
+            ++mNextRequestId;
+        }
+        return new RequestId(currentRequestId);
+    }
+
+    private RequestId(long requestId) {
+        this.requestId = requestId;
+
+        checkConstructorArguments();
+    }
+
+    private void checkConstructorArguments() {
+        if (requestId < 0) {
+            throw new IllegalArgumentException("request id must be non-negative");
+        }
+    }
+
+    @Override
+    public String toString() {
+        return String.format("{requestId: %ld}", requestId);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        } else if (other instanceof RequestId) {
+            return equals((RequestId) other);
+        }
+        return false;
+    }
+
+    private boolean equals(RequestId other) {
+        return requestId == other.requestId;
+    }
+
+
+    //<editor-fold desc="Binder boilerplate">
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeLong(requestId);
+    }
+
+    private RequestId(Parcel in) {
+        requestId = in.readLong();
+
+        checkConstructorArguments();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<RequestId> CREATOR
+            = new Parcelable.Creator<RequestId>() {
+        public RequestId createFromParcel(Parcel in) {
+            return new RequestId(in);
+        }
+
+        public RequestId[] newArray(int size) {
+            return new RequestId[size];
+        }
+    };
+    //</editor-fold>
+}
diff --git a/startop/iorap/src/com/google/android/startop/iorap/SystemServiceEvent.java b/startop/iorap/src/com/google/android/startop/iorap/SystemServiceEvent.java
new file mode 100644
index 0000000..75d47f9
--- /dev/null
+++ b/startop/iorap/src/com/google/android/startop/iorap/SystemServiceEvent.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2018 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.google.android.startop.iorap;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Forward system service events to iorapd.
+ *
+ * @see com.android.server.SystemService
+ *
+ * @hide
+ */
+public class SystemServiceEvent implements Parcelable {
+
+    /** @see com.android.server.SystemService#onBootPhase */
+    public static final int TYPE_BOOT_PHASE = 0;
+    /** @see com.android.server.SystemService#onStart */
+    public static final int TYPE_START = 1;
+    private static final int TYPE_MAX = TYPE_START;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "TYPE_" }, value = {
+            TYPE_BOOT_PHASE,
+            TYPE_START,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Type {}
+
+    @Type public final int type;
+
+    // TODO: do we want to pass the exact build phase enum?
+
+    public SystemServiceEvent(@Type int type) {
+        this.type = type;
+        checkConstructorArguments();
+    }
+
+    private void checkConstructorArguments() {
+        CheckHelpers.checkTypeInRange(type, TYPE_MAX);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("{type: %d}", type);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        } else if (other instanceof SystemServiceEvent) {
+            return equals((SystemServiceEvent) other);
+        }
+        return false;
+    }
+
+    private boolean equals(SystemServiceEvent other) {
+        return type == other.type;
+    }
+
+    //<editor-fold desc="Binder boilerplate">
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(type);
+    }
+
+    private SystemServiceEvent(Parcel in) {
+        this.type = in.readInt();
+        checkConstructorArguments();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<SystemServiceEvent> CREATOR
+            = new Parcelable.Creator<SystemServiceEvent>() {
+        public SystemServiceEvent createFromParcel(Parcel in) {
+            return new SystemServiceEvent(in);
+        }
+
+        public SystemServiceEvent[] newArray(int size) {
+            return new SystemServiceEvent[size];
+        }
+    };
+    //</editor-fold>
+}
diff --git a/startop/iorap/src/com/google/android/startop/iorap/SystemServiceUserEvent.java b/startop/iorap/src/com/google/android/startop/iorap/SystemServiceUserEvent.java
new file mode 100644
index 0000000..b77c03c
--- /dev/null
+++ b/startop/iorap/src/com/google/android/startop/iorap/SystemServiceUserEvent.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2018 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.google.android.startop.iorap;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Forward user events to iorapd.<br /><br />
+ *
+ * Knowledge of the logged-in user is reserved to be used to set-up appropriate policies
+ * by iorapd (e.g. to handle user default pinned applications changing).
+ *
+ * @see com.android.server.SystemService
+ *
+ * @hide
+ */
+public class SystemServiceUserEvent implements Parcelable {
+
+    /** @see com.android.server.SystemService#onStartUser */
+    public static final int TYPE_START_USER = 0;
+    /** @see com.android.server.SystemService#onUnlockUser */
+    public static final int TYPE_UNLOCK_USER = 1;
+    /** @see com.android.server.SystemService#onSwitchUser*/
+    public static final int TYPE_SWITCH_USER = 2;
+    /** @see com.android.server.SystemService#onStopUser */
+    public static final int TYPE_STOP_USER = 3;
+    /** @see com.android.server.SystemService#onCleanupUser */
+    public static final int TYPE_CLEANUP_USER = 4;
+    private static final int TYPE_MAX = TYPE_CLEANUP_USER;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "TYPE_" }, value = {
+            TYPE_START_USER,
+            TYPE_UNLOCK_USER,
+            TYPE_SWITCH_USER,
+            TYPE_STOP_USER,
+            TYPE_CLEANUP_USER,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Type {}
+
+    @Type public final int type;
+    public final int userHandle;
+
+    public SystemServiceUserEvent(@Type int type, int userHandle) {
+        this.type = type;
+        this.userHandle = userHandle;
+        checkConstructorArguments();
+    }
+
+    private void checkConstructorArguments() {
+        CheckHelpers.checkTypeInRange(type, TYPE_MAX);
+        if (userHandle < 0) {
+            throw new IllegalArgumentException("userHandle must be non-negative");
+        }
+    }
+
+    @Override
+    public String toString() {
+        return String.format("{type: %d, userHandle: %d}", type, userHandle);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        } else if (other instanceof SystemServiceUserEvent) {
+            return equals((SystemServiceUserEvent) other);
+        }
+        return false;
+    }
+
+    private boolean equals(SystemServiceUserEvent other) {
+        return type == other.type &&
+                userHandle == other.userHandle;
+    }
+
+    //<editor-fold desc="Binder boilerplate">
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(type);
+        out.writeInt(userHandle);
+    }
+
+    private SystemServiceUserEvent(Parcel in) {
+        this.type = in.readInt();
+        this.userHandle = in.readInt();
+        checkConstructorArguments();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<SystemServiceUserEvent> CREATOR
+            = new Parcelable.Creator<SystemServiceUserEvent>() {
+        public SystemServiceUserEvent createFromParcel(Parcel in) {
+            return new SystemServiceUserEvent(in);
+        }
+
+        public SystemServiceUserEvent[] newArray(int size) {
+            return new SystemServiceUserEvent[size];
+        }
+    };
+    //</editor-fold>
+}
diff --git a/startop/iorap/src/com/google/android/startop/iorap/TaskResult.java b/startop/iorap/src/com/google/android/startop/iorap/TaskResult.java
new file mode 100644
index 0000000..b5fd6d8
--- /dev/null
+++ b/startop/iorap/src/com/google/android/startop/iorap/TaskResult.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2018 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.google.android.startop.iorap;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Result data accompanying a request for {@link com.google.android.startop.iorap.ITaskListener}
+ * callbacks.<br /><br />
+ *
+ * Following {@link com.google.android.startop.iorap.IIorap} method invocation,
+ * iorapd will issue in-order callbacks for that corresponding {@link RequestId}.<br /><br />
+ *
+ * State transitions are as follows: <br /><br />
+ *
+ * <pre>
+ *          ┌─────────────────────────────┐
+ *          │                             ▼
+ *        ┌───────┐     ┌─────────┐     ╔═══════════╗
+ *    ──▶ │ BEGAN │ ──▶ │ ONGOING │ ──▶ ║ COMPLETED ║
+ *        └───────┘     └─────────┘     ╚═══════════╝
+ *          │             │
+ *          │             │
+ *          ▼             │
+ *        ╔═══════╗       │
+ *    ──▶ ║ ERROR ║ ◀─────┘
+ *        ╚═══════╝
+ *
+ * </pre> <!-- system/iorap/docs/binder/TaskResult.dot -->
+ *
+ * @hide
+ */
+public class TaskResult implements Parcelable {
+
+    public static final int STATE_BEGAN = 0;
+    public static final int STATE_ONGOING = 1;
+    public static final int STATE_COMPLETED = 2;
+    public static final int STATE_ERROR = 3;
+    private static final int STATE_MAX = STATE_ERROR;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "STATE_" }, value = {
+            STATE_BEGAN,
+            STATE_ONGOING,
+            STATE_COMPLETED,
+            STATE_ERROR,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface State {}
+
+    @State public final int state;
+
+    @Override
+    public String toString() {
+        return String.format("{state: %d}", state);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        } else if (other instanceof TaskResult) {
+            return equals((TaskResult) other);
+        }
+        return false;
+    }
+
+    private boolean equals(TaskResult other) {
+        return state == other.state;
+    }
+
+    public TaskResult(@State int state) {
+        this.state = state;
+
+        checkConstructorArguments();
+    }
+
+    private void checkConstructorArguments() {
+        CheckHelpers.checkStateInRange(state, STATE_MAX);
+    }
+
+    //<editor-fold desc="Binder boilerplate">
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(state);
+    }
+
+    private TaskResult(Parcel in) {
+        state = in.readInt();
+
+        checkConstructorArguments();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<TaskResult> CREATOR
+            = new Parcelable.Creator<TaskResult>() {
+        public TaskResult createFromParcel(Parcel in) {
+            return new TaskResult(in);
+        }
+
+        public TaskResult[] newArray(int size) {
+            return new TaskResult[size];
+        }
+    };
+    //</editor-fold>
+}
diff --git a/startop/iorap/tests/src/com/google/android/startop/iorap/ParcelablesTest.kt b/startop/iorap/tests/src/com/google/android/startop/iorap/ParcelablesTest.kt
new file mode 100644
index 0000000..4abbb3e
--- /dev/null
+++ b/startop/iorap/tests/src/com/google/android/startop/iorap/ParcelablesTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2018 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.google.android.startop.iorap
+
+import android.net.Uri
+import android.os.Parcel
+import android.os.Parcelable
+import android.support.test.filters.SmallTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import com.google.common.truth.Truth.assertThat
+import org.junit.runners.Parameterized
+
+/**
+ * Basic unit tests to ensure that all of the [Parcelable]s in [com.google.android.startop.iorap]
+ * have a valid-conforming interface implementation.
+ */
+@SmallTest
+@RunWith(Parameterized::class)
+class ParcelablesTest<T : Parcelable>(private val inputData : InputData<T>) {
+    companion object {
+        private val initialRequestId = RequestId.nextValueForSequence()!!
+
+        @JvmStatic
+        @Parameterized.Parameters
+        fun data() = listOf(
+                InputData(
+                        newActivityInfo(),
+                        newActivityInfo(),
+                        ActivityInfo("some package", "some other activity")),
+                InputData(
+                        ActivityHintEvent(ActivityHintEvent.TYPE_COMPLETED, newActivityInfo()),
+                        ActivityHintEvent(ActivityHintEvent.TYPE_COMPLETED, newActivityInfo()),
+                        ActivityHintEvent(ActivityHintEvent.TYPE_POST_COMPLETED,
+                                newActivityInfo())),
+                InputData(
+                        AppIntentEvent.createDefaultIntentChanged(newActivityInfo(),
+                                newActivityInfoOther()),
+                        AppIntentEvent.createDefaultIntentChanged(newActivityInfo(),
+                                newActivityInfoOther()),
+                        AppIntentEvent.createDefaultIntentChanged(newActivityInfoOther(),
+                                newActivityInfo())),
+                InputData(
+                        PackageEvent.createReplaced(newUri(), "some package"),
+                        PackageEvent.createReplaced(newUri(), "some package"),
+                        PackageEvent.createReplaced(newUri(), "some other package")
+                ),
+                InputData(initialRequestId, cloneRequestId(initialRequestId),
+                        RequestId.nextValueForSequence()),
+                InputData(
+                        SystemServiceEvent(SystemServiceEvent.TYPE_BOOT_PHASE),
+                        SystemServiceEvent(SystemServiceEvent.TYPE_BOOT_PHASE),
+                        SystemServiceEvent(SystemServiceEvent.TYPE_START)),
+                InputData(
+                        SystemServiceUserEvent(SystemServiceUserEvent.TYPE_START_USER, 12345),
+                        SystemServiceUserEvent(SystemServiceUserEvent.TYPE_START_USER, 12345),
+                        SystemServiceUserEvent(SystemServiceUserEvent.TYPE_CLEANUP_USER, 12345)),
+                InputData(
+                        TaskResult(TaskResult.STATE_COMPLETED),
+                        TaskResult(TaskResult.STATE_COMPLETED),
+                        TaskResult(TaskResult.STATE_ONGOING))
+        )
+
+        private fun newActivityInfo() : ActivityInfo {
+            return ActivityInfo("some package", "some activity")
+        }
+
+        private fun newActivityInfoOther() : ActivityInfo {
+            return ActivityInfo("some package 2", "some activity 2")
+        }
+
+        private fun newUri() : Uri {
+            return Uri.parse("https://www.google.com")
+        }
+
+        private fun cloneRequestId(requestId: RequestId) : RequestId {
+            val constructor = requestId::class.java.declaredConstructors[0]
+            constructor.isAccessible = true
+            return constructor.newInstance(requestId.requestId) as RequestId
+        }
+    }
+
+    /**
+     * Test for [Object.equals] implementation.
+     */
+    @Test
+    fun testEquality() {
+        assertThat(inputData.valid).isEqualTo(inputData.valid)
+        assertThat(inputData.valid).isEqualTo(inputData.validCopy)
+        assertThat(inputData.valid).isNotEqualTo(inputData.validOther)
+    }
+
+    /**
+     * Test for [Parcelable] implementation.
+     */
+    @Test
+    fun testParcelRoundTrip() {
+        // calling writeToParcel and then T::CREATOR.createFromParcel would return the same data.
+        val assertParcels = { it : T, data : InputData<T> ->
+            val parcel = Parcel.obtain()
+            it.writeToParcel(parcel, 0)
+            parcel.setDataPosition(0) // future reads will see all previous writes.
+            assertThat(it).isEqualTo(data.createFromParcel(parcel))
+            parcel.recycle()
+        }
+
+        assertParcels(inputData.valid, inputData)
+        assertParcels(inputData.validCopy, inputData)
+        assertParcels(inputData.validOther, inputData)
+    }
+
+    data class InputData<T : Parcelable>(val valid : T, val validCopy : T, val validOther : T) {
+        val kls = valid.javaClass
+        init {
+            assertThat(valid).isNotSameAs(validCopy)
+            // Don't use isInstanceOf because of phantom warnings in intellij about Class!
+            assertThat(validCopy.javaClass).isEqualTo(valid.javaClass)
+            assertThat(validOther.javaClass).isEqualTo(valid.javaClass)
+        }
+
+        fun createFromParcel(parcel : Parcel) : T {
+            val field  = kls.getDeclaredField("CREATOR")
+            val creator = field.get(null) as Parcelable.Creator<T>
+
+            return creator.createFromParcel(parcel)
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 57b652e..9b5b700 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1633,11 +1633,21 @@
      * When {@code false}, use default title for Enhanced 4G LTE Mode settings.
      * When {@code true}, use the variant.
      * @hide
+     * @deprecated use {@link #KEY_ENHANCED_4G_LTE_TITLE_VARIANT_INT}.
      */
+    @Deprecated
     public static final String KEY_ENHANCED_4G_LTE_TITLE_VARIANT_BOOL =
             "enhanced_4g_lte_title_variant_bool";
 
     /**
+     * The index indicates the carrier specified title string of Enahnce 4G LTE Mode settings.
+     * Default value is 0, which indicates the default title string.
+     * @hide
+     */
+    public static final String KEY_ENHANCED_4G_LTE_TITLE_VARIANT_INT =
+            "enhanced_4g_lte_title_variant_int";
+
+    /**
      * Indicates whether the carrier wants to notify the user when handover of an LTE video call to
      * WIFI fails.
      * <p>
@@ -2419,6 +2429,7 @@
 
         sDefaults.putStringArray(KEY_IMS_REASONINFO_MAPPING_STRING_ARRAY, null);
         sDefaults.putBoolean(KEY_ENHANCED_4G_LTE_TITLE_VARIANT_BOOL, false);
+        sDefaults.putInt(KEY_ENHANCED_4G_LTE_TITLE_VARIANT_INT, 0);
         sDefaults.putBoolean(KEY_NOTIFY_VT_HANDOVER_TO_WIFI_FAILURE_BOOL, false);
         sDefaults.putStringArray(KEY_FILTERED_CNAP_NAMES_STRING_ARRAY, null);
         sDefaults.putBoolean(KEY_EDITABLE_WFC_ROAMING_MODE_BOOL, false);
diff --git a/tests/net/java/android/net/dhcp/DhcpPacketTest.java b/tests/net/java/android/net/dhcp/DhcpPacketTest.java
index 312b3d1..a592809 100644
--- a/tests/net/java/android/net/dhcp/DhcpPacketTest.java
+++ b/tests/net/java/android/net/dhcp/DhcpPacketTest.java
@@ -25,6 +25,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.annotation.Nullable;
 import android.net.DhcpResults;
 import android.net.LinkAddress;
 import android.net.NetworkUtils;
@@ -37,6 +38,7 @@
 import java.io.ByteArrayOutputStream;
 import java.net.Inet4Address;
 import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -56,6 +58,8 @@
     private static final Inet4Address NETMASK = getPrefixMaskAsInet4Address(PREFIX_LENGTH);
     private static final Inet4Address BROADCAST_ADDR = getBroadcastAddress(
             SERVER_ADDR, PREFIX_LENGTH);
+    private static final String HOSTNAME = "testhostname";
+    private static final short MTU = 1500;
     // Use our own empty address instead of Inet4Address.ANY or INADDR_ANY to ensure that the code
     // doesn't use == instead of equals when comparing addresses.
     private static final Inet4Address ANY = (Inet4Address) v4Address("0.0.0.0");
@@ -960,7 +964,8 @@
         assertTrue(msg, Arrays.equals(expected, actual));
     }
 
-    public void checkBuildOfferPacket(int leaseTimeSecs) throws Exception {
+    public void checkBuildOfferPacket(int leaseTimeSecs, @Nullable String hostname)
+            throws Exception {
         final int renewalTime = (int) (Integer.toUnsignedLong(leaseTimeSecs) / 2);
         final int rebindingTime = (int) (Integer.toUnsignedLong(leaseTimeSecs) * 875 / 1000);
         final int transactionId = 0xdeadbeef;
@@ -971,7 +976,8 @@
                 CLIENT_MAC, leaseTimeSecs, NETMASK /* netMask */,
                 BROADCAST_ADDR /* bcAddr */, Collections.singletonList(SERVER_ADDR) /* gateways */,
                 Collections.singletonList(SERVER_ADDR) /* dnsServers */,
-                SERVER_ADDR /* dhcpServerIdentifier */, null /* domainName */, false /* metered */);
+                SERVER_ADDR /* dhcpServerIdentifier */, null /* domainName */, hostname,
+                false /* metered */, MTU);
 
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
         // BOOTP headers
@@ -1027,12 +1033,22 @@
         // Nameserver
         bos.write(new byte[] { (byte) 0x06, (byte) 0x04 });
         bos.write(SERVER_ADDR.getAddress());
+        // Hostname
+        if (hostname != null) {
+            bos.write(new byte[]{(byte) 0x0c, (byte) hostname.length()});
+            bos.write(hostname.getBytes(Charset.forName("US-ASCII")));
+        }
+        // MTU
+        bos.write(new byte[] { (byte) 0x1a, (byte) 0x02 });
+        bos.write(shortToByteArray(MTU));
         // End options.
         bos.write(0xff);
 
-        final byte[] expected = bos.toByteArray();
-        assertTrue((expected.length & 1) == 0);
+        if ((bos.size() & 1) != 0) {
+            bos.write(0x00);
+        }
 
+        final byte[] expected = bos.toByteArray();
         final byte[] actual = new byte[packet.limit()];
         packet.get(actual);
         final String msg = "Expected:\n  " + HexDump.dumpHexString(expected) +
@@ -1042,13 +1058,18 @@
 
     @Test
     public void testOfferPacket() throws Exception {
-        checkBuildOfferPacket(3600);
-        checkBuildOfferPacket(Integer.MAX_VALUE);
-        checkBuildOfferPacket(0x80000000);
-        checkBuildOfferPacket(INFINITE_LEASE);
+        checkBuildOfferPacket(3600, HOSTNAME);
+        checkBuildOfferPacket(Integer.MAX_VALUE, HOSTNAME);
+        checkBuildOfferPacket(0x80000000, HOSTNAME);
+        checkBuildOfferPacket(INFINITE_LEASE, HOSTNAME);
+        checkBuildOfferPacket(3600, null);
     }
 
     private static byte[] intToByteArray(int val) {
         return ByteBuffer.allocate(4).putInt(val).array();
     }
+
+    private static byte[] shortToByteArray(short val) {
+        return ByteBuffer.allocate(2).putShort(val).array();
+    }
 }
diff --git a/tests/net/java/android/net/dhcp/DhcpServerTest.java b/tests/net/java/android/net/dhcp/DhcpServerTest.java
index 45a50d9..df34c73 100644
--- a/tests/net/java/android/net/dhcp/DhcpServerTest.java
+++ b/tests/net/java/android/net/dhcp/DhcpServerTest.java
@@ -17,6 +17,7 @@
 package android.net.dhcp;
 
 import static android.net.dhcp.DhcpPacket.DHCP_CLIENT;
+import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME;
 import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP;
 import static android.net.dhcp.DhcpPacket.INADDR_ANY;
 import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
@@ -87,6 +88,7 @@
             Arrays.asList(parseAddr("192.168.0.200"), parseAddr("192.168.0.201")));
     private static final long TEST_LEASE_TIME_SECS = 3600L;
     private static final int TEST_MTU = 1500;
+    private static final String TEST_HOSTNAME = "testhostname";
 
     private static final int TEST_TRANSACTION_ID = 123;
     private static final byte[] TEST_CLIENT_MAC_BYTES = new byte [] { 1, 2, 3, 4, 5, 6 };
@@ -96,7 +98,10 @@
     private static final long TEST_CLOCK_TIME = 1234L;
     private static final int TEST_LEASE_EXPTIME_SECS = 3600;
     private static final DhcpLease TEST_LEASE = new DhcpLease(null, TEST_CLIENT_MAC,
-            TEST_CLIENT_ADDR, TEST_LEASE_EXPTIME_SECS*1000L + TEST_CLOCK_TIME, null /* hostname */);
+            TEST_CLIENT_ADDR, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME,
+            null /* hostname */);
+    private static final DhcpLease TEST_LEASE_WITH_HOSTNAME = new DhcpLease(null, TEST_CLIENT_MAC,
+            TEST_CLIENT_ADDR, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME, TEST_HOSTNAME);
 
     @NonNull @Mock
     private Dependencies mDeps;
@@ -217,15 +222,17 @@
     public void testRequest_Selecting_Ack() throws Exception {
         when(mRepository.requestLease(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
                 eq(INADDR_ANY) /* clientAddr */, eq(INADDR_ANY) /* relayAddr */,
-                eq(TEST_CLIENT_ADDR) /* reqAddr */, eq(true) /* sidSet */, isNull() /* hostname */))
-                .thenReturn(TEST_LEASE);
+                eq(TEST_CLIENT_ADDR) /* reqAddr */, eq(true) /* sidSet */, eq(TEST_HOSTNAME)))
+                .thenReturn(TEST_LEASE_WITH_HOSTNAME);
 
         final DhcpRequestPacket request = makeRequestSelectingPacket();
+        request.mHostName = TEST_HOSTNAME;
+        request.mRequestedParams = new byte[] { DHCP_HOST_NAME };
         mServer.processPacket(request, DHCP_CLIENT);
 
         assertResponseSentTo(TEST_CLIENT_ADDR);
         final DhcpAckPacket packet = assertAck(getPacket());
-        assertMatchesTestLease(packet);
+        assertMatchesTestLease(packet, TEST_HOSTNAME);
     }
 
     @Test
@@ -270,14 +277,18 @@
      *  - other request states (init-reboot/renewing/rebinding)
      */
 
-    private void assertMatchesTestLease(@NonNull DhcpPacket packet) {
+    private void assertMatchesTestLease(@NonNull DhcpPacket packet, @Nullable String hostname) {
         assertMatchesClient(packet);
         assertFalse(packet.hasExplicitClientId());
         assertEquals(TEST_SERVER_ADDR, packet.mServerIdentifier);
         assertEquals(TEST_CLIENT_ADDR, packet.mYourIp);
         assertNotNull(packet.mLeaseTime);
         assertEquals(TEST_LEASE_EXPTIME_SECS, (int) packet.mLeaseTime);
-        assertNull(packet.mHostName);
+        assertEquals(hostname, packet.mHostName);
+    }
+
+    private void assertMatchesTestLease(@NonNull DhcpPacket packet) {
+        assertMatchesTestLease(packet, null);
     }
 
     private void assertMatchesClient(@NonNull DhcpPacket packet) {
diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
index 9c4da1f..14312cf 100644
--- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
@@ -83,16 +83,6 @@
         }
 
         try {
-            mWm.setFocusedApp(null, false);
-            fail("IWindowManager.setFocusedApp did not throw SecurityException as"
-                    + " expected");
-        } catch (SecurityException e) {
-            // expected
-        } catch (RemoteException e) {
-            fail("Unexpected remote exception");
-        }
-
-        try {
             mWm.prepareAppTransition(0, false);
             fail("IWindowManager.prepareAppTransition did not throw SecurityException as"
                     + " expected");