Loading services/core/java/com/android/server/display/DisplayManagerService.java +75 −34 Original line number Diff line number Diff line Loading @@ -684,8 +684,9 @@ public final class DisplayManagerService extends SystemService { final var backupManager = new BackupManager(mContext); Consumer<Pair<DisplayTopology, DisplayTopologyGraph>> topologyChangedCallback = update -> { if (mInputManagerInternal != null) { mInputManagerInternal.setDisplayTopology(update.second); DisplayTopologyGraph graph = update.second; if (mInputManagerInternal != null && graph != null) { mInputManagerInternal.setDisplayTopology(graph); } deliverTopologyUpdate(update.first); }; Loading Loading @@ -3647,7 +3648,7 @@ public final class DisplayManagerService extends SystemService { private void deliverTopologyUpdate(DisplayTopology topology) { if (DEBUG) { Slog.d(TAG, "Delivering topology update"); Slog.d(TAG, "Delivering topology update: " + topology); } if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) { Trace.instant(Trace.TRACE_TAG_POWER, "deliverTopologyUpdate"); Loading Loading @@ -4209,13 +4210,18 @@ public final class DisplayManagerService extends SystemService { public boolean mWifiDisplayScanRequested; // A single pending event. // A single pending display event. private record Event(int displayId, @DisplayEvent int event) { }; // The list of pending events. This is null until there is a pending event to be saved. // This is only used if {@link deferDisplayEventsWhenFrozen()} is true. // The list of pending display events. This is null until there is a pending event to be // saved. This is only used if {@link deferDisplayEventsWhenFrozen()} is true. @GuardedBy("mCallback") private ArrayList<Event> mPendingEvents; @Nullable private ArrayList<Event> mPendingDisplayEvents; @GuardedBy("mCallback") @Nullable private DisplayTopology mPendingTopology; // Process states: a process is ready to receive events if it is neither cached nor // frozen. Loading Loading @@ -4285,7 +4291,10 @@ public final class DisplayManagerService extends SystemService { */ @GuardedBy("mCallback") private boolean hasPendingAndIsReadyLocked() { return isReadyLocked() && mPendingEvents != null && !mPendingEvents.isEmpty() && mAlive; boolean pendingDisplayEvents = mPendingDisplayEvents != null && !mPendingDisplayEvents.isEmpty(); boolean pendingTopology = mPendingTopology != null; return isReadyLocked() && (pendingDisplayEvents || pendingTopology) && mAlive; } /** Loading Loading @@ -4366,7 +4375,8 @@ public final class DisplayManagerService extends SystemService { // occurs as the client is transitioning to ready but pending events have not // been dispatched. The new event must be added to the pending list to // preserve event ordering. if (!isReadyLocked() || (mPendingEvents != null && !mPendingEvents.isEmpty())) { if (!isReadyLocked() || (mPendingDisplayEvents != null && !mPendingDisplayEvents.isEmpty())) { // The client is interested in the event but is not ready to receive it. // Put the event on the pending list. addDisplayEvent(displayId, event); Loading Loading @@ -4453,13 +4463,13 @@ public final class DisplayManagerService extends SystemService { // This is only used if {@link deferDisplayEventsWhenFrozen()} is true. @GuardedBy("mCallback") private void addDisplayEvent(int displayId, int event) { if (mPendingEvents == null) { mPendingEvents = new ArrayList<>(); if (mPendingDisplayEvents == null) { mPendingDisplayEvents = new ArrayList<>(); } if (!mPendingEvents.isEmpty()) { if (!mPendingDisplayEvents.isEmpty()) { // Ignore redundant events. Further optimization is possible by merging adjacent // events. Event last = mPendingEvents.get(mPendingEvents.size() - 1); Event last = mPendingDisplayEvents.get(mPendingDisplayEvents.size() - 1); if (last.displayId == displayId && last.event == event) { if (DEBUG) { Slog.d(TAG, "Ignore redundant display event " + displayId + "/" + event Loading @@ -4468,12 +4478,13 @@ public final class DisplayManagerService extends SystemService { return; } } mPendingEvents.add(new Event(displayId, event)); mPendingDisplayEvents.add(new Event(displayId, event)); } /** * @return {@code false} if RemoteException happens; otherwise {@code true} for * success. * success. This returns true even if the update was deferred because the remote client is * cached or frozen. */ boolean notifyTopologyUpdateAsync(DisplayTopology topology) { if ((mInternalEventFlagsMask.get() Loading @@ -4490,6 +4501,18 @@ public final class DisplayManagerService extends SystemService { // The client is not interested in this event, so do nothing. return true; } if (deferDisplayEventsWhenFrozen()) { synchronized (mCallback) { // Save the new update if the client frozen or cached (not ready). if (!isReadyLocked()) { // The client is interested in the update but is not ready to receive it. mPendingTopology = topology; return true; } } } return transmitTopologyUpdate(topology); } Loading @@ -4514,21 +4537,29 @@ public final class DisplayManagerService extends SystemService { // would be unusual to do so. The method returns true on success. // This is only used if {@link deferDisplayEventsWhenFrozen()} is true. public boolean dispatchPending() { Event[] pending; Event[] pendingDisplayEvents = null; DisplayTopology pendingTopology; synchronized (mCallback) { if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) { if (!mAlive) { return true; } if (!isReadyLocked()) { return false; } pending = new Event[mPendingEvents.size()]; pending = mPendingEvents.toArray(pending); mPendingEvents.clear(); if (mPendingDisplayEvents != null && !mPendingDisplayEvents.isEmpty()) { pendingDisplayEvents = new Event[mPendingDisplayEvents.size()]; pendingDisplayEvents = mPendingDisplayEvents.toArray(pendingDisplayEvents); mPendingDisplayEvents.clear(); } pendingTopology = mPendingTopology; mPendingTopology = null; } try { for (int i = 0; i < pending.length; i++) { Event displayEvent = pending[i]; if (pendingDisplayEvents != null) { for (int i = 0; i < pendingDisplayEvents.length; i++) { Event displayEvent = pendingDisplayEvents[i]; if (DEBUG) { Slog.d(TAG, "Send pending display event #" + i + " " + displayEvent.displayId + "/" Loading @@ -4541,10 +4572,19 @@ public final class DisplayManagerService extends SystemService { transmitDisplayEvent(displayEvent.displayId, displayEvent.event); } } if (pendingTopology != null) { if (DEBUG) { Slog.d(TAG, "Send pending topology: " + pendingTopology + " to " + mUid + "/" + mPid); } mCallback.onTopologyChanged(pendingTopology); } return true; } catch (RemoteException ex) { Slog.w(TAG, "Failed to notify process " + mPid + " that display topology changed, assuming it died.", ex); Slog.w(TAG, "Failed to notify process " + mPid + ", assuming it died.", ex); binderDied(); return false; Loading @@ -4556,11 +4596,12 @@ public final class DisplayManagerService extends SystemService { if (deferDisplayEventsWhenFrozen()) { final String fmt = "mPid=%d mUid=%d mWifiDisplayScanRequested=%s" + " cached=%s frozen=%s pending=%d"; + " cached=%s frozen=%s pendingDisplayEvents=%d pendingTopology=%b"; synchronized (mCallback) { return formatSimple(fmt, mPid, mUid, mWifiDisplayScanRequested, mCached, mFrozen, (mPendingEvents == null) ? 0 : mPendingEvents.size()); (mPendingDisplayEvents == null) ? 0 : mPendingDisplayEvents.size(), mPendingTopology != null); } } else { final String fmt = Loading services/tests/displayservicetests/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ android_test { data: [ ":DisplayManagerTestApp", ":TopologyTestApp", ], certificate: "platform", Loading services/tests/displayservicetests/AndroidManifest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <uses-permission android:name="android.permission.MANAGE_USB" /> <uses-permission android:name="android.permission.MANAGE_DISPLAYS" /> <!-- Permissions needed for DisplayTransformManagerTest --> <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> Loading services/tests/displayservicetests/AndroidTest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ <option name="cleanup-apks" value="true" /> <option name="install-arg" value="-t" /> <option name="test-file-name" value="DisplayManagerTestApp.apk" /> <option name="test-file-name" value="TopologyTestApp.apk" /> </target_preparer> <option name="test-tag" value="DisplayServiceTests" /> Loading services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java +32 −212 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ package com.android.server.display; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; import static android.util.DisplayMetrics.DENSITY_HIGH; Loading @@ -27,19 +26,11 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; import android.app.ActivityManager; import android.app.Instrumentation; import android.content.Context; import android.content.Intent; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.os.BinderProxy; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.platform.test.annotations.AppModeSdkSandbox; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; Loading @@ -48,10 +39,7 @@ import android.util.SparseArray; import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.test.platform.app.InstrumentationRegistry; import com.android.compatibility.common.util.SystemUtil; import com.android.compatibility.common.util.TestUtils; import com.android.server.am.Flags; import org.junit.After; Loading @@ -63,9 +51,7 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; import java.io.IOException; import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; Loading @@ -73,8 +59,7 @@ import java.util.concurrent.TimeUnit; * Tests that applications can receive display events correctly. */ @RunWith(Parameterized.class) @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).") public class DisplayEventDeliveryTest { public class DisplayEventDeliveryTest extends EventDeliveryTestBase { private static final String TAG = "DisplayEventDeliveryTest"; @Rule Loading @@ -85,37 +70,17 @@ public class DisplayEventDeliveryTest { private static final int WIDTH = 720; private static final int HEIGHT = 480; private static final int MESSAGE_LAUNCHED = 1; private static final int MESSAGE_CALLBACK = 2; private static final int DISPLAY_ADDED = 1; private static final int DISPLAY_CHANGED = 2; private static final int DISPLAY_REMOVED = 3; private static final long DISPLAY_EVENT_TIMEOUT_MSEC = 100; private static final long TEST_FAILURE_TIMEOUT_MSEC = 10000; private static final String TEST_PACKAGE = "com.android.servicestests.apps.displaymanagertestapp"; private static final String TEST_ACTIVITY = TEST_PACKAGE + ".DisplayEventActivity"; private static final String TEST_DISPLAYS = "DISPLAYS"; private static final String TEST_MESSENGER = "MESSENGER"; private final Object mLock = new Object(); private Instrumentation mInstrumentation; private Context mContext; private DisplayManager mDisplayManager; private ActivityManager mActivityManager; private ActivityManager.OnUidImportanceListener mUidImportanceListener; private CountDownLatch mLatchActivityLaunch; private CountDownLatch mLatchActivityCached; private HandlerThread mHandlerThread; private Handler mHandler; private Messenger mMessenger; private int mPid; private int mUid; /** * Array of DisplayBundle. The test handler uses it to check if certain display events have * been sent to DisplayEventActivity. Loading Loading @@ -167,7 +132,7 @@ public class DisplayEventDeliveryTest { */ public void assertNoDisplayEvents() { try { assertNull(mExpectations.poll(DISPLAY_EVENT_TIMEOUT_MSEC, TimeUnit.MILLISECONDS)); assertNull(mExpectations.poll(EVENT_TIMEOUT_MSEC, TimeUnit.MILLISECONDS)); } catch (InterruptedException e) { throw new RuntimeException(e); } Loading Loading @@ -239,37 +204,17 @@ public class DisplayEventDeliveryTest { } @Before public void setUp() throws Exception { mInstrumentation = InstrumentationRegistry.getInstrumentation(); mContext = mInstrumentation.getContext(); mDisplayManager = mContext.getSystemService(DisplayManager.class); mLatchActivityLaunch = new CountDownLatch(1); mLatchActivityCached = new CountDownLatch(1); mActivityManager = mContext.getSystemService(ActivityManager.class); mUidImportanceListener = (uid, importance) -> { if (uid == mUid && importance == IMPORTANCE_CACHED) { Log.d(TAG, "Listener " + uid + " becomes " + importance); mLatchActivityCached.countDown(); } }; SystemUtil.runWithShellPermissionIdentity(() -> mActivityManager.addOnUidImportanceListener(mUidImportanceListener, IMPORTANCE_CACHED)); public void setUp() { super.setUp(); // The lock is not functionally necessary but eliminates lint error messages. synchronized (mLock) { mDisplayBundles = new SparseArray<>(); } mHandlerThread = new HandlerThread("handler"); mHandlerThread.start(); mHandler = new TestHandler(mHandlerThread.getLooper()); mMessenger = new Messenger(mHandler); mPid = 0; } @After public void tearDown() throws Exception { mActivityManager.removeOnUidImportanceListener(mUidImportanceListener); mHandlerThread.quitSafely(); super.tearDown(); synchronized (mLock) { for (int i = 0; i < mDisplayBundles.size(); i++) { DisplayBundle bundle = mDisplayBundles.valueAt(i); Loading @@ -278,55 +223,45 @@ public class DisplayEventDeliveryTest { } mDisplayBundles.clear(); } SystemUtil.runShellCommand(mInstrumentation, "am force-stop " + TEST_PACKAGE); } /** * Return a display bundle at the stated index. The bundle is retrieved under lock. */ private DisplayBundle displayBundleAt(int i) { synchronized (mLock) { return mDisplayBundles.valueAt(i); @Override protected String getTag() { return TAG; } @Override protected Handler getHandler(Looper looper) { return new TestHandler(looper); } /** * Return true if the freezer is enabled on this platform and if freezer notifications are * supported. It is not enough to test that the freezer notification feature is enabled * because some devices do not have the necessary kernel support. */ private boolean isAppFreezerEnabled() { try { return mActivityManager.getService().isAppFreezerEnabled() && android.os.Flags.binderFrozenStateChangeCallback() && BinderProxy.isFrozenStateChangeCallbackSupported(); } catch (Exception e) { Log.e(TAG, "isAppFreezerEnabled() failed: " + e); return false; @Override protected String getTestPackage() { return TEST_PACKAGE; } @Override protected String getTestActivity() { return TEST_ACTIVITY; } private void waitForProcessFreeze(int pid, long timeoutMs) { // TODO: Add a listener to monitor freezer state changes. SystemUtil.runWithShellPermissionIdentity(() -> { TestUtils.waitUntil("Timed out waiting for test process to be frozen; pid=" + pid, (int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs), () -> mActivityManager.isProcessFrozen(pid)); }); @Override protected void putExtra(Intent intent) { intent.putExtra(TEST_DISPLAYS, mDisplayCount); } private void waitForProcessUnfreeze(int pid, long timeoutMs) { // TODO: Add a listener to monitor freezer state changes. SystemUtil.runWithShellPermissionIdentity(() -> { TestUtils.waitUntil("Timed out waiting for test process to be frozen; pid=" + pid, (int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs), () -> !mActivityManager.isProcessFrozen(pid)); }); /** * Return a display bundle at the stated index. The bundle is retrieved under lock. */ private DisplayBundle displayBundleAt(int i) { synchronized (mLock) { return mDisplayBundles.valueAt(i); } } /** * Create virtual displays, change their configurations and release them. The number of * displays is set by the {@link #mDisplays} variable. * displays is set by the {@link #data()} parameter. */ private void testDisplayEventsInternal(boolean cached, boolean frozen) { Log.d(TAG, "Start test testDisplayEvents " + mDisplayCount + " " + cached + " " + frozen); Loading Loading @@ -444,110 +379,6 @@ public class DisplayEventDeliveryTest { testDisplayEventsInternal(true, true); } /** * Launch the test activity that would listen to display events. Return its process ID. */ private int launchTestActivity() { Intent intent = new Intent(Intent.ACTION_MAIN); intent.setClassName(TEST_PACKAGE, TEST_ACTIVITY); intent.putExtra(TEST_MESSENGER, mMessenger); intent.putExtra(TEST_DISPLAYS, mDisplayCount); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); SystemUtil.runWithShellPermissionIdentity( () -> { mContext.startActivity(intent); }, android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX); waitLatch(mLatchActivityLaunch); try { String cmd = "pidof " + TEST_PACKAGE; String result = SystemUtil.runShellCommand(mInstrumentation, cmd); return Integer.parseInt(result.trim()); } catch (IOException e) { fail("failed to get pid of test package"); return 0; } catch (NumberFormatException e) { fail("failed to parse pid " + e); return 0; } } /** * Bring the test activity back to top */ private void bringTestActivityTop() { Intent intent = new Intent(Intent.ACTION_MAIN); intent.setClassName(TEST_PACKAGE, TEST_ACTIVITY); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); SystemUtil.runWithShellPermissionIdentity( () -> { mContext.startActivity(intent); }, android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX); } /** * Bring the test activity into cached mode by launching another 2 apps */ private void makeTestActivityCached() { // Launch another activity to bring the test activity into background Intent intent = new Intent(Intent.ACTION_MAIN); intent.setClass(mContext, SimpleActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); // Launch another activity to bring the test activity into cached mode Intent intent2 = new Intent(Intent.ACTION_MAIN); intent2.setClass(mContext, SimpleActivity2.class); intent2.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); SystemUtil.runWithShellPermissionIdentity( () -> { mInstrumentation.startActivitySync(intent); mInstrumentation.startActivitySync(intent2); }, android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX); waitLatch(mLatchActivityCached); } // Sleep, ignoring interrupts. private void pause(int s) { try { Thread.sleep(s * 1000); } catch (Exception e) { } } /** * Freeze the test activity. */ private void makeTestActivityFrozen(int pid) { // The delay here is meant to allow pending binder transactions to drain. A process // cannot be frozen if it has pending binder transactions, and attempting to freeze such a // process more than a few times will result in the system killing the process. pause(5); try { String cmd = "am freeze --sticky "; SystemUtil.runShellCommand(mInstrumentation, cmd + TEST_PACKAGE); } catch (IOException e) { fail(e.toString()); } // Wait for the freeze to complete in the kernel and for the frozen process // notification to settle out. waitForProcessFreeze(pid, 5 * 1000); } /** * Freeze the test activity. */ private void makeTestActivityUnfrozen(int pid) { try { String cmd = "am unfreeze --sticky "; SystemUtil.runShellCommand(mInstrumentation, cmd + TEST_PACKAGE); } catch (IOException e) { fail(e.toString()); } // Wait for the freeze to complete in the kernel and for the frozen process // notification to settle out. waitForProcessUnfreeze(pid, 5 * 1000); } /** * Create a virtual display * Loading @@ -560,15 +391,4 @@ public class DisplayEventDeliveryTest { VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY /* flags: a public virtual display that another app can access */); } /** * Wait for CountDownLatch with timeout */ private void waitLatch(CountDownLatch latch) { try { latch.await(TEST_FAILURE_TIMEOUT_MSEC, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); } } } Loading
services/core/java/com/android/server/display/DisplayManagerService.java +75 −34 Original line number Diff line number Diff line Loading @@ -684,8 +684,9 @@ public final class DisplayManagerService extends SystemService { final var backupManager = new BackupManager(mContext); Consumer<Pair<DisplayTopology, DisplayTopologyGraph>> topologyChangedCallback = update -> { if (mInputManagerInternal != null) { mInputManagerInternal.setDisplayTopology(update.second); DisplayTopologyGraph graph = update.second; if (mInputManagerInternal != null && graph != null) { mInputManagerInternal.setDisplayTopology(graph); } deliverTopologyUpdate(update.first); }; Loading Loading @@ -3647,7 +3648,7 @@ public final class DisplayManagerService extends SystemService { private void deliverTopologyUpdate(DisplayTopology topology) { if (DEBUG) { Slog.d(TAG, "Delivering topology update"); Slog.d(TAG, "Delivering topology update: " + topology); } if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) { Trace.instant(Trace.TRACE_TAG_POWER, "deliverTopologyUpdate"); Loading Loading @@ -4209,13 +4210,18 @@ public final class DisplayManagerService extends SystemService { public boolean mWifiDisplayScanRequested; // A single pending event. // A single pending display event. private record Event(int displayId, @DisplayEvent int event) { }; // The list of pending events. This is null until there is a pending event to be saved. // This is only used if {@link deferDisplayEventsWhenFrozen()} is true. // The list of pending display events. This is null until there is a pending event to be // saved. This is only used if {@link deferDisplayEventsWhenFrozen()} is true. @GuardedBy("mCallback") private ArrayList<Event> mPendingEvents; @Nullable private ArrayList<Event> mPendingDisplayEvents; @GuardedBy("mCallback") @Nullable private DisplayTopology mPendingTopology; // Process states: a process is ready to receive events if it is neither cached nor // frozen. Loading Loading @@ -4285,7 +4291,10 @@ public final class DisplayManagerService extends SystemService { */ @GuardedBy("mCallback") private boolean hasPendingAndIsReadyLocked() { return isReadyLocked() && mPendingEvents != null && !mPendingEvents.isEmpty() && mAlive; boolean pendingDisplayEvents = mPendingDisplayEvents != null && !mPendingDisplayEvents.isEmpty(); boolean pendingTopology = mPendingTopology != null; return isReadyLocked() && (pendingDisplayEvents || pendingTopology) && mAlive; } /** Loading Loading @@ -4366,7 +4375,8 @@ public final class DisplayManagerService extends SystemService { // occurs as the client is transitioning to ready but pending events have not // been dispatched. The new event must be added to the pending list to // preserve event ordering. if (!isReadyLocked() || (mPendingEvents != null && !mPendingEvents.isEmpty())) { if (!isReadyLocked() || (mPendingDisplayEvents != null && !mPendingDisplayEvents.isEmpty())) { // The client is interested in the event but is not ready to receive it. // Put the event on the pending list. addDisplayEvent(displayId, event); Loading Loading @@ -4453,13 +4463,13 @@ public final class DisplayManagerService extends SystemService { // This is only used if {@link deferDisplayEventsWhenFrozen()} is true. @GuardedBy("mCallback") private void addDisplayEvent(int displayId, int event) { if (mPendingEvents == null) { mPendingEvents = new ArrayList<>(); if (mPendingDisplayEvents == null) { mPendingDisplayEvents = new ArrayList<>(); } if (!mPendingEvents.isEmpty()) { if (!mPendingDisplayEvents.isEmpty()) { // Ignore redundant events. Further optimization is possible by merging adjacent // events. Event last = mPendingEvents.get(mPendingEvents.size() - 1); Event last = mPendingDisplayEvents.get(mPendingDisplayEvents.size() - 1); if (last.displayId == displayId && last.event == event) { if (DEBUG) { Slog.d(TAG, "Ignore redundant display event " + displayId + "/" + event Loading @@ -4468,12 +4478,13 @@ public final class DisplayManagerService extends SystemService { return; } } mPendingEvents.add(new Event(displayId, event)); mPendingDisplayEvents.add(new Event(displayId, event)); } /** * @return {@code false} if RemoteException happens; otherwise {@code true} for * success. * success. This returns true even if the update was deferred because the remote client is * cached or frozen. */ boolean notifyTopologyUpdateAsync(DisplayTopology topology) { if ((mInternalEventFlagsMask.get() Loading @@ -4490,6 +4501,18 @@ public final class DisplayManagerService extends SystemService { // The client is not interested in this event, so do nothing. return true; } if (deferDisplayEventsWhenFrozen()) { synchronized (mCallback) { // Save the new update if the client frozen or cached (not ready). if (!isReadyLocked()) { // The client is interested in the update but is not ready to receive it. mPendingTopology = topology; return true; } } } return transmitTopologyUpdate(topology); } Loading @@ -4514,21 +4537,29 @@ public final class DisplayManagerService extends SystemService { // would be unusual to do so. The method returns true on success. // This is only used if {@link deferDisplayEventsWhenFrozen()} is true. public boolean dispatchPending() { Event[] pending; Event[] pendingDisplayEvents = null; DisplayTopology pendingTopology; synchronized (mCallback) { if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) { if (!mAlive) { return true; } if (!isReadyLocked()) { return false; } pending = new Event[mPendingEvents.size()]; pending = mPendingEvents.toArray(pending); mPendingEvents.clear(); if (mPendingDisplayEvents != null && !mPendingDisplayEvents.isEmpty()) { pendingDisplayEvents = new Event[mPendingDisplayEvents.size()]; pendingDisplayEvents = mPendingDisplayEvents.toArray(pendingDisplayEvents); mPendingDisplayEvents.clear(); } pendingTopology = mPendingTopology; mPendingTopology = null; } try { for (int i = 0; i < pending.length; i++) { Event displayEvent = pending[i]; if (pendingDisplayEvents != null) { for (int i = 0; i < pendingDisplayEvents.length; i++) { Event displayEvent = pendingDisplayEvents[i]; if (DEBUG) { Slog.d(TAG, "Send pending display event #" + i + " " + displayEvent.displayId + "/" Loading @@ -4541,10 +4572,19 @@ public final class DisplayManagerService extends SystemService { transmitDisplayEvent(displayEvent.displayId, displayEvent.event); } } if (pendingTopology != null) { if (DEBUG) { Slog.d(TAG, "Send pending topology: " + pendingTopology + " to " + mUid + "/" + mPid); } mCallback.onTopologyChanged(pendingTopology); } return true; } catch (RemoteException ex) { Slog.w(TAG, "Failed to notify process " + mPid + " that display topology changed, assuming it died.", ex); Slog.w(TAG, "Failed to notify process " + mPid + ", assuming it died.", ex); binderDied(); return false; Loading @@ -4556,11 +4596,12 @@ public final class DisplayManagerService extends SystemService { if (deferDisplayEventsWhenFrozen()) { final String fmt = "mPid=%d mUid=%d mWifiDisplayScanRequested=%s" + " cached=%s frozen=%s pending=%d"; + " cached=%s frozen=%s pendingDisplayEvents=%d pendingTopology=%b"; synchronized (mCallback) { return formatSimple(fmt, mPid, mUid, mWifiDisplayScanRequested, mCached, mFrozen, (mPendingEvents == null) ? 0 : mPendingEvents.size()); (mPendingDisplayEvents == null) ? 0 : mPendingDisplayEvents.size(), mPendingTopology != null); } } else { final String fmt = Loading
services/tests/displayservicetests/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ android_test { data: [ ":DisplayManagerTestApp", ":TopologyTestApp", ], certificate: "platform", Loading
services/tests/displayservicetests/AndroidManifest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <uses-permission android:name="android.permission.MANAGE_USB" /> <uses-permission android:name="android.permission.MANAGE_DISPLAYS" /> <!-- Permissions needed for DisplayTransformManagerTest --> <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> Loading
services/tests/displayservicetests/AndroidTest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ <option name="cleanup-apks" value="true" /> <option name="install-arg" value="-t" /> <option name="test-file-name" value="DisplayManagerTestApp.apk" /> <option name="test-file-name" value="TopologyTestApp.apk" /> </target_preparer> <option name="test-tag" value="DisplayServiceTests" /> Loading
services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java +32 −212 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ package com.android.server.display; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; import static android.util.DisplayMetrics.DENSITY_HIGH; Loading @@ -27,19 +26,11 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; import android.app.ActivityManager; import android.app.Instrumentation; import android.content.Context; import android.content.Intent; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.os.BinderProxy; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.platform.test.annotations.AppModeSdkSandbox; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; Loading @@ -48,10 +39,7 @@ import android.util.SparseArray; import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.test.platform.app.InstrumentationRegistry; import com.android.compatibility.common.util.SystemUtil; import com.android.compatibility.common.util.TestUtils; import com.android.server.am.Flags; import org.junit.After; Loading @@ -63,9 +51,7 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; import java.io.IOException; import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; Loading @@ -73,8 +59,7 @@ import java.util.concurrent.TimeUnit; * Tests that applications can receive display events correctly. */ @RunWith(Parameterized.class) @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).") public class DisplayEventDeliveryTest { public class DisplayEventDeliveryTest extends EventDeliveryTestBase { private static final String TAG = "DisplayEventDeliveryTest"; @Rule Loading @@ -85,37 +70,17 @@ public class DisplayEventDeliveryTest { private static final int WIDTH = 720; private static final int HEIGHT = 480; private static final int MESSAGE_LAUNCHED = 1; private static final int MESSAGE_CALLBACK = 2; private static final int DISPLAY_ADDED = 1; private static final int DISPLAY_CHANGED = 2; private static final int DISPLAY_REMOVED = 3; private static final long DISPLAY_EVENT_TIMEOUT_MSEC = 100; private static final long TEST_FAILURE_TIMEOUT_MSEC = 10000; private static final String TEST_PACKAGE = "com.android.servicestests.apps.displaymanagertestapp"; private static final String TEST_ACTIVITY = TEST_PACKAGE + ".DisplayEventActivity"; private static final String TEST_DISPLAYS = "DISPLAYS"; private static final String TEST_MESSENGER = "MESSENGER"; private final Object mLock = new Object(); private Instrumentation mInstrumentation; private Context mContext; private DisplayManager mDisplayManager; private ActivityManager mActivityManager; private ActivityManager.OnUidImportanceListener mUidImportanceListener; private CountDownLatch mLatchActivityLaunch; private CountDownLatch mLatchActivityCached; private HandlerThread mHandlerThread; private Handler mHandler; private Messenger mMessenger; private int mPid; private int mUid; /** * Array of DisplayBundle. The test handler uses it to check if certain display events have * been sent to DisplayEventActivity. Loading Loading @@ -167,7 +132,7 @@ public class DisplayEventDeliveryTest { */ public void assertNoDisplayEvents() { try { assertNull(mExpectations.poll(DISPLAY_EVENT_TIMEOUT_MSEC, TimeUnit.MILLISECONDS)); assertNull(mExpectations.poll(EVENT_TIMEOUT_MSEC, TimeUnit.MILLISECONDS)); } catch (InterruptedException e) { throw new RuntimeException(e); } Loading Loading @@ -239,37 +204,17 @@ public class DisplayEventDeliveryTest { } @Before public void setUp() throws Exception { mInstrumentation = InstrumentationRegistry.getInstrumentation(); mContext = mInstrumentation.getContext(); mDisplayManager = mContext.getSystemService(DisplayManager.class); mLatchActivityLaunch = new CountDownLatch(1); mLatchActivityCached = new CountDownLatch(1); mActivityManager = mContext.getSystemService(ActivityManager.class); mUidImportanceListener = (uid, importance) -> { if (uid == mUid && importance == IMPORTANCE_CACHED) { Log.d(TAG, "Listener " + uid + " becomes " + importance); mLatchActivityCached.countDown(); } }; SystemUtil.runWithShellPermissionIdentity(() -> mActivityManager.addOnUidImportanceListener(mUidImportanceListener, IMPORTANCE_CACHED)); public void setUp() { super.setUp(); // The lock is not functionally necessary but eliminates lint error messages. synchronized (mLock) { mDisplayBundles = new SparseArray<>(); } mHandlerThread = new HandlerThread("handler"); mHandlerThread.start(); mHandler = new TestHandler(mHandlerThread.getLooper()); mMessenger = new Messenger(mHandler); mPid = 0; } @After public void tearDown() throws Exception { mActivityManager.removeOnUidImportanceListener(mUidImportanceListener); mHandlerThread.quitSafely(); super.tearDown(); synchronized (mLock) { for (int i = 0; i < mDisplayBundles.size(); i++) { DisplayBundle bundle = mDisplayBundles.valueAt(i); Loading @@ -278,55 +223,45 @@ public class DisplayEventDeliveryTest { } mDisplayBundles.clear(); } SystemUtil.runShellCommand(mInstrumentation, "am force-stop " + TEST_PACKAGE); } /** * Return a display bundle at the stated index. The bundle is retrieved under lock. */ private DisplayBundle displayBundleAt(int i) { synchronized (mLock) { return mDisplayBundles.valueAt(i); @Override protected String getTag() { return TAG; } @Override protected Handler getHandler(Looper looper) { return new TestHandler(looper); } /** * Return true if the freezer is enabled on this platform and if freezer notifications are * supported. It is not enough to test that the freezer notification feature is enabled * because some devices do not have the necessary kernel support. */ private boolean isAppFreezerEnabled() { try { return mActivityManager.getService().isAppFreezerEnabled() && android.os.Flags.binderFrozenStateChangeCallback() && BinderProxy.isFrozenStateChangeCallbackSupported(); } catch (Exception e) { Log.e(TAG, "isAppFreezerEnabled() failed: " + e); return false; @Override protected String getTestPackage() { return TEST_PACKAGE; } @Override protected String getTestActivity() { return TEST_ACTIVITY; } private void waitForProcessFreeze(int pid, long timeoutMs) { // TODO: Add a listener to monitor freezer state changes. SystemUtil.runWithShellPermissionIdentity(() -> { TestUtils.waitUntil("Timed out waiting for test process to be frozen; pid=" + pid, (int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs), () -> mActivityManager.isProcessFrozen(pid)); }); @Override protected void putExtra(Intent intent) { intent.putExtra(TEST_DISPLAYS, mDisplayCount); } private void waitForProcessUnfreeze(int pid, long timeoutMs) { // TODO: Add a listener to monitor freezer state changes. SystemUtil.runWithShellPermissionIdentity(() -> { TestUtils.waitUntil("Timed out waiting for test process to be frozen; pid=" + pid, (int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs), () -> !mActivityManager.isProcessFrozen(pid)); }); /** * Return a display bundle at the stated index. The bundle is retrieved under lock. */ private DisplayBundle displayBundleAt(int i) { synchronized (mLock) { return mDisplayBundles.valueAt(i); } } /** * Create virtual displays, change their configurations and release them. The number of * displays is set by the {@link #mDisplays} variable. * displays is set by the {@link #data()} parameter. */ private void testDisplayEventsInternal(boolean cached, boolean frozen) { Log.d(TAG, "Start test testDisplayEvents " + mDisplayCount + " " + cached + " " + frozen); Loading Loading @@ -444,110 +379,6 @@ public class DisplayEventDeliveryTest { testDisplayEventsInternal(true, true); } /** * Launch the test activity that would listen to display events. Return its process ID. */ private int launchTestActivity() { Intent intent = new Intent(Intent.ACTION_MAIN); intent.setClassName(TEST_PACKAGE, TEST_ACTIVITY); intent.putExtra(TEST_MESSENGER, mMessenger); intent.putExtra(TEST_DISPLAYS, mDisplayCount); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); SystemUtil.runWithShellPermissionIdentity( () -> { mContext.startActivity(intent); }, android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX); waitLatch(mLatchActivityLaunch); try { String cmd = "pidof " + TEST_PACKAGE; String result = SystemUtil.runShellCommand(mInstrumentation, cmd); return Integer.parseInt(result.trim()); } catch (IOException e) { fail("failed to get pid of test package"); return 0; } catch (NumberFormatException e) { fail("failed to parse pid " + e); return 0; } } /** * Bring the test activity back to top */ private void bringTestActivityTop() { Intent intent = new Intent(Intent.ACTION_MAIN); intent.setClassName(TEST_PACKAGE, TEST_ACTIVITY); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); SystemUtil.runWithShellPermissionIdentity( () -> { mContext.startActivity(intent); }, android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX); } /** * Bring the test activity into cached mode by launching another 2 apps */ private void makeTestActivityCached() { // Launch another activity to bring the test activity into background Intent intent = new Intent(Intent.ACTION_MAIN); intent.setClass(mContext, SimpleActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); // Launch another activity to bring the test activity into cached mode Intent intent2 = new Intent(Intent.ACTION_MAIN); intent2.setClass(mContext, SimpleActivity2.class); intent2.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); SystemUtil.runWithShellPermissionIdentity( () -> { mInstrumentation.startActivitySync(intent); mInstrumentation.startActivitySync(intent2); }, android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX); waitLatch(mLatchActivityCached); } // Sleep, ignoring interrupts. private void pause(int s) { try { Thread.sleep(s * 1000); } catch (Exception e) { } } /** * Freeze the test activity. */ private void makeTestActivityFrozen(int pid) { // The delay here is meant to allow pending binder transactions to drain. A process // cannot be frozen if it has pending binder transactions, and attempting to freeze such a // process more than a few times will result in the system killing the process. pause(5); try { String cmd = "am freeze --sticky "; SystemUtil.runShellCommand(mInstrumentation, cmd + TEST_PACKAGE); } catch (IOException e) { fail(e.toString()); } // Wait for the freeze to complete in the kernel and for the frozen process // notification to settle out. waitForProcessFreeze(pid, 5 * 1000); } /** * Freeze the test activity. */ private void makeTestActivityUnfrozen(int pid) { try { String cmd = "am unfreeze --sticky "; SystemUtil.runShellCommand(mInstrumentation, cmd + TEST_PACKAGE); } catch (IOException e) { fail(e.toString()); } // Wait for the freeze to complete in the kernel and for the frozen process // notification to settle out. waitForProcessUnfreeze(pid, 5 * 1000); } /** * Create a virtual display * Loading @@ -560,15 +391,4 @@ public class DisplayEventDeliveryTest { VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY /* flags: a public virtual display that another app can access */); } /** * Wait for CountDownLatch with timeout */ private void waitLatch(CountDownLatch latch) { try { latch.await(TEST_FAILURE_TIMEOUT_MSEC, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); } } }