diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 32671721f58b9..731a72d1ee8bd 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -251,6 +251,15 @@ public final class DisplayManager { */ public static final int VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 1 << 5; + /** + * Virtual display flag: Specifies that the virtual display can be associated with a + * touchpad device that matches its uniqueId. + * + * @see #createVirtualDisplay + * @hide + */ + public static final int VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH = 1 << 6; + /** @hide */ public DisplayManager(Context context) { mContext = context; @@ -542,16 +551,17 @@ public final class DisplayManager { public VirtualDisplay createVirtualDisplay(@NonNull String name, int width, int height, int densityDpi, @Nullable Surface surface, int flags, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) { - return createVirtualDisplay(null, - name, width, height, densityDpi, surface, flags, callback, handler); + return createVirtualDisplay(null /* projection */, name, width, height, densityDpi, surface, + flags, callback, handler, null /* uniqueId */); } /** @hide */ public VirtualDisplay createVirtualDisplay(@Nullable MediaProjection projection, @NonNull String name, int width, int height, int densityDpi, @Nullable Surface surface, - int flags, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) { + int flags, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler, + @Nullable String uniqueId) { return mGlobal.createVirtualDisplay(mContext, projection, - name, width, height, densityDpi, surface, flags, callback, handler); + name, width, height, densityDpi, surface, flags, callback, handler, uniqueId); } /** diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 341754c76edb4..876125e380141 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -383,7 +383,7 @@ public final class DisplayManagerGlobal { public VirtualDisplay createVirtualDisplay(Context context, MediaProjection projection, String name, int width, int height, int densityDpi, Surface surface, int flags, - VirtualDisplay.Callback callback, Handler handler) { + VirtualDisplay.Callback callback, Handler handler, String uniqueId) { if (TextUtils.isEmpty(name)) { throw new IllegalArgumentException("name must be non-null and non-empty"); } @@ -397,7 +397,8 @@ public final class DisplayManagerGlobal { int displayId; try { displayId = mDm.createVirtualDisplay(callbackWrapper, projectionToken, - context.getPackageName(), name, width, height, densityDpi, surface, flags); + context.getPackageName(), name, width, height, densityDpi, surface, flags, + uniqueId); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } diff --git a/core/java/android/hardware/display/DisplayViewport.java b/core/java/android/hardware/display/DisplayViewport.java index c2d498b7fe684..bb41b45f21a57 100644 --- a/core/java/android/hardware/display/DisplayViewport.java +++ b/core/java/android/hardware/display/DisplayViewport.java @@ -17,6 +17,7 @@ package android.hardware.display; import android.graphics.Rect; +import android.text.TextUtils; /** * Describes how the pixels of physical display device reflects the content of @@ -52,6 +53,9 @@ public final class DisplayViewport { public int deviceWidth; public int deviceHeight; + // The ID used to uniquely identify this display. + public String uniqueId; + public void copyFrom(DisplayViewport viewport) { valid = viewport.valid; displayId = viewport.displayId; @@ -60,6 +64,49 @@ public final class DisplayViewport { physicalFrame.set(viewport.physicalFrame); deviceWidth = viewport.deviceWidth; deviceHeight = viewport.deviceHeight; + uniqueId = viewport.uniqueId; + } + + public DisplayViewport makeCopy() { + DisplayViewport dv = new DisplayViewport(); + dv.copyFrom(this); + return dv; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof DisplayViewport)) { + return false; + } + + DisplayViewport other = (DisplayViewport) o; + return valid == other.valid && + displayId == other.displayId && + orientation == other.orientation && + logicalFrame.equals(other.logicalFrame) && + physicalFrame.equals(other.physicalFrame) && + deviceWidth == other.deviceWidth && + deviceHeight == other.deviceHeight && + TextUtils.equals(uniqueId, other.uniqueId); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result += prime * result + (valid ? 1 : 0); + result += prime * result + displayId; + result += prime * result + orientation; + result += prime * result + logicalFrame.hashCode(); + result += prime * result + physicalFrame.hashCode(); + result += prime * result + deviceWidth; + result += prime * result + deviceHeight; + result += prime * result + uniqueId.hashCode(); + return result; } // For debugging purposes. @@ -67,6 +114,7 @@ public final class DisplayViewport { public String toString() { return "DisplayViewport{valid=" + valid + ", displayId=" + displayId + + ", uniqueId='" + uniqueId + "'" + ", orientation=" + orientation + ", logicalFrame=" + logicalFrame + ", physicalFrame=" + physicalFrame diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index f696c8d154463..7ca4dc140eca2 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -66,7 +66,7 @@ interface IDisplayManager { // MediaProjection token for certain combinations of flags. int createVirtualDisplay(in IVirtualDisplayCallback callback, in IMediaProjection projectionToken, String packageName, String name, - int width, int height, int densityDpi, in Surface surface, int flags); + int width, int height, int densityDpi, in Surface surface, int flags, String uniqueId); // No permissions required, but must be same Uid as the creator. void resizeVirtualDisplay(in IVirtualDisplayCallback token, diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java index 01a540484f1a9..4ea0f55218a94 100644 --- a/core/java/android/hardware/input/InputManagerInternal.java +++ b/core/java/android/hardware/input/InputManagerInternal.java @@ -22,6 +22,8 @@ import android.view.InputEvent; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; +import java.util.List; + /** * Input manager local system service interface. * @@ -35,7 +37,7 @@ public abstract class InputManagerInternal { * by the input system. The input system must copy this information to retain it. */ public abstract void setDisplayViewports(DisplayViewport defaultViewport, - DisplayViewport externalTouchViewport); + DisplayViewport externalTouchViewport, List virtualTouchViewports); /** * Called by the power manager to tell the input manager whether it should start diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 77c72eb0e3d19..cab47582b8e8e 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -162,6 +162,7 @@ LOCAL_SRC_FILES:= \ android_hardware_camera2_legacy_LegacyCameraDevice.cpp \ android_hardware_camera2_legacy_PerfMeasurement.cpp \ android_hardware_camera2_DngCreator.cpp \ + android_hardware_display_DisplayViewport.cpp \ android_hardware_HardwareBuffer.cpp \ android_hardware_Radio.cpp \ android_hardware_SensorManager.cpp \ diff --git a/core/jni/android_hardware_display_DisplayViewport.cpp b/core/jni/android_hardware_display_DisplayViewport.cpp new file mode 100644 index 0000000000000..1823a2cfeccbd --- /dev/null +++ b/core/jni/android_hardware_display_DisplayViewport.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "DisplayViewport-JNI" + +#include "JNIHelp.h" +#include "core_jni_helpers.h" + +#include +#include +#include +#include + +#include + +namespace android { + +// ---------------------------------------------------------------------------- + +static struct { + jclass clazz; + + jfieldID displayId; + jfieldID orientation; + jfieldID logicalFrame; + jfieldID physicalFrame; + jfieldID deviceWidth; + jfieldID deviceHeight; + jfieldID uniqueId; +} gDisplayViewportClassInfo; + +static struct { + jfieldID left; + jfieldID top; + jfieldID right; + jfieldID bottom; +} gRectClassInfo; + +// ---------------------------------------------------------------------------- + +status_t android_hardware_display_DisplayViewport_toNative(JNIEnv* env, jobject viewportObj, + DisplayViewport* viewport) { + viewport->displayId = env->GetIntField(viewportObj, gDisplayViewportClassInfo.displayId); + viewport->orientation = env->GetIntField(viewportObj, gDisplayViewportClassInfo.orientation); + viewport->deviceWidth = env->GetIntField(viewportObj, gDisplayViewportClassInfo.deviceWidth); + viewport->deviceHeight = env->GetIntField(viewportObj, gDisplayViewportClassInfo.deviceHeight); + + jstring uniqueId = + jstring(env->GetObjectField(viewportObj, gDisplayViewportClassInfo.uniqueId)); + if (uniqueId != nullptr) { + viewport->uniqueId.setTo(ScopedUtfChars(env, uniqueId).c_str()); + } + + jobject logicalFrameObj = + env->GetObjectField(viewportObj, gDisplayViewportClassInfo.logicalFrame); + viewport->logicalLeft = env->GetIntField(logicalFrameObj, gRectClassInfo.left); + viewport->logicalTop = env->GetIntField(logicalFrameObj, gRectClassInfo.top); + viewport->logicalRight = env->GetIntField(logicalFrameObj, gRectClassInfo.right); + viewport->logicalBottom = env->GetIntField(logicalFrameObj, gRectClassInfo.bottom); + + jobject physicalFrameObj = + env->GetObjectField(viewportObj, gDisplayViewportClassInfo.physicalFrame); + viewport->physicalLeft = env->GetIntField(physicalFrameObj, gRectClassInfo.left); + viewport->physicalTop = env->GetIntField(physicalFrameObj, gRectClassInfo.top); + viewport->physicalRight = env->GetIntField(physicalFrameObj, gRectClassInfo.right); + viewport->physicalBottom = env->GetIntField(physicalFrameObj, gRectClassInfo.bottom); + + return OK; +} + +// ---------------------------------------------------------------------------- + +int register_android_hardware_display_DisplayViewport(JNIEnv* env) { + jclass clazz = FindClassOrDie(env, "android/hardware/display/DisplayViewport"); + gDisplayViewportClassInfo.clazz = MakeGlobalRefOrDie(env, clazz); + + gDisplayViewportClassInfo.displayId = GetFieldIDOrDie(env, + gDisplayViewportClassInfo.clazz, "displayId", "I"); + + gDisplayViewportClassInfo.orientation = GetFieldIDOrDie(env, + gDisplayViewportClassInfo.clazz, "orientation", "I"); + + gDisplayViewportClassInfo.deviceWidth = GetFieldIDOrDie(env, + gDisplayViewportClassInfo.clazz, "deviceWidth", "I"); + + gDisplayViewportClassInfo.deviceHeight = GetFieldIDOrDie(env, + gDisplayViewportClassInfo.clazz, "deviceHeight", "I"); + + gDisplayViewportClassInfo.logicalFrame = GetFieldIDOrDie(env, + gDisplayViewportClassInfo.clazz, "logicalFrame", "Landroid/graphics/Rect;"); + + gDisplayViewportClassInfo.physicalFrame = GetFieldIDOrDie(env, + gDisplayViewportClassInfo.clazz, "physicalFrame", "Landroid/graphics/Rect;"); + + gDisplayViewportClassInfo.uniqueId = GetFieldIDOrDie(env, + gDisplayViewportClassInfo.clazz, "uniqueId", "Ljava/lang/String;"); + + clazz = FindClassOrDie(env, "android/graphics/Rect"); + gRectClassInfo.left = GetFieldIDOrDie(env, clazz, "left", "I"); + gRectClassInfo.top = GetFieldIDOrDie(env, clazz, "top", "I"); + gRectClassInfo.right = GetFieldIDOrDie(env, clazz, "right", "I"); + gRectClassInfo.bottom = GetFieldIDOrDie(env, clazz, "bottom", "I"); + + return 0; +} + +} // namespace android diff --git a/core/jni/android_hardware_display_DisplayViewport.h b/core/jni/android_hardware_display_DisplayViewport.h new file mode 100644 index 0000000000000..5853177131631 --- /dev/null +++ b/core/jni/android_hardware_display_DisplayViewport.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_HARDWARE_DISPLAY_DISPLAYVIEWPORT_H +#define _ANDROID_HARDWARE_DISPLAY_DISPLAYVIEWPORT_H + +#include "jni.h" +#include +#include +#include + +namespace android { + +/* Copies the contents of a DVM DisplayViewport object to a native DisplayViewport instance. + * Returns non-zero on error. */ +extern status_t android_hardware_display_DisplayViewport_toNative(JNIEnv* env, jobject eventObj, + DisplayViewport* viewport); + +} // namespace android + +#endif // _ANDROID_HARDWARE_DISPLAY_DISPLAYVIEWPORT_H diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java index e757f093a4aea..f9c5b8d729c72 100644 --- a/media/java/android/media/projection/MediaProjection.java +++ b/media/java/android/media/projection/MediaProjection.java @@ -104,7 +104,8 @@ public final class MediaProjection { int flags = isSecure ? DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE : 0; return dm.createVirtualDisplay(this, name, width, height, dpi, surface, flags | DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR | - DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION, callback, handler); + DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION, callback, handler, + null /* uniqueId */); } /** @@ -134,8 +135,8 @@ public final class MediaProjection { int width, int height, int dpi, int flags, @Nullable Surface surface, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) { DisplayManager dm = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE); - return dm.createVirtualDisplay( - this, name, width, height, dpi, surface, flags, callback, handler); + return dm.createVirtualDisplay(this, name, width, height, dpi, surface, flags, callback, + handler, null /* uniqueId */); } /** diff --git a/services/core/java/com/android/server/Vr2dDisplay.java b/services/core/java/com/android/server/Vr2dDisplay.java index 1116e4e2c3fcd..f9bcb01b8c7bb 100644 --- a/services/core/java/com/android/server/Vr2dDisplay.java +++ b/services/core/java/com/android/server/Vr2dDisplay.java @@ -40,6 +40,8 @@ class Vr2dDisplay { private int mVirtualDisplayWidth; private int mVirtualDisplayDpi; private final static int STOP_VIRTUAL_DISPLAY_DELAY_MILLIS = 2000; + private final static String UNIQUE_DISPLAY_ID = "277f1a09-b88d-4d1e-8716-796f114d080b"; + private final static String DISPLAY_NAME = "VR 2D Display"; private final static String DEBUG_ACTION_SET_MODE = "com.android.server.vr.Vr2dDisplay.SET_MODE"; @@ -268,9 +270,11 @@ class Vr2dDisplay { return; } - mVirtualDisplay = mDisplayManager.createVirtualDisplay("VR 2D Display", - mVirtualDisplayWidth, mVirtualDisplayHeight, mVirtualDisplayDpi, - null /* Surface */, 0 /* flags */); + int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH; + mVirtualDisplay = mDisplayManager.createVirtualDisplay(null /* projection */, + DISPLAY_NAME, mVirtualDisplayWidth, mVirtualDisplayHeight, mVirtualDisplayDpi, + null /* surface */, flags, null /* callback */, null /* handler */, + UNIQUE_DISPLAY_ID); if (mVirtualDisplay != null) { mActivityManagerInternal.setVr2dDisplayId( diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index d5b54ca8bf391..419f9c548b180 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -4978,10 +4978,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D VirtualActivityDisplay(int width, int height, int density) { DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance(); - mVirtualDisplay = dm.createVirtualDisplay(mService.mContext, null, - VIRTUAL_DISPLAY_BASE_NAME, width, height, density, null, + mVirtualDisplay = dm.createVirtualDisplay(mService.mContext, null /* projection */, + VIRTUAL_DISPLAY_BASE_NAME, width, height, density, null /* surface */, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC | - DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY, null, null); + DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY, null /* callback */, + null /* handler */, null /* uniqueId */); init(mVirtualDisplay.getDisplay()); diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index 4a52b3ce5d95c..ef6de4c1ee51f 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -112,6 +112,13 @@ final class DisplayDeviceInfo { */ public static final int TOUCH_EXTERNAL = 2; + /** + * Touch attachment: Touch input is via an input device matching {@link VirtualDisplay}'s + * uniqueId. + * @hide + */ + public static final int TOUCH_VIRTUAL = 3; + /** * Diff result: The {@link #state} fields differ. */ @@ -391,6 +398,8 @@ final class DisplayDeviceInfo { return "INTERNAL"; case TOUCH_EXTERNAL: return "EXTERNAL"; + case TOUCH_VIRTUAL: + return "VIRTUAL"; default: return Integer.toString(touch); } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 208295847e24d..8129f450a85bc 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -23,10 +23,12 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECUR import static android.hardware.display.DisplayManager .VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import android.Manifest; +import android.annotation.NonNull; import android.content.Context; import android.content.pm.PackageManager; import android.hardware.SensorManager; @@ -213,6 +215,7 @@ public final class DisplayManagerService extends SystemService { // input from an external source. Used by the input system. private final DisplayViewport mDefaultViewport = new DisplayViewport(); private final DisplayViewport mExternalTouchViewport = new DisplayViewport(); + private final ArrayList mVirtualTouchViewports = new ArrayList<>(); // Persistent data store for all internal settings maintained by the display manager service. private final PersistentDataStore mPersistentDataStore = new PersistentDataStore(); @@ -228,6 +231,7 @@ public final class DisplayManagerService extends SystemService { // input system. May be used outside of the lock but only on the handler thread. private final DisplayViewport mTempDefaultViewport = new DisplayViewport(); private final DisplayViewport mTempExternalTouchViewport = new DisplayViewport(); + private final ArrayList mTempVirtualTouchViewports = new ArrayList<>(); // The default color mode for default displays. Overrides the usual // Display.Display.COLOR_MODE_DEFAULT for displays with the @@ -242,8 +246,16 @@ public final class DisplayManagerService extends SystemService { // Lists of UIDs that are present on the displays. Maps displayId -> array of UIDs. private final SparseArray mDisplayAccessUIDs = new SparseArray<>(); + private final Injector mInjector; + public DisplayManagerService(Context context) { + this(context, new Injector()); + } + + @VisibleForTesting + DisplayManagerService(Context context, Injector injector) { super(context); + mInjector = injector; mContext = context; mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper()); mUiHandler = UiThread.getHandler(); @@ -326,6 +338,11 @@ public final class DisplayManagerService extends SystemService { mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS); } + @VisibleForTesting + Handler getDisplayHandler() { + return mHandler; + } + private void registerDisplayTransactionListenerInternal( DisplayTransactionListener listener) { // List is self-synchronized copy-on-write. @@ -363,7 +380,8 @@ public final class DisplayManagerService extends SystemService { } } - private void performTraversalInTransactionFromWindowManagerInternal() { + @VisibleForTesting + void performTraversalInTransactionFromWindowManagerInternal() { synchronized (mSyncRoot) { if (!mPendingTraversal) { return; @@ -601,8 +619,8 @@ public final class DisplayManagerService extends SystemService { } private int createVirtualDisplayInternal(IVirtualDisplayCallback callback, - IMediaProjection projection, int callingUid, String packageName, - String name, int width, int height, int densityDpi, Surface surface, int flags) { + IMediaProjection projection, int callingUid, String packageName, String name, int width, + int height, int densityDpi, Surface surface, int flags, String uniqueId) { synchronized (mSyncRoot) { if (mVirtualDisplayAdapter == null) { Slog.w(TAG, "Rejecting request to create private virtual display " @@ -611,8 +629,8 @@ public final class DisplayManagerService extends SystemService { } DisplayDevice device = mVirtualDisplayAdapter.createVirtualDisplayLocked( - callback, projection, callingUid, packageName, - name, width, height, densityDpi, surface, flags); + callback, projection, callingUid, packageName, name, width, height, densityDpi, + surface, flags, uniqueId); if (device == null) { return -1; } @@ -702,8 +720,8 @@ public final class DisplayManagerService extends SystemService { } private void registerVirtualDisplayAdapterLocked() { - mVirtualDisplayAdapter = new VirtualDisplayAdapter( - mSyncRoot, mContext, mHandler, mDisplayAdapterListener); + mVirtualDisplayAdapter = mInjector.getVirtualDisplayAdapter(mSyncRoot, mContext, mHandler, + mDisplayAdapterListener); registerDisplayAdapterLocked(mVirtualDisplayAdapter); } @@ -995,6 +1013,7 @@ public final class DisplayManagerService extends SystemService { private void clearViewportsLocked() { mDefaultViewport.valid = false; mExternalTouchViewport.valid = false; + mVirtualTouchViewports.clear(); } private void configureDisplayInTransactionLocked(DisplayDevice device) { @@ -1033,6 +1052,28 @@ public final class DisplayManagerService extends SystemService { && info.touch == DisplayDeviceInfo.TOUCH_EXTERNAL) { setViewportLocked(mExternalTouchViewport, display, device); } + + if (info.touch == DisplayDeviceInfo.TOUCH_VIRTUAL && !TextUtils.isEmpty(info.uniqueId)) { + final DisplayViewport viewport = getVirtualTouchViewportLocked(info.uniqueId); + setViewportLocked(viewport, display, device); + } + } + + /** Gets the virtual device viewport or creates it if not yet created. */ + private DisplayViewport getVirtualTouchViewportLocked(@NonNull String uniqueId) { + DisplayViewport viewport; + final int count = mVirtualTouchViewports.size(); + for (int i = 0; i < count; i++) { + viewport = mVirtualTouchViewports.get(i); + if (uniqueId.equals(viewport.uniqueId)) { + return viewport; + } + } + + viewport = new DisplayViewport(); + viewport.uniqueId = uniqueId; + mVirtualTouchViewports.add(viewport); + return viewport; } private static void setViewportLocked(DisplayViewport viewport, @@ -1113,6 +1154,7 @@ public final class DisplayManagerService extends SystemService { pw.println(" mNextNonDefaultDisplayId=" + mNextNonDefaultDisplayId); pw.println(" mDefaultViewport=" + mDefaultViewport); pw.println(" mExternalTouchViewport=" + mExternalTouchViewport); + pw.println(" mVirtualTouchViewports=" + mVirtualTouchViewports); pw.println(" mDefaultDisplayDefaultColorMode=" + mDefaultDisplayDefaultColorMode); pw.println(" mSingleDisplayDemoMode=" + mSingleDisplayDemoMode); pw.println(" mWifiDisplayScanRequestCount=" + mWifiDisplayScanRequestCount); @@ -1171,6 +1213,14 @@ public final class DisplayManagerService extends SystemService { public static final class SyncRoot { } + @VisibleForTesting + static class Injector { + VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context, + Handler handler, DisplayAdapter.Listener displayAdapterListener) { + return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener); + } + } + private final class DisplayManagerHandler extends Handler { public DisplayManagerHandler(Looper looper) { super(looper, null, true /*async*/); @@ -1199,9 +1249,15 @@ public final class DisplayManagerService extends SystemService { synchronized (mSyncRoot) { mTempDefaultViewport.copyFrom(mDefaultViewport); mTempExternalTouchViewport.copyFrom(mExternalTouchViewport); + if (!mTempVirtualTouchViewports.equals(mVirtualTouchViewports)) { + mTempVirtualTouchViewports.clear(); + for (DisplayViewport d : mVirtualTouchViewports) { + mTempVirtualTouchViewports.add(d.makeCopy()); + } + } } - mInputManagerInternal.setDisplayViewports( - mTempDefaultViewport, mTempExternalTouchViewport); + mInputManagerInternal.setDisplayViewports(mTempDefaultViewport, + mTempExternalTouchViewport, mTempVirtualTouchViewports); break; } } @@ -1264,7 +1320,8 @@ public final class DisplayManagerService extends SystemService { } } - private final class BinderService extends IDisplayManager.Stub { + @VisibleForTesting + final class BinderService extends IDisplayManager.Stub { /** * Returns information about the specified logical display. * @@ -1458,7 +1515,8 @@ public final class DisplayManagerService extends SystemService { @Override // Binder call public int createVirtualDisplay(IVirtualDisplayCallback callback, IMediaProjection projection, String packageName, String name, - int width, int height, int densityDpi, Surface surface, int flags) { + int width, int height, int densityDpi, Surface surface, int flags, + String uniqueId) { final int callingUid = Binder.getCallingUid(); if (!validatePackageName(callingUid, packageName)) { throw new SecurityException("packageName must match the calling uid"); @@ -1520,8 +1578,8 @@ public final class DisplayManagerService extends SystemService { final long token = Binder.clearCallingIdentity(); try { - return createVirtualDisplayInternal(callback, projection, callingUid, - packageName, name, width, height, densityDpi, surface, flags); + return createVirtualDisplayInternal(callback, projection, callingUid, packageName, + name, width, height, densityDpi, surface, flags, uniqueId); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index 514993380564a..9d3021af1ac24 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -22,6 +22,7 @@ import static android.hardware.display.DisplayManager import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH; import android.content.Context; import android.hardware.display.IVirtualDisplayCallback; @@ -39,6 +40,8 @@ import android.view.Display; import android.view.Surface; import android.view.SurfaceControl; +import com.android.internal.annotations.VisibleForTesting; + import java.io.PrintWriter; import java.util.Iterator; @@ -48,7 +51,8 @@ import java.util.Iterator; * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock. *

*/ -final class VirtualDisplayAdapter extends DisplayAdapter { +@VisibleForTesting +public class VirtualDisplayAdapter extends DisplayAdapter { static final String TAG = "VirtualDisplayAdapter"; static final boolean DEBUG = false; @@ -57,27 +61,42 @@ final class VirtualDisplayAdapter extends DisplayAdapter { private final ArrayMap mVirtualDisplayDevices = new ArrayMap(); - private Handler mHandler; + private final Handler mHandler; + private final SurfaceControlDisplayFactory mSurfaceControlDisplayFactory; // Called with SyncRoot lock held. public VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler, Listener listener) { + this(syncRoot, context, handler, listener, + (String name, boolean secure) -> SurfaceControl.createDisplay(name, secure)); + } + + @VisibleForTesting + VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, + Context context, Handler handler, Listener listener, + SurfaceControlDisplayFactory surfaceControlDisplayFactory) { super(syncRoot, context, handler, listener, TAG); mHandler = handler; + mSurfaceControlDisplayFactory = surfaceControlDisplayFactory; } public DisplayDevice createVirtualDisplayLocked(IVirtualDisplayCallback callback, - IMediaProjection projection, int ownerUid, String ownerPackageName, - String name, int width, int height, int densityDpi, Surface surface, int flags) { + IMediaProjection projection, int ownerUid, String ownerPackageName, String name, + int width, int height, int densityDpi, Surface surface, int flags, String uniqueId) { boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0; IBinder appToken = callback.asBinder(); - IBinder displayToken = SurfaceControl.createDisplay(name, secure); + IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure); final String baseUniqueId = UNIQUE_ID_PREFIX + ownerPackageName + "," + ownerUid + "," + name + ","; final int uniqueIndex = getNextUniqueIndex(baseUniqueId); + if (uniqueId == null) { + uniqueId = baseUniqueId + uniqueIndex; + } else { + uniqueId = UNIQUE_ID_PREFIX + ownerPackageName + ":" + uniqueId; + } VirtualDisplayDevice device = new VirtualDisplayDevice(displayToken, appToken, ownerUid, ownerPackageName, name, width, height, densityDpi, surface, flags, - new Callback(callback, mHandler), baseUniqueId + uniqueIndex, uniqueIndex); + new Callback(callback, mHandler), uniqueId, uniqueIndex); mVirtualDisplayDevices.put(appToken, device); @@ -341,7 +360,8 @@ final class VirtualDisplayAdapter extends DisplayAdapter { mInfo.flags |= DisplayDeviceInfo.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD; } mInfo.type = Display.TYPE_VIRTUAL; - mInfo.touch = DisplayDeviceInfo.TOUCH_NONE; + mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ? + DisplayDeviceInfo.TOUCH_NONE : DisplayDeviceInfo.TOUCH_VIRTUAL; mInfo.state = mSurface != null ? Display.STATE_ON : Display.STATE_OFF; mInfo.ownerUid = mOwnerUid; mInfo.ownerPackageName = mOwnerPackageName; @@ -407,4 +427,9 @@ final class VirtualDisplayAdapter extends DisplayAdapter { } } } + + @VisibleForTesting + public interface SurfaceControlDisplayFactory { + public IBinder createDisplay(String name, boolean secure); + } } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index aafc9a8f50d7a..907b5c1c43304 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -190,11 +190,13 @@ public class InputManagerService extends IInputManager.Stub private static native long nativeInit(InputManagerService service, Context context, MessageQueue messageQueue); private static native void nativeStart(long ptr); - private static native void nativeSetDisplayViewport(long ptr, boolean external, + private static native void nativeSetVirtualDisplayViewports(long ptr, + DisplayViewport[] viewports); + private static native void nativeSetDisplayViewport(long ptr, int viewportType, int displayId, int rotation, int logicalLeft, int logicalTop, int logicalRight, int logicalBottom, int physicalLeft, int physicalTop, int physicalRight, int physicalBottom, - int deviceWidth, int deviceHeight); + int deviceWidth, int deviceHeight, String uniqueId); private static native int nativeGetScanCodeState(long ptr, int deviceId, int sourceMask, int scanCode); @@ -289,6 +291,11 @@ public class InputManagerService extends IInputManager.Stub /** Switch code: Camera lens cover. When set the lens is covered. */ public static final int SW_CAMERA_LENS_COVER = 0x09; + // Viewport constants defined in InputReader.h. + public static final int VIEWPORT_DEFAULT = 1; + public static final int VIEWPORT_EXTERNAL = 2; + public static final int VIEWPORT_VIRTUAL = 3; + public static final int SW_LID_BIT = 1 << SW_LID; public static final int SW_TABLET_MODE_BIT = 1 << SW_TABLET_MODE; public static final int SW_KEYPAD_SLIDE_BIT = 1 << SW_KEYPAD_SLIDE; @@ -406,26 +413,30 @@ public class InputManagerService extends IInputManager.Stub } private void setDisplayViewportsInternal(DisplayViewport defaultViewport, - DisplayViewport externalTouchViewport) { + DisplayViewport externalTouchViewport, + List virtualTouchViewports) { if (defaultViewport.valid) { - setDisplayViewport(false, defaultViewport); + setDisplayViewport(VIEWPORT_DEFAULT, defaultViewport); } if (externalTouchViewport.valid) { - setDisplayViewport(true, externalTouchViewport); + setDisplayViewport(VIEWPORT_EXTERNAL, externalTouchViewport); } else if (defaultViewport.valid) { - setDisplayViewport(true, defaultViewport); + setDisplayViewport(VIEWPORT_EXTERNAL, defaultViewport); } + + nativeSetVirtualDisplayViewports(mPtr, + virtualTouchViewports.toArray(new DisplayViewport[0])); } - private void setDisplayViewport(boolean external, DisplayViewport viewport) { - nativeSetDisplayViewport(mPtr, external, + private void setDisplayViewport(int viewportType, DisplayViewport viewport) { + nativeSetDisplayViewport(mPtr, viewportType, viewport.displayId, viewport.orientation, viewport.logicalFrame.left, viewport.logicalFrame.top, viewport.logicalFrame.right, viewport.logicalFrame.bottom, viewport.physicalFrame.left, viewport.physicalFrame.top, viewport.physicalFrame.right, viewport.physicalFrame.bottom, - viewport.deviceWidth, viewport.deviceHeight); + viewport.deviceWidth, viewport.deviceHeight, viewport.uniqueId); } /** @@ -2296,9 +2307,11 @@ public class InputManagerService extends IInputManager.Stub private final class LocalService extends InputManagerInternal { @Override - public void setDisplayViewports( - DisplayViewport defaultViewport, DisplayViewport externalTouchViewport) { - setDisplayViewportsInternal(defaultViewport, externalTouchViewport); + public void setDisplayViewports(DisplayViewport defaultViewport, + DisplayViewport externalTouchViewport, + List virtualTouchViewports) { + setDisplayViewportsInternal(defaultViewport, externalTouchViewport, + virtualTouchViewports); } @Override diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 8baa96bf9bae6..6f24e7e6197ff 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -57,6 +57,7 @@ #include "com_android_server_power_PowerManagerService.h" #include "com_android_server_input_InputApplicationHandle.h" #include "com_android_server_input_InputWindowHandle.h" +#include "android_hardware_display_DisplayViewport.h" #define INDENT " " @@ -190,7 +191,8 @@ public: void dump(String8& dump); - void setDisplayViewport(bool external, const DisplayViewport& viewport); + void setVirtualDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray); + void setDisplayViewport(int32_t viewportType, const DisplayViewport& viewport); status_t registerInputChannel(JNIEnv* env, const sp& inputChannel, const sp& inputWindowHandle, bool monitor); @@ -264,6 +266,7 @@ private: // Display size information. DisplayViewport internalViewport; DisplayViewport externalViewport; + Vector virtualViewports; // System UI visibility. int32_t systemUiVisibility; @@ -365,17 +368,53 @@ bool NativeInputManager::checkAndClearExceptionFromCallback(JNIEnv* env, const c return false; } -void NativeInputManager::setDisplayViewport(bool external, const DisplayViewport& viewport) { +void NativeInputManager::setVirtualDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray) { + Vector viewports; + + if (viewportObjArray) { + jsize length = env->GetArrayLength(viewportObjArray); + for (jsize i = 0; i < length; i++) { + jobject viewportObj = env->GetObjectArrayElement(viewportObjArray, i); + if (! viewportObj) { + break; // found null element indicating end of used portion of the array + } + + DisplayViewport viewport; + android_hardware_display_DisplayViewport_toNative(env, viewportObj, &viewport); + ALOGI("Viewport [%d] to add: %s", (int) length, viewport.uniqueId.c_str()); + viewports.push(viewport); + + env->DeleteLocalRef(viewportObj); + } + } + + { + AutoMutex _l(mLock); + mLocked.virtualViewports = viewports; + } + + mInputManager->getReader()->requestRefreshConfiguration( + InputReaderConfiguration::CHANGE_DISPLAY_INFO); +} + +void NativeInputManager::setDisplayViewport(int32_t type, const DisplayViewport& viewport) { bool changed = false; { AutoMutex _l(mLock); - DisplayViewport& v = external ? mLocked.externalViewport : mLocked.internalViewport; - if (v != viewport) { - changed = true; - v = viewport; + ViewportType viewportType = static_cast(type); + DisplayViewport* v = NULL; + if (viewportType == ViewportType::VIEWPORT_EXTERNAL) { + v = &mLocked.externalViewport; + } else if (viewportType == ViewportType::VIEWPORT_INTERNAL) { + v = &mLocked.internalViewport; + } - if (!external) { + if (v != NULL && *v != viewport) { + changed = true; + *v = viewport; + + if (viewportType == ViewportType::VIEWPORT_INTERNAL) { sp controller = mLocked.pointerController.promote(); if (controller != NULL) { controller->setDisplayViewport( @@ -468,8 +507,11 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon outConfig->pointerCapture = mLocked.pointerCapture; - outConfig->setDisplayInfo(false /*external*/, mLocked.internalViewport); - outConfig->setDisplayInfo(true /*external*/, mLocked.externalViewport); + outConfig->setPhysicalDisplayViewport(ViewportType::VIEWPORT_INTERNAL, + mLocked.internalViewport); + outConfig->setPhysicalDisplayViewport(ViewportType::VIEWPORT_EXTERNAL, + mLocked.externalViewport); + outConfig->setVirtualDisplayViewports(mLocked.virtualViewports); } // release lock } @@ -1153,11 +1195,17 @@ static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) { } } -static void nativeSetDisplayViewport(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, - jboolean external, jint displayId, jint orientation, +static void nativeSetVirtualDisplayViewports(JNIEnv* env, jclass /* clazz */, jlong ptr, + jobjectArray viewportObjArray) { + NativeInputManager* im = reinterpret_cast(ptr); + im->setVirtualDisplayViewports(env, viewportObjArray); +} + +static void nativeSetDisplayViewport(JNIEnv* env, jclass /* clazz */, jlong ptr, + jint viewportType, jint displayId, jint orientation, jint logicalLeft, jint logicalTop, jint logicalRight, jint logicalBottom, jint physicalLeft, jint physicalTop, jint physicalRight, jint physicalBottom, - jint deviceWidth, jint deviceHeight) { + jint deviceWidth, jint deviceHeight, jstring uniqueId) { NativeInputManager* im = reinterpret_cast(ptr); DisplayViewport v; @@ -1173,7 +1221,11 @@ static void nativeSetDisplayViewport(JNIEnv* /* env */, jclass /* clazz */, jlon v.physicalBottom = physicalBottom; v.deviceWidth = deviceWidth; v.deviceHeight = deviceHeight; - im->setDisplayViewport(external, v); + if (uniqueId != nullptr) { + v.uniqueId.setTo(ScopedUtfChars(env, uniqueId).c_str()); + } + + im->setDisplayViewport(viewportType, v); } static jint nativeGetScanCodeState(JNIEnv* /* env */, jclass /* clazz */, @@ -1514,7 +1566,9 @@ static const JNINativeMethod gInputManagerMethods[] = { (void*) nativeInit }, { "nativeStart", "(J)V", (void*) nativeStart }, - { "nativeSetDisplayViewport", "(JZIIIIIIIIIIII)V", + { "nativeSetVirtualDisplayViewports", "(J[Landroid/hardware/display/DisplayViewport;)V", + (void*) nativeSetVirtualDisplayViewports }, + { "nativeSetDisplayViewport", "(JIIIIIIIIIIIIILjava/lang/String;)V", (void*) nativeSetDisplayViewport }, { "nativeGetScanCodeState", "(JIII)I", (void*) nativeGetScanCodeState }, diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index f22b330b5b9de..e96bda910c6cf 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -47,6 +47,7 @@ int register_android_server_Watchdog(JNIEnv* env); int register_android_server_HardwarePropertiesManagerService(JNIEnv* env); int register_android_server_SyntheticPasswordManager(JNIEnv* env); int register_android_server_GraphicsStatsService(JNIEnv* env); +int register_android_hardware_display_DisplayViewport(JNIEnv* env); }; using namespace android; @@ -89,6 +90,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_storage_AppFuse(env); register_android_server_SyntheticPasswordManager(env); register_android_server_GraphicsStatsService(env); + register_android_hardware_display_DisplayViewport(env); return JNI_VERSION_1_4; } diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index fa72416edd16d..686dad4a8340b 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -191,7 +191,7 @@ + android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.servicestests" + android:label="Frameworks Services Tests" /> diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java new file mode 100644 index 0000000000000..c399a5de46dd5 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.display; + +import android.content.Context; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayViewport; +import android.hardware.display.IVirtualDisplayCallback; +import android.hardware.input.InputManagerInternal; +import android.os.Handler; +import android.os.IBinder; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.view.SurfaceControl; +import android.view.WindowManagerInternal; + +import com.android.server.LocalServices; +import com.android.server.display.DisplayManagerService.SyncRoot; +import com.android.server.display.VirtualDisplayAdapter.SurfaceControlDisplayFactory; + +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@SmallTest +public class DisplayManagerServiceTest extends AndroidTestCase { + private Handler mHandler; + private DisplayManagerService mDisplayManager; + @Mock InputManagerInternal mMockInputManagerInternal; + @Mock IVirtualDisplayCallback.Stub mMockAppToken; + @Mock WindowManagerInternal mMockWindowManagerInternal; + @Mock VirtualDisplayAdapter mMockVirtualDisplayAdapter; + @Mock IBinder mMockDisplayToken; + + @Override + protected void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mDisplayManager = new DisplayManagerService(mContext, + new DisplayManagerService.Injector() { + @Override + VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context, + Handler handler, DisplayAdapter.Listener displayAdapterListener) { + return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener, + (String name, boolean secure) -> mMockDisplayToken); + } + }); + mHandler = mDisplayManager.getDisplayHandler(); + + LocalServices.removeServiceForTest(InputManagerInternal.class); + LocalServices.addService(InputManagerInternal.class, mMockInputManagerInternal); + LocalServices.removeServiceForTest(WindowManagerInternal.class); + LocalServices.addService(WindowManagerInternal.class, mMockWindowManagerInternal); + + mDisplayManager.systemReady(false /* safeMode */, false /* onlyCore */); + mDisplayManager.windowManagerAndInputReady(); + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + public void testCreateVirtualDisplay_sentToInputManager() throws Exception { + // This is effectively the DisplayManager service published to ServiceManager. + DisplayManagerService.BinderService bs = mDisplayManager.new BinderService(); + + String uniqueId = "uniqueId --- Test"; + String uniqueIdPrefix = "virtual:" + mContext.getPackageName() + ":"; + int width = 600; + int height = 800; + int dpi = 320; + int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH; + + when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + int displayId = bs.createVirtualDisplay(mMockAppToken /* callback */, + null /* projection */, "com.android.frameworks.servicestests", + "Test Virtual Display", width, height, dpi, null /* surface */, flags /* flags */, + uniqueId); + + mDisplayManager.performTraversalInTransactionFromWindowManagerInternal(); + + // flush the handler + mHandler.runWithScissors(() -> {}, 0 /* now */); + + ArgumentCaptor> virtualViewportCaptor = + ArgumentCaptor.forClass(List.class); + verify(mMockInputManagerInternal).setDisplayViewports( + any(), any(), virtualViewportCaptor.capture()); + + assertEquals(1, virtualViewportCaptor.getValue().size()); + DisplayViewport dv = virtualViewportCaptor.getValue().get(0); + assertEquals(height, dv.deviceHeight); + assertEquals(width, dv.deviceWidth); + assertEquals(uniqueIdPrefix + uniqueId, dv.uniqueId); + assertEquals(displayId, dv.displayId); + } +}