Set permissions for launching on private displays

- System UIDs must be allowed to launch anything and everywhere.
- Display owner must be allowed to launch activities on it.
- Apps that are already on target display must be allowed to launch
  there.
- All other apps mustn't be allowed to launch on private displays.

Bug: 34230873
Test: android.server.cts.ActivityManagerDisplayTests
Test: #testPermissionLaunchFromSystem
Test: #testPermissionLaunchFromAppOnSecondary
Test: #testPermissionLaunchFromOwner
Test: #testPermissionLaunchFromDifferentApp
Change-Id: Ic98005649a6368370c512e822cba4e9decc18ae9
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index aea1258..3eb5844 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -19,6 +19,8 @@
 import android.hardware.SensorManager;
 import android.os.Handler;
 import android.os.PowerManager;
+import android.util.IntArray;
+import android.util.SparseArray;
 import android.view.Display;
 import android.view.DisplayInfo;
 
@@ -147,6 +149,21 @@
     public abstract void setDisplayOffsets(int displayId, int x, int y);
 
     /**
+     * Provide a list of UIDs that are present on the display and are allowed to access it.
+     *
+     * @param displayAccessUIDs Mapping displayId -> int array of UIDs.
+     */
+    public abstract void setDisplayAccessUIDs(SparseArray<IntArray> displayAccessUIDs);
+
+    /**
+     * Check if specified UID's content is present on display and should be granted access to it.
+     *
+     * @param uid UID to be checked.
+     * @param displayId id of the display where presence of the content is checked.
+     * */
+    public abstract boolean isUidPresentOnDisplay(int uid, int displayId);
+
+    /**
      * Describes the requested power state of the display.
      *
      * This object is intended to describe the general characteristics of the
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 3748134..bc4ae6d 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -750,13 +750,14 @@
      * @param windowFlags Window layout flags.
      * @param overrideConfig override configuration to consider when generating
      *        context to for resources.
+     * @param displayId Id of the display to show the splash screen at.
      *
      * @return The starting surface.
      *
      */
     public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
             CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
-            int logo, int windowFlags, Configuration overrideConfig);
+            int logo, int windowFlags, Configuration overrideConfig, int displayId);
 
     /**
      * Prepare for a window being added to the window manager.  You can throw an
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index a2fb9f9..40428c4 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -149,6 +149,7 @@
     AppWindowContainerController mWindowContainerController;
     final ActivityInfo info; // all about me
     final ApplicationInfo appInfo; // information about activity's app
+    final int launchedFromPid; // always the pid who started the activity.
     final int launchedFromUid; // always the uid who started the activity.
     final String launchedFromPackage; // always the package who started the activity.
     final int userId;          // Which user is this running for?
@@ -595,7 +596,7 @@
         return ResolverActivity.class.getName().equals(realActivity.getClassName());
     }
 
-    ActivityRecord(ActivityManagerService _service, ProcessRecord _caller,
+    ActivityRecord(ActivityManagerService _service, ProcessRecord _caller, int _launchedFromPid,
             int _launchedFromUid, String _launchedFromPackage, Intent _intent, String _resolvedType,
             ActivityInfo aInfo, Configuration _configuration,
             ActivityRecord _resultTo, String _resultWho, int _reqCode,
@@ -605,6 +606,7 @@
         service = _service;
         appToken = new Token(this);
         info = aInfo;
+        launchedFromPid = _launchedFromPid;
         launchedFromUid = _launchedFromUid;
         launchedFromPackage = _launchedFromPackage;
         userId = UserHandle.getUserId(aInfo.applicationInfo.uid);
@@ -2206,9 +2208,11 @@
             throw new XmlPullParserException("restoreActivity resolver error. Intent=" + intent +
                     " resolvedType=" + resolvedType);
         }
-        final ActivityRecord r = new ActivityRecord(service, /*caller*/null, launchedFromUid,
-                launchedFromPackage, intent, resolvedType, aInfo, service.getConfiguration(),
-                null, null, 0, componentSpecified, false, stackSupervisor, null, null, null);
+        final ActivityRecord r = new ActivityRecord(service, null /* caller */,
+                0 /* launchedFromPid */, launchedFromUid, launchedFromPackage, intent, resolvedType,
+                aInfo, service.getConfiguration(), null /* resultTo */, null /* resultWho */,
+                0 /* reqCode */, componentSpecified, false /* rootVoiceInteraction */,
+                stackSupervisor, null /* container */, null /* options */, null /* sourceRecord */);
 
         r.persistentState = persistentState;
         r.taskDescription = taskDescription;
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 4df0cb1..4510836 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -105,6 +105,7 @@
 import android.service.voice.IVoiceInteractionSession;
 import android.util.ArraySet;
 import android.util.EventLog;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -195,6 +196,12 @@
         return mActivityContainer.mActivityDisplay;
     }
 
+    @Override
+    void onParentChanged() {
+        super.onParentChanged();
+        mStackSupervisor.updateUIDsPresentOnDisplay();
+    }
+
     enum ActivityState {
         INITIALIZING,
         RESUMED,
@@ -680,6 +687,27 @@
         return null;
     }
 
+    boolean isInStackLocked(TaskRecord task) {
+        return mTaskHistory.contains(task);
+    }
+
+    /** Checks if there are tasks with specific UID in the stack. */
+    boolean isUidPresent(int uid) {
+        for (TaskRecord task : mTaskHistory) {
+            if (task.effectiveUid == uid) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** Get all UIDs that are present in the stack. */
+    void getPresentUIDs(IntArray presentUIDs) {
+        for (TaskRecord task : mTaskHistory) {
+            presentUIDs.add(task.effectiveUid);
+        }
+    }
+
     final boolean updateLRUListLocked(ActivityRecord r) {
         final boolean hadit = mLRUActivities.remove(r);
         mLRUActivities.add(r);
@@ -4904,13 +4932,13 @@
         final boolean toTop = position >= mTaskHistory.size();
         final ActivityStack prevStack = preAddTask(task, reason, toTop);
 
+        mTaskHistory.add(position, task);
         task.setStack(this);
 
         if (toTop) {
             updateTaskReturnToForTopInsertion(task);
         }
 
-        mTaskHistory.add(position, task);
         updateTaskMovement(task, toTop);
 
         postAddTask(task, prevStack);
@@ -4927,8 +4955,8 @@
 
         final ActivityRecord topRunningActivity = task.topRunningActivityLocked();
         final boolean wasResumed = topRunningActivity == task.getStack().mResumedActivity;
-        task.setStack(this);
         insertTaskAtPosition(task, index);
+        task.setStack(this);
         postAddTask(task, null /* prevStack */);
 
         if (wasResumed) {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 4fe8939..5ae0102 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -125,6 +125,7 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.VirtualDisplay;
 import android.hardware.input.InputManager;
 import android.hardware.input.InputManagerInternal;
@@ -154,6 +155,7 @@
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
+import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -380,7 +382,10 @@
     /** Mapping from displayId to display current state */
     private final SparseArray<ActivityDisplay> mActivityDisplays = new SparseArray<>();
 
-    InputManagerInternal mInputManagerInternal;
+    private final SparseArray<IntArray> mDisplayAccessUIDs = new SparseArray<>();
+
+    private DisplayManagerInternal mDisplayManagerInternal;
+    private InputManagerInternal mInputManagerInternal;
 
     /** The chain of tasks in lockTask mode. The current frontmost task is at the top, and tasks
      * may be finished until there is only one entry left. If this is empty the system is not
@@ -580,6 +585,7 @@
             mDisplayManager =
                     (DisplayManager)mService.mContext.getSystemService(Context.DISPLAY_SERVICE);
             mDisplayManager.registerDisplayListener(this, null);
+            mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
 
             Display[] displays = mDisplayManager.getDisplays();
             for (int displayNdx = displays.length - 1; displayNdx >= 0; --displayNdx) {
@@ -1452,7 +1458,7 @@
             ActivityRecord resultRecord, ActivityStack resultStack, ActivityOptions options) {
         final int startAnyPerm = mService.checkPermission(START_ANY_ACTIVITY, callingPid,
                 callingUid);
-        if (startAnyPerm ==  PERMISSION_GRANTED) {
+        if (startAnyPerm == PERMISSION_GRANTED) {
             return true;
         }
         final int componentRestriction = getComponentRestrictionForCallingPackage(
@@ -1519,25 +1525,76 @@
             // Check if someone tries to launch an activity on a private display with a different
             // owner.
             final int launchDisplayId = options.getLaunchDisplayId();
-            if (launchDisplayId != INVALID_DISPLAY) {
-                final ActivityDisplay activityDisplay = mActivityDisplays.get(launchDisplayId);
-                if (activityDisplay != null
-                        && (activityDisplay.mDisplay.getFlags() & FLAG_PRIVATE) != 0) {
-                    if (activityDisplay.mDisplay.getOwnerUid() != callingUid) {
-                        final String msg = "Permission Denial: starting " + intent.toString()
-                                + " from " + callerApp + " (pid=" + callingPid
-                                + ", uid=" + callingUid + ") with launchDisplayId="
-                                + launchDisplayId;
-                        Slog.w(TAG, msg);
-                        throw new SecurityException(msg);
-                    }
-                }
+            if (launchDisplayId != INVALID_DISPLAY
+                    && !isCallerAllowedToLaunchOnDisplay(callingPid, callingUid, launchDisplayId)) {
+                final String msg = "Permission Denial: starting " + intent.toString()
+                        + " from " + callerApp + " (pid=" + callingPid
+                        + ", uid=" + callingUid + ") with launchDisplayId="
+                        + launchDisplayId;
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
             }
         }
 
         return true;
     }
 
+    /** Check if caller is allowed to launch activities on specified display. */
+    boolean isCallerAllowedToLaunchOnDisplay(int callingPid, int callingUid, int launchDisplayId) {
+        if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check: displayId=" + launchDisplayId
+                + " callingPid=" + callingPid + " callingUid=" + callingUid);
+
+        final ActivityDisplay activityDisplay = mActivityDisplays.get(launchDisplayId);
+        if (activityDisplay == null) {
+            Slog.w(TAG, "Launch on display check: display not found");
+            return false;
+        }
+
+        if (!activityDisplay.isPrivate()) {
+            // Anyone can launch on a public display.
+            if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
+                    + " allow launch on public display");
+            return true;
+        }
+
+        // Check if the caller is the owner of the display.
+        if (activityDisplay.mDisplay.getOwnerUid() == callingUid) {
+            if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
+                    + " allow launch for owner of the display");
+            return true;
+        }
+
+        // Check if caller is present on display
+        if (activityDisplay.isUidPresent(callingUid)) {
+            if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
+                    + " allow launch for caller present on the display");
+            return true;
+        }
+
+        // Check if the caller can launch anything.
+        final int startAnyPerm = mService.checkPermission(START_ANY_ACTIVITY, callingPid,
+                callingUid);
+        if (startAnyPerm == PERMISSION_GRANTED) {
+            if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
+                    + " allow launch any on display");
+            return true;
+        }
+
+        Slog.w(TAG, "Launch on display check: denied");
+        return false;
+    }
+
+    /** Update lists of UIDs that are present on displays and have access to them. */
+    void updateUIDsPresentOnDisplay() {
+        mDisplayAccessUIDs.clear();
+        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
+            final ActivityDisplay activityDisplay = mActivityDisplays.valueAt(displayNdx);
+            mDisplayAccessUIDs.append(activityDisplay.mDisplayId, activityDisplay.getPresentUIDs());
+        }
+        // Store updated lists in DisplayManager. Callers from outside of AM should get them there.
+        mDisplayManagerInternal.setDisplayAccessUIDs(mDisplayAccessUIDs);
+    }
+
     UserInfo getUserInfo(int userId) {
         final long identity = Binder.clearCallingIdentity();
         try {
@@ -4568,6 +4625,9 @@
 
         ActivityRecord mVisibleBehindActivity;
 
+        /** Array of all UIDs that are present on the display. */
+        private IntArray mDisplayAccessUIDs = new IntArray();
+
         ActivityDisplay() {
         }
 
@@ -4630,6 +4690,28 @@
         protected ConfigurationContainer getParent() {
             return ActivityStackSupervisor.this;
         }
+
+        boolean isPrivate() {
+            return (mDisplay.getFlags() & FLAG_PRIVATE) != 0;
+        }
+
+        boolean isUidPresent(int uid) {
+            for (ActivityStack stack : mStacks) {
+                if (stack.isUidPresent(uid)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /** Update and get all UIDs that are present on the display and have access to it. */
+        private IntArray getPresentUIDs() {
+            mDisplayAccessUIDs.clear();
+            for (ActivityStack stack : mStacks) {
+                stack.getPresentUIDs(mDisplayAccessUIDs);
+            }
+            return mDisplayAccessUIDs;
+        }
     }
 
     class VirtualActivityDisplay extends ActivityDisplay {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 2634385..c064cb0 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -476,10 +476,10 @@
             aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, null /*profilerInfo*/);
         }
 
-        ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
-                intent, resolvedType, aInfo, mService.getGlobalConfiguration(), resultRecord,
-                resultWho, requestCode, componentSpecified, voiceSession != null, mSupervisor,
-                container, options, sourceRecord);
+        ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid,
+                callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(),
+                resultRecord, resultWho, requestCode, componentSpecified, voiceSession != null,
+                mSupervisor, container, options, sourceRecord);
         if (outActivity != null) {
             outActivity[0] = r;
         }
@@ -1896,16 +1896,14 @@
 
         final ActivityStack currentStack = task != null ? task.getStack() : null;
         if (currentStack != null) {
-            if (currentStack.isOnHomeDisplay()) {
-                if (mSupervisor.mFocusedStack != currentStack) {
-                    if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
-                            "computeStackFocus: Setting " + "focused stack to r=" + r
-                                    + " task=" + task);
-                } else {
-                    if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
-                            "computeStackFocus: Focused stack already="
-                                    + mSupervisor.mFocusedStack);
-                }
+            if (mSupervisor.mFocusedStack != currentStack) {
+                if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
+                        "computeStackFocus: Setting " + "focused stack to r=" + r
+                                + " task=" + task);
+            } else {
+                if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
+                        "computeStackFocus: Focused stack already="
+                                + mSupervisor.mFocusedStack);
             }
             return currentStack;
         }
@@ -1922,13 +1920,7 @@
         // Same also applies to dynamic stacks, as they behave similar to fullscreen stack.
         // If the freeform or docked stack has focus, and the activity to be launched is resizeable,
         // we can also put it in the focused stack.
-        final int focusedStackId = mSupervisor.mFocusedStack.mStackId;
-        final boolean canUseFocusedStack = focusedStackId == FULLSCREEN_WORKSPACE_STACK_ID
-                || (focusedStackId == DOCKED_STACK_ID && r.canGoInDockedStack())
-                || (focusedStackId == FREEFORM_WORKSPACE_STACK_ID && r.isResizeableOrForced())
-                || isDynamicStack(focusedStackId);
-        if (canUseFocusedStack && (!newTask
-                || mSupervisor.mFocusedStack.mActivityContainer.isEligibleForNewTasks())) {
+        if (canLaunchIntoFocusedStack(r, newTask)) {
             if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
                     "computeStackFocus: Have a focused stack=" + mSupervisor.mFocusedStack);
             return mSupervisor.mFocusedStack;
@@ -1955,6 +1947,36 @@
         return stack;
     }
 
+    /** Check if provided activity record can launch in currently focused stack. */
+    private boolean canLaunchIntoFocusedStack(ActivityRecord r, boolean newTask) {
+        // The fullscreen stack can contain any task regardless of if the task is resizeable
+        // or not. So, we let the task go in the fullscreen task if it is the focus stack.
+        // Same also applies to dynamic stacks, as they behave similar to fullscreen stack.
+        // If the freeform or docked stack has focus, and the activity to be launched is resizeable,
+        // we can also put it in the focused stack.
+        final ActivityStack focusedStack = mSupervisor.mFocusedStack;
+        final int focusedStackId = mSupervisor.mFocusedStack.mStackId;
+        final boolean canUseFocusedStack;
+        switch (focusedStackId) {
+            case FULLSCREEN_WORKSPACE_STACK_ID:
+                canUseFocusedStack = true;
+                break;
+            case DOCKED_STACK_ID:
+                canUseFocusedStack = r.canGoInDockedStack();
+                break;
+            case FREEFORM_WORKSPACE_STACK_ID:
+                canUseFocusedStack = r.isResizeableOrForced();
+                break;
+            default:
+                canUseFocusedStack = isDynamicStack(focusedStackId)
+                        && mSupervisor.isCallerAllowedToLaunchOnDisplay(r.launchedFromPid,
+                        r.launchedFromUid, focusedStack.mDisplayId);
+        }
+
+        return canUseFocusedStack
+                && (!newTask || focusedStack.mActivityContainer.isEligibleForNewTasks());
+    }
+
     private ActivityStack getLaunchStack(ActivityRecord r, int launchFlags, TaskRecord task,
             ActivityOptions aOptions) {
 
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index a72a958..6f984a5c 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -741,8 +741,14 @@
         return mStack;
     }
 
-    /** Must be used for setting parent stack because it performs configuration updates. */
+    /**
+     * Must be used for setting parent stack because it performs configuration updates.
+     * Must be called after adding task as a child to the stack.
+     */
     void setStack(ActivityStack stack) {
+        if (stack != null && !stack.isInStackLocked(this)) {
+            throw new IllegalStateException("Task must be added as a Stack child first.");
+        }
         mStack = stack;
         onParentChanged();
     }
@@ -769,6 +775,12 @@
         return mStack;
     }
 
+    @Override
+    void onParentChanged() {
+        super.onParentChanged();
+        mService.mStackSupervisor.updateUIDsPresentOnDisplay();
+    }
+
     // Close up recents linked list.
     void closeRecentsChain() {
         if (mPrevAffiliate != null) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 9c762cc..cd07793 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -48,6 +48,7 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.text.TextUtils;
+import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
@@ -230,6 +231,9 @@
     // intended for use inside of the requestGlobalDisplayStateInternal function.
     private final ArrayList<Runnable> mTempDisplayStateWorkQueue = new ArrayList<Runnable>();
 
+    // Lists of UIDs that are present on the displays. Maps displayId -> array of UIDs.
+    private final SparseArray<IntArray> mDisplayAccessUIDs = new SparseArray<>();
+
     public DisplayManagerService(Context context) {
         super(context);
         mContext = context;
@@ -394,7 +398,8 @@
             LogicalDisplay display = mLogicalDisplays.get(displayId);
             if (display != null) {
                 DisplayInfo info = display.getDisplayInfoLocked();
-                if (info.hasAccess(callingUid)) {
+                if (info.hasAccess(callingUid)
+                        || isUidPresentOnDisplayInternal(callingUid, displayId)) {
                     return info;
                 }
             }
@@ -937,6 +942,25 @@
         }
     }
 
+    // Updates the lists of UIDs that are present on displays.
+    private void setDisplayAccessUIDsInternal(SparseArray<IntArray> newDisplayAccessUIDs) {
+        synchronized (mSyncRoot) {
+            mDisplayAccessUIDs.clear();
+            for (int i = newDisplayAccessUIDs.size() - 1; i >= 0; i--) {
+                mDisplayAccessUIDs.append(newDisplayAccessUIDs.keyAt(i),
+                        newDisplayAccessUIDs.valueAt(i));
+            }
+        }
+    }
+
+    // Checks if provided UID's content is present on the display and UID has access to it.
+    private boolean isUidPresentOnDisplayInternal(int uid, int displayId) {
+        synchronized (mSyncRoot) {
+            final IntArray displayUIDs = mDisplayAccessUIDs.get(displayId);
+            return displayUIDs != null && displayUIDs.indexOf(uid) != -1;
+        }
+    }
+
     private void clearViewportsLocked() {
         mDefaultViewport.valid = false;
         mExternalTouchViewport.valid = false;
@@ -1647,5 +1671,15 @@
         public void setDisplayOffsets(int displayId, int x, int y) {
             setDisplayOffsetsInternal(displayId, x, y);
         }
+
+        @Override
+        public void setDisplayAccessUIDs(SparseArray<IntArray> newDisplayAccessUIDs) {
+            setDisplayAccessUIDsInternal(newDisplayAccessUIDs);
+        }
+
+        @Override
+        public boolean isUidPresentOnDisplay(int uid, int displayId) {
+            return isUidPresentOnDisplayInternal(uid, displayId);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 32b8c9b..c25ad46 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -19,6 +19,8 @@
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.HOME_STACK_ID;
+import static android.content.Context.DISPLAY_SERVICE;
+import static android.content.Context.WINDOW_SERVICE;
 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
 import static android.content.pm.PackageManager.FEATURE_TELEVISION;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
@@ -140,6 +142,7 @@
 import android.database.ContentObserver;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiPlaybackClient;
 import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
@@ -2254,8 +2257,7 @@
             }
             lp.format = PixelFormat.TRANSLUCENT;
             lp.setTitle("PointerLocation");
-            WindowManager wm = (WindowManager)
-                    mContext.getSystemService(Context.WINDOW_SERVICE);
+            WindowManager wm = (WindowManager) mContext.getSystemService(WINDOW_SERVICE);
             lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
             wm.addView(mPointerLocationView, lp);
             mWindowManagerFuncs.registerPointerEventListener(mPointerLocationView);
@@ -2265,7 +2267,7 @@
     private void disablePointerLocation() {
         if (mPointerLocationView != null) {
             mWindowManagerFuncs.unregisterPointerEventListener(mPointerLocationView);
-            WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+            WindowManager wm = (WindowManager) mContext.getSystemService(WINDOW_SERVICE);
             wm.removeView(mPointerLocationView);
             mPointerLocationView = null;
         }
@@ -2826,7 +2828,7 @@
     @Override
     public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
             CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
-            int logo, int windowFlags, Configuration overrideConfig) {
+            int logo, int windowFlags, Configuration overrideConfig, int displayId) {
         if (!SHOW_SPLASH_SCREENS) {
             return null;
         }
@@ -2927,7 +2929,13 @@
 
             params.setTitle("Splash Screen " + packageName);
 
-            wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+            // Obtain proper context to launch on the right display.
+            final Context displayContext = getDisplayContext(context, displayId);
+            if (displayContext == null) {
+                // Can't show splash screen on requested display, so skip showing at all.
+                return null;
+            }
+            wm = (WindowManager) displayContext.getSystemService(WINDOW_SERVICE);
             view = win.getDecorView();
 
             if (DEBUG_SPLASH_SCREEN) Slog.d(TAG, "Adding splash screen window for "
@@ -2957,6 +2965,24 @@
         return null;
     }
 
+    /** Obtain proper context for showing splash screen on the provided display. */
+    private Context getDisplayContext(Context context, int displayId) {
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            // The default context fits.
+            return context;
+        }
+
+        final DisplayManager dm = (DisplayManager) context.getSystemService(DISPLAY_SERVICE);
+        final Display targetDisplay = dm.getDisplay(displayId);
+        if (targetDisplay == null) {
+            // Failed to obtain the non-default display where splash screen should be shown,
+            // lets not show at all.
+            return null;
+        }
+
+        return context.createDisplayContext(targetDisplay);
+    }
+
     /**
      * Preflight adding a window to the system.
      *
diff --git a/services/core/java/com/android/server/wm/SplashScreenStartingData.java b/services/core/java/com/android/server/wm/SplashScreenStartingData.java
index ee4209f..4b14f86 100644
--- a/services/core/java/com/android/server/wm/SplashScreenStartingData.java
+++ b/services/core/java/com/android/server/wm/SplashScreenStartingData.java
@@ -54,6 +54,6 @@
     StartingSurface createStartingSurface(AppWindowToken atoken) {
         return mService.mPolicy.addSplashScreen(atoken.token, mPkg, mTheme, mCompatInfo,
                 mNonLocalizedLabel, mLabelRes, mIcon, mLogo, mWindowFlags,
-                mMergedOverrideConfiguration);
+                mMergedOverrideConfiguration, atoken.getDisplayContent().getDisplayId());
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index dcc0c6f..71edbb7 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1115,7 +1115,8 @@
                         + displayId + ".  Aborting.");
                 return WindowManagerGlobal.ADD_INVALID_DISPLAY;
             }
-            if (!displayContent.hasAccess(session.mUid)) {
+            if (!displayContent.hasAccess(session.mUid)
+                    && !mDisplayManagerInternal.isUidPresentOnDisplay(session.mUid, displayId)) {
                 Slog.w(TAG_WM, "Attempted to add window to a display for which the application "
                         + "does not have access: " + displayId + ".  Aborting.");
                 return WindowManagerGlobal.ADD_INVALID_DISPLAY;
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
index c0c8fb0..269b719 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -321,7 +321,7 @@
     @Override
     public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
             CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
-            int logo, int windowFlags, Configuration overrideConfig) {
+            int logo, int windowFlags, Configuration overrideConfig, int displayId) {
         return null;
     }