diff options
author | 2024-09-30 15:54:11 +0000 | |
---|---|---|
committer | 2024-09-30 15:54:11 +0000 | |
commit | f4dca5254ec4d6545693787b48814ef042f08ead (patch) | |
tree | 91d7b103da07874472f25676dec961c9596a7e37 | |
parent | 745ebaa43db4b3a62ba61894fda79b573f5e67f1 (diff) | |
parent | 21d6448c978c83a337f10a422d746deaaba326a4 (diff) |
Merge "DisplayManagerService: use per-pid freezer listener" into main
-rw-r--r-- | services/core/java/com/android/server/display/DisplayManagerService.java | 298 | ||||
-rw-r--r-- | services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java | 188 |
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 * |