summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Lee Shombert <shombert@google.com> 2024-09-30 15:54:11 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-09-30 15:54:11 +0000
commitf4dca5254ec4d6545693787b48814ef042f08ead (patch)
tree91d7b103da07874472f25676dec961c9596a7e37
parent745ebaa43db4b3a62ba61894fda79b573f5e67f1 (diff)
parent21d6448c978c83a337f10a422d746deaaba326a4 (diff)
Merge "DisplayManagerService: use per-pid freezer listener" into main
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java298
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java188
2 files changed, 448 insertions, 38 deletions
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 1f9eb082aaf4..67e2ca2b312c 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 static android.os.Process.ROOT_UID;
import static android.provider.Settings.Secure.RESOLUTION_MODE_FULL;
import static android.provider.Settings.Secure.RESOLUTION_MODE_HIGH;
import static android.provider.Settings.Secure.RESOLUTION_MODE_UNKNOWN;
+import static android.text.TextUtils.formatSimple;
import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
import static com.android.server.display.layout.Layout.Display.POSITION_REAR;
@@ -279,6 +280,8 @@ public final class DisplayManagerService extends SystemService {
private InputManagerInternal mInputManagerInternal;
private ActivityManagerInternal mActivityManagerInternal;
private final UidImportanceListener mUidImportanceListener = new UidImportanceListener();
+ private final DisplayFrozenProcessListener mDisplayFrozenProcessListener;
+
@Nullable
private IMediaProjectionManager mProjectionService;
private DeviceStateManagerInternal mDeviceStateManager;
@@ -321,6 +324,12 @@ public final class DisplayManagerService extends SystemService {
@GuardedBy("mSyncRoot")
private final SparseArray<CallbackRecord> mCallbacks = new SparseArray<>();
+ // All callback records indexed by [uid][pid], for fast lookup by uid.
+ // This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
+ @GuardedBy("mSyncRoot")
+ private final SparseArray<SparseArray<CallbackRecord>> mCallbackRecordByPidByUid =
+ new SparseArray<>();
+
/**
* All {@link IVirtualDevice} and {@link DisplayWindowPolicyController}s indexed by
* {@link DisplayInfo#displayId}.
@@ -472,6 +481,7 @@ public final class DisplayManagerService extends SystemService {
// Pending callback records indexed by calling process uid and pid.
// Must be used outside of the lock mSyncRoot and should be self-locked.
+ // This is only used when {@link deferDisplayEventsWhenFrozen()} is false.
@GuardedBy("mPendingCallbackSelfLocked")
private final SparseArray<SparseArray<PendingCallback>> mPendingCallbackSelfLocked =
new SparseArray<>();
@@ -611,6 +621,7 @@ public final class DisplayManagerService extends SystemService {
mFlags = injector.getFlags();
mHandler = new DisplayManagerHandler(displayThreadLooper);
mHandlerExecutor = new HandlerExecutor(mHandler);
+ mDisplayFrozenProcessListener = new DisplayFrozenProcessListener();
mUiHandler = UiThread.getHandler();
mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
mLogicalDisplayMapper = new LogicalDisplayMapper(mContext,
@@ -1034,10 +1045,14 @@ public final class DisplayManagerService extends SystemService {
private class UidImportanceListener implements ActivityManager.OnUidImportanceListener {
@Override
public void onUidImportance(int uid, int importance) {
- onUidImportanceInternal(uid, importance);
+ if (deferDisplayEventsWhenFrozen()) {
+ onUidImportanceFlagged(uid, importance);
+ } else {
+ onUidImportanceUnflagged(uid, importance);
+ }
}
- private void onUidImportanceInternal(int uid, int importance) {
+ private void onUidImportanceUnflagged(int uid, int importance) {
synchronized (mPendingCallbackSelfLocked) {
if (importance >= IMPORTANCE_GONE) {
// Clean up as the app is already gone
@@ -1068,6 +1083,83 @@ public final class DisplayManagerService extends SystemService {
mPendingCallbackSelfLocked.delete(uid);
}
}
+
+ private void onUidImportanceFlagged(int uid, int importance) {
+ final boolean cached = (importance >= IMPORTANCE_CACHED);
+ List<CallbackRecord> readyCallbackRecords = null;
+ synchronized (mSyncRoot) {
+ final SparseArray<CallbackRecord> procs = mCallbackRecordByPidByUid.get(uid);
+ if (procs == null) {
+ return;
+ }
+ if (cached) {
+ setCachedLocked(procs);
+ } else {
+ readyCallbackRecords = setUncachedLocked(procs);
+ }
+ }
+ if (readyCallbackRecords != null) {
+ // Attempt to dispatch pending events if the UID is coming out of cached state.
+ for (int i = 0; i < readyCallbackRecords.size(); i++) {
+ readyCallbackRecords.get(i).dispatchPending();
+ }
+ }
+ }
+
+ // Set all processes in the list to cached.
+ @GuardedBy("mSyncRoot")
+ private void setCachedLocked(SparseArray<CallbackRecord> procs) {
+ for (int i = 0; i < procs.size(); i++) {
+ final CallbackRecord cb = procs.valueAt(i);
+ if (cb != null) {
+ cb.setCached(true);
+ }
+ }
+ }
+
+ // Set all processes to uncached and return the list of processes that were modified.
+ @GuardedBy("mSyncRoot")
+ private List<CallbackRecord> setUncachedLocked(SparseArray<CallbackRecord> procs) {
+ ArrayList<CallbackRecord> ready = null;
+ for (int i = 0; i < procs.size(); i++) {
+ final CallbackRecord cb = procs.valueAt(i);
+ if (cb != null) {
+ if (cb.setCached(false)) {
+ if (ready == null) ready = new ArrayList<>();
+ ready.add(cb);
+ }
+ }
+ }
+ return ready;
+ }
+ }
+
+ private class DisplayFrozenProcessListener
+ implements ActivityManagerInternal.FrozenProcessListener {
+ public void onProcessFrozen(int pid) {
+ synchronized (mSyncRoot) {
+ CallbackRecord callback = mCallbacks.get(pid);
+ if (callback == null) {
+ return;
+ }
+ callback.setFrozen(true);
+ }
+ }
+
+ public void onProcessUnfrozen(int pid) {
+ // First, see if there is a callback associated with this pid. If there's no
+ // callback, then there is nothing to do.
+ CallbackRecord callback;
+ synchronized (mSyncRoot) {
+ callback = mCallbacks.get(pid);
+ if (callback == null) {
+ return;
+ }
+ callback.setFrozen(false);
+ }
+ // Attempt to dispatch pending events if the process is coming out of frozen.
+ callback.dispatchPending();
+ }
}
private class SettingsObserver extends ContentObserver {
@@ -1314,12 +1406,29 @@ public final class DisplayManagerService extends SystemService {
}
mCallbacks.put(callingPid, record);
+ if (deferDisplayEventsWhenFrozen()) {
+ SparseArray<CallbackRecord> uidPeers = mCallbackRecordByPidByUid.get(record.mUid);
+ if (uidPeers == null) {
+ uidPeers = new SparseArray<CallbackRecord>();
+ mCallbackRecordByPidByUid.put(record.mUid, uidPeers);
+ }
+ uidPeers.put(record.mPid, record);
+ }
}
}
private void onCallbackDied(CallbackRecord record) {
synchronized (mSyncRoot) {
mCallbacks.remove(record.mPid);
+ if (deferDisplayEventsWhenFrozen()) {
+ SparseArray<CallbackRecord> uidPeers = mCallbackRecordByPidByUid.get(record.mUid);
+ if (uidPeers != null) {
+ uidPeers.remove(record.mPid);
+ if (uidPeers.size() == 0) {
+ mCallbackRecordByPidByUid.remove(record.mUid);
+ }
+ }
+ }
stopWifiDisplayScanLocked(record);
}
}
@@ -3296,12 +3405,16 @@ public final class DisplayManagerService extends SystemService {
// After releasing the lock, send the notifications out.
for (int i = 0; i < mTempCallbacks.size(); i++) {
CallbackRecord callbackRecord = mTempCallbacks.get(i);
- deliverEventInternal(callbackRecord, displayId, event);
+ if (deferDisplayEventsWhenFrozen()) {
+ deliverEventFlagged(callbackRecord, displayId, event);
+ } else {
+ deliverEventUnflagged(callbackRecord, displayId, event);
+ }
}
mTempCallbacks.clear();
}
- private void deliverEventInternal(CallbackRecord callbackRecord, int displayId, int event) {
+ private void deliverEventUnflagged(CallbackRecord callbackRecord, int displayId, int event) {
final int uid = callbackRecord.mUid;
final int pid = callbackRecord.mPid;
if (isUidCached(uid)) {
@@ -3330,6 +3443,10 @@ public final class DisplayManagerService extends SystemService {
}
}
+ private void deliverEventFlagged(CallbackRecord callbackRecord, int displayId, int event) {
+ callbackRecord.notifyDisplayEventAsync(displayId, event);
+ }
+
private boolean extraLogging(String packageName) {
return mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(packageName);
}
@@ -3454,9 +3571,7 @@ public final class DisplayManagerService extends SystemService {
pw.println("Callbacks: size=" + callbackCount);
pw.println("-----------------");
for (int i = 0; i < callbackCount; i++) {
- CallbackRecord callback = mCallbacks.valueAt(i);
- pw.println(" " + i + ": mPid=" + callback.mPid
- + ", mWifiDisplayScanRequested=" + callback.mWifiDisplayScanRequested);
+ pw.println(" " + i + ": " + mCallbacks.valueAt(i).dump());
}
pw.println();
@@ -3855,12 +3970,43 @@ public final class DisplayManagerService extends SystemService {
public boolean mWifiDisplayScanRequested;
+ // A single pending 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.
+ @GuardedBy("mCallback")
+ private ArrayList<Event> mPendingEvents;
+
+ // Process states: a process is ready to receive events if it is neither cached nor
+ // frozen.
+ @GuardedBy("mCallback")
+ private boolean mCached;
+ @GuardedBy("mCallback")
+ private boolean mFrozen;
+
CallbackRecord(int pid, int uid, @NonNull IDisplayManagerCallback callback,
@EventsMask long eventsMask) {
mPid = pid;
mUid = uid;
mCallback = callback;
mEventsMask = new AtomicLong(eventsMask);
+ mCached = false;
+ mFrozen = false;
+
+ if (deferDisplayEventsWhenFrozen()) {
+ // Some CallbackRecords are registered very early in system boot, before
+ // mActivityManagerInternal is initialized. If mActivityManagerInternal is null,
+ // do not register the frozen process listener. However, do verify that all such
+ // registrations are for the self pid (which can never be frozen, so the frozen
+ // process listener does not matter).
+ if (mActivityManagerInternal != null) {
+ mActivityManagerInternal.addFrozenProcessListener(pid, mHandlerExecutor,
+ mDisplayFrozenProcessListener);
+ } else if (Process.myPid() != pid) {
+ Slog.e(TAG, "DisplayListener registered too early");
+ }
+ }
String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
mPackageName = packageNames == null ? null : packageNames[0];
@@ -3870,6 +4016,46 @@ public final class DisplayManagerService extends SystemService {
mEventsMask.set(eventsMask);
}
+ /**
+ * Return true if the process can accept events.
+ */
+ @GuardedBy("mCallback")
+ private boolean isReadyLocked() {
+ return !mCached && !mFrozen;
+ }
+
+ /**
+ * Return true if the process is now ready and has pending events to be delivered.
+ */
+ @GuardedBy("mCallback")
+ private boolean hasPendingAndIsReadyLocked() {
+ return isReadyLocked() && mPendingEvents != null && !mPendingEvents.isEmpty();
+ }
+
+ /**
+ * Set the frozen flag for this process. Return true if the process is now ready to
+ * receive events and there are pending events to be delivered.
+ * This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
+ */
+ public boolean setFrozen(boolean frozen) {
+ synchronized (mCallback) {
+ mFrozen = frozen;
+ return hasPendingAndIsReadyLocked();
+ }
+ }
+
+ /**
+ * Set the cached flag for this process. Return true if the process is now ready to
+ * receive events and there are pending events to be delivered.
+ * This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
+ */
+ public boolean setCached(boolean cached) {
+ synchronized (mCallback) {
+ mCached = cached;
+ return hasPendingAndIsReadyLocked();
+ }
+ }
+
@Override
public void binderDied() {
if (DEBUG || extraLogging(mPackageName)) {
@@ -3885,7 +4071,7 @@ public final class DisplayManagerService extends SystemService {
/**
* @return {@code false} if RemoteException happens; otherwise {@code true} for
* success. This returns true even if the event was deferred because the remote client is
- * cached.
+ * cached or frozen.
*/
public boolean notifyDisplayEventAsync(int displayId, @DisplayEvent int event) {
if (!shouldSendEvent(event)) {
@@ -3902,6 +4088,22 @@ public final class DisplayManagerService extends SystemService {
return true;
}
+ if (deferDisplayEventsWhenFrozen()) {
+ synchronized (mCallback) {
+ // Add the new event to the pending list if the client frozen or cached (not
+ // ready) or if there are existing pending events. The latter condition
+ // 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())) {
+ // The client is interested in the event but is not ready to receive it.
+ // Put the event on the pending list.
+ addDisplayEvent(displayId, event);
+ return true;
+ }
+ }
+ }
+
return transmitDisplayEvent(displayId, event);
}
@@ -3948,8 +4150,81 @@ public final class DisplayManagerService extends SystemService {
return true;
}
}
+
+ // Add a single event to the pending list, possibly combining or collapsing events in the
+ // list.
+ // 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 (!mPendingEvents.isEmpty()) {
+ // Ignore redundant events. Further optimization is possible by merging adjacent
+ // events.
+ Event last = mPendingEvents.get(mPendingEvents.size() - 1);
+ if (last.displayId == displayId && last.event == event) {
+ if (DEBUG) {
+ Slog.d(TAG, "Ignore redundant display event " + displayId + "/" + event
+ + " to " + mUid + "/" + mPid);
+ }
+ return;
+ }
+ }
+ mPendingEvents.add(new Event(displayId, event));
+ }
+
+ // Send all pending events. This can safely be called if the process is not ready, but it
+ // would be unusual to do so. The method returns true on success.
+ // This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
+ public boolean dispatchPending() {
+ synchronized (mCallback) {
+ if (mPendingEvents == null || mPendingEvents.isEmpty()) {
+ return true;
+ }
+ if (!isReadyLocked()) {
+ return false;
+ }
+ for (int i = 0; i < mPendingEvents.size(); i++) {
+ Event displayEvent = mPendingEvents.get(i);
+ if (DEBUG) {
+ Slog.d(TAG, "Send pending display event #" + i + " "
+ + displayEvent.displayId + "/"
+ + displayEvent.event + " to " + mUid + "/" + mPid);
+ }
+ if (!transmitDisplayEvent(displayEvent.displayId, displayEvent.event)) {
+ Slog.d(TAG, "Drop pending events for dead process " + mPid);
+ break;
+ }
+ }
+ mPendingEvents.clear();
+ return true;
+ }
+ }
+
+ // Return a string suitable for dumpsys.
+ private String dump() {
+ if (deferDisplayEventsWhenFrozen()) {
+ final String fmt =
+ "mPid=%d mUid=%d mWifiDisplayScanRequested=%s"
+ + " cached=%s frozen=%s pending=%d";
+ synchronized (mCallback) {
+ return formatSimple(fmt,
+ mPid, mUid, mWifiDisplayScanRequested, mCached, mFrozen,
+ (mPendingEvents == null) ? 0 : mPendingEvents.size());
+ }
+ } else {
+ final String fmt =
+ "mPid=%d mUid=%d mWifiDisplayScanRequested=%s";
+ return formatSimple(fmt,
+ mPid, mUid, mWifiDisplayScanRequested);
+ }
+ }
}
+ /**
+ * This is only used if {@link deferDisplayEventsWhenFrozen()} is false.
+ */
private static final class PendingCallback {
private final CallbackRecord mCallbackRecord;
private final ArrayList<Pair<Integer, Integer>> mDisplayEvents;
@@ -5504,4 +5779,11 @@ public final class DisplayManagerService extends SystemService {
return mExternalDisplayStatsService;
}
}
+
+ /**
+ * Return the value of the pause
+ */
+ private static boolean deferDisplayEventsWhenFrozen() {
+ return com.android.server.am.Flags.deferDisplayEventsWhenFrozen();
+ }
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
index 90f62577b261..d00e2c677930 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
@@ -25,6 +25,7 @@ import static android.util.DisplayMetrics.DENSITY_MEDIUM;
import static org.junit.Assert.assertNotNull;
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;
@@ -38,6 +39,9 @@ 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;
import android.util.Log;
import android.util.SparseArray;
@@ -46,15 +50,19 @@ 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;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
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;
@@ -68,6 +76,10 @@ import java.util.concurrent.TimeUnit;
public class DisplayEventDeliveryTest {
private static final String TAG = "DisplayEventDeliveryTest";
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final String NAME = TAG;
private static final int WIDTH = 720;
private static final int HEIGHT = 480;
@@ -149,7 +161,6 @@ public class DisplayEventDeliveryTest {
mExpectations.offer(event);
}
-
/**
* Assert that there isn't any unexpected display event from the test activity
*/
@@ -189,19 +200,9 @@ public class DisplayEventDeliveryTest {
@Parameter(0)
public int mDisplayCount;
- /**
- * True if running the test activity in cached mode
- * False if running it in non-cached mode
- */
- @Parameter(1)
- public boolean mCached;
-
- @Parameters(name = "#{index}: {0} {1}")
+ @Parameters(name = "#{index}: {0}")
public static Iterable<? extends Object> data() {
- return Arrays.asList(new Object[][]{
- {1, false}, {2, false}, {3, false}, {10, false},
- {1, true}, {2, true}, {3, true}, {10, true}
- });
+ return Arrays.asList(new Object[][]{ {1}, {2}, {3}, {10} });
}
private class TestHandler extends Handler {
@@ -289,20 +290,51 @@ public class DisplayEventDeliveryTest {
}
/**
- * Create virtual displays, change their configurations and release them
- * mDisplays: the amount of virtual displays to be created
- * mCached: true to run the test activity in cached mode; false in non-cached mode
+ * Return true if the freezer is enabled on this platform.
*/
- @Test
- public void testDisplayEvents() {
- Log.d(TAG, "Start test testDisplayEvents " + mDisplayCount + " " + mCached);
+ private boolean isAppFreezerEnabled() {
+ try {
+ return mActivityManager.getService().isAppFreezerEnabled();
+ } catch (Exception e) {
+ Log.e(TAG, "isAppFreezerEnabled() failed: " + e);
+ return false;
+ }
+ }
+
+ 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));
+ });
+ }
+
+ 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));
+ });
+ }
+
+ /**
+ * Create virtual displays, change their configurations and release them. The number of
+ * displays is set by the {@link #mDisplays} variable.
+ */
+ private void testDisplayEventsInternal(boolean cached, boolean frozen) {
+ Log.d(TAG, "Start test testDisplayEvents " + mDisplayCount + " " + cached + " " + frozen);
// Launch DisplayEventActivity and start listening to display events
- launchTestActivity();
+ int pid = launchTestActivity();
- if (mCached) {
- // The test activity in cached mode won't receive the pending display events
+ // The test activity in cached or frozen mode won't receive the pending display events.
+ if (cached) {
makeTestActivityCached();
}
+ if (frozen) {
+ makeTestActivityFrozen(pid);
+ }
// Create new virtual displays
for (int i = 0; i < mDisplayCount; i++) {
@@ -315,8 +347,8 @@ public class DisplayEventDeliveryTest {
}
for (int i = 0; i < mDisplayCount; i++) {
- if (mCached) {
- // DISPLAY_ADDED should be deferred for cached process
+ if (cached || frozen) {
+ // DISPLAY_ADDED should be deferred for a cached or frozen process.
displayBundleAt(i).assertNoDisplayEvents();
} else {
// DISPLAY_ADDED should arrive immediately for non-cached process
@@ -331,8 +363,8 @@ public class DisplayEventDeliveryTest {
}
for (int i = 0; i < mDisplayCount; i++) {
- if (mCached) {
- // DISPLAY_CHANGED should be deferred for cached process
+ if (cached || frozen) {
+ // DISPLAY_CHANGED should be deferred for cached or frozen process.
displayBundleAt(i).assertNoDisplayEvents();
} else {
// DISPLAY_CHANGED should arrive immediately for non-cached process
@@ -340,10 +372,16 @@ public class DisplayEventDeliveryTest {
}
}
- if (mCached) {
- // The test activity becomes non-cached and should receive the pending display events
+ // Unfreeze the test activity, if it was frozen.
+ if (frozen) {
+ makeTestActivityUnfrozen(pid);
+ }
+
+ if (cached || frozen) {
+ // Always ensure the test activity is not cached.
bringTestActivityTop();
+ // The test activity becomes non-cached and should receive the pending display events
for (int i = 0; i < mDisplayCount; i++) {
// The pending DISPLAY_ADDED & DISPLAY_CHANGED should arrive now
displayBundleAt(i).waitDisplayEvent(DISPLAY_ADDED);
@@ -363,9 +401,48 @@ public class DisplayEventDeliveryTest {
}
/**
- * Launch the test activity that would listen to display events
+ * Create virtual displays, change their configurations and release them.
+ */
+ @Test
+ public void testDisplayEvents() {
+ testDisplayEventsInternal(false, false);
+ }
+
+ /**
+ * Create virtual displays, change their configurations and release them. The display app is
+ * moved to cached and the test verifies that no events are delivered to the cached app.
*/
- private void launchTestActivity() {
+ @Test
+ public void testDisplayEventsCached() {
+ testDisplayEventsInternal(true, false);
+ }
+
+ /**
+ * Create virtual displays, change their configurations and release them. The display app is
+ * frozen and the test verifies that no events are delivered to the frozen app.
+ */
+ @RequiresFlagsEnabled(Flags.FLAG_DEFER_DISPLAY_EVENTS_WHEN_FROZEN)
+ @Test
+ public void testDisplayEventsFrozen() {
+ assumeTrue(isAppFreezerEnabled());
+ testDisplayEventsInternal(false, true);
+ }
+
+ /**
+ * Create virtual displays, change their configurations and release them. The display app is
+ * cached and frozen and the test verifies that no events are delivered to the app.
+ */
+ @RequiresFlagsEnabled(Flags.FLAG_DEFER_DISPLAY_EVENTS_WHEN_FROZEN)
+ @Test
+ public void testDisplayEventsCachedFrozen() {
+ assumeTrue(isAppFreezerEnabled());
+ 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);
@@ -377,6 +454,18 @@ public class DisplayEventDeliveryTest {
},
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;
+ }
}
/**
@@ -415,6 +504,45 @@ public class DisplayEventDeliveryTest {
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
*