| /* |
| * Copyright (C) 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.am; |
| |
| import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; |
| import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED; |
| import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD; |
| import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST; |
| import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO; |
| import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_ALARM; |
| import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_FOREGROUND; |
| import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_INTERACTIVE; |
| import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_MANIFEST; |
| import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_ORDERED; |
| import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_PRIORITIZED; |
| import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList; |
| import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList; |
| import static com.android.server.am.BroadcastQueueTest.CLASS_BLUE; |
| import static com.android.server.am.BroadcastQueueTest.CLASS_GREEN; |
| import static com.android.server.am.BroadcastQueueTest.CLASS_RED; |
| import static com.android.server.am.BroadcastQueueTest.CLASS_YELLOW; |
| import static com.android.server.am.BroadcastQueueTest.PACKAGE_BLUE; |
| import static com.android.server.am.BroadcastQueueTest.PACKAGE_GREEN; |
| import static com.android.server.am.BroadcastQueueTest.PACKAGE_RED; |
| import static com.android.server.am.BroadcastQueueTest.PACKAGE_YELLOW; |
| import static com.android.server.am.BroadcastQueueTest.getUidForPackage; |
| import static com.android.server.am.BroadcastQueueTest.makeManifestReceiver; |
| import static com.android.server.am.BroadcastQueueTest.withPriority; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotEquals; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| import static org.mockito.ArgumentMatchers.anyInt; |
| import static org.mockito.ArgumentMatchers.anyLong; |
| import static org.mockito.ArgumentMatchers.anyString; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.ArgumentMatchers.nullable; |
| import static org.mockito.Mockito.doReturn; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.times; |
| |
| import android.annotation.NonNull; |
| import android.app.Activity; |
| import android.app.AppOpsManager; |
| import android.app.BackgroundStartPrivileges; |
| import android.app.BroadcastOptions; |
| import android.appwidget.AppWidgetManager; |
| import android.content.IIntentReceiver; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.pm.ResolveInfo; |
| import android.media.AudioManager; |
| import android.os.Bundle; |
| import android.os.BundleMerger; |
| import android.os.DropBoxManager; |
| import android.os.HandlerThread; |
| import android.os.SystemClock; |
| import android.os.UserHandle; |
| import android.provider.Settings; |
| import android.util.IndentingPrintWriter; |
| import android.util.Pair; |
| |
| import androidx.test.filters.SmallTest; |
| |
| import com.android.internal.util.FrameworkStatsLog; |
| import com.android.server.ExtendedMockitoRule; |
| import com.android.server.am.BroadcastQueueTest.SyncBarrier; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.mockito.Mock; |
| |
| import java.io.PrintWriter; |
| import java.io.Writer; |
| import java.lang.reflect.Array; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| @SmallTest |
| public final class BroadcastQueueModernImplTest { |
| private static final int TEST_UID = android.os.Process.FIRST_APPLICATION_UID; |
| private static final int TEST_UID2 = android.os.Process.FIRST_APPLICATION_UID + 1; |
| |
| @Mock ActivityManagerService mAms; |
| @Mock ProcessRecord mProcess; |
| |
| @Mock BroadcastProcessQueue mQueue1; |
| @Mock BroadcastProcessQueue mQueue2; |
| @Mock BroadcastProcessQueue mQueue3; |
| @Mock BroadcastProcessQueue mQueue4; |
| |
| HandlerThread mHandlerThread; |
| |
| BroadcastConstants mConstants; |
| BroadcastQueueModernImpl mImpl; |
| |
| BroadcastProcessQueue mHead; |
| |
| @Rule |
| public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this) |
| .spyStatic(FrameworkStatsLog.class) |
| .build(); |
| |
| @Before |
| public void setUp() throws Exception { |
| mHandlerThread = new HandlerThread(getClass().getSimpleName()); |
| mHandlerThread.start(); |
| |
| mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS); |
| mConstants.DELAY_URGENT_MILLIS = -120_000; |
| mConstants.DELAY_NORMAL_MILLIS = 10_000; |
| mConstants.DELAY_CACHED_MILLIS = 120_000; |
| |
| final BroadcastSkipPolicy emptySkipPolicy = new BroadcastSkipPolicy(mAms) { |
| public boolean shouldSkip(BroadcastRecord r, Object o) { |
| // Ignored |
| return false; |
| } |
| public String shouldSkipMessage(BroadcastRecord r, Object o) { |
| // Ignored |
| return null; |
| } |
| public boolean disallowBackgroundStart(BroadcastRecord r) { |
| // Ignored |
| return false; |
| } |
| }; |
| final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) { |
| public void addBroadcastToHistoryLocked(BroadcastRecord original) { |
| // Ignored |
| } |
| }; |
| |
| |
| mImpl = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(), |
| mConstants, mConstants, emptySkipPolicy, emptyHistory); |
| |
| doReturn(1L).when(mQueue1).getRunnableAt(); |
| doReturn(2L).when(mQueue2).getRunnableAt(); |
| doReturn(3L).when(mQueue3).getRunnableAt(); |
| doReturn(4L).when(mQueue4).getRunnableAt(); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| mHandlerThread.quit(); |
| } |
| |
| private static void assertOrphan(BroadcastProcessQueue queue) { |
| assertNull(queue.runnableAtNext); |
| assertNull(queue.runnableAtPrev); |
| } |
| |
| private static void assertRunnableList(@NonNull List<BroadcastProcessQueue> expected, |
| @NonNull BroadcastProcessQueue actualHead) { |
| BroadcastProcessQueue test = actualHead; |
| final int N = expected.size(); |
| for (int i = 0; i < N; i++) { |
| final BroadcastProcessQueue expectedPrev = (i > 0) ? expected.get(i - 1) : null; |
| final BroadcastProcessQueue expectedTest = expected.get(i); |
| final BroadcastProcessQueue expectedNext = (i < N - 1) ? expected.get(i + 1) : null; |
| |
| assertEquals("prev", expectedPrev, test.runnableAtPrev); |
| assertEquals("test", expectedTest, test); |
| assertEquals("next", expectedNext, test.runnableAtNext); |
| |
| test = test.runnableAtNext; |
| } |
| if (N == 0) { |
| assertNull(actualHead); |
| } |
| } |
| |
| private static Intent makeMockIntent() { |
| return mock(Intent.class); |
| } |
| |
| private static ResolveInfo makeMockManifestReceiver() { |
| return mock(ResolveInfo.class); |
| } |
| |
| private static BroadcastFilter makeMockRegisteredReceiver() { |
| return mock(BroadcastFilter.class); |
| } |
| |
| private BroadcastRecord makeBroadcastRecord(Intent intent) { |
| return makeBroadcastRecord(intent, BroadcastOptions.makeBasic(), |
| List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), false); |
| } |
| |
| private BroadcastRecord makeOrderedBroadcastRecord(Intent intent) { |
| return makeBroadcastRecord(intent, BroadcastOptions.makeBasic(), |
| List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), true); |
| } |
| |
| private BroadcastRecord makeBroadcastRecord(Intent intent, List receivers) { |
| return makeBroadcastRecord(intent, BroadcastOptions.makeBasic(), receivers, false); |
| } |
| |
| private BroadcastRecord makeBroadcastRecord(Intent intent, BroadcastOptions options) { |
| return makeBroadcastRecord(intent, options, |
| List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), false); |
| } |
| |
| private BroadcastRecord makeBroadcastRecord(Intent intent, BroadcastOptions options, |
| List receivers, boolean ordered) { |
| return makeBroadcastRecord(intent, options, receivers, null, ordered); |
| } |
| |
| private BroadcastRecord makeBroadcastRecord(Intent intent, BroadcastOptions options, |
| List receivers, IIntentReceiver resultTo, boolean ordered) { |
| return new BroadcastRecord(mImpl, intent, mProcess, PACKAGE_RED, null, 21, 42, false, null, |
| null, null, null, AppOpsManager.OP_NONE, options, receivers, null, resultTo, |
| Activity.RESULT_OK, null, null, ordered, false, false, UserHandle.USER_SYSTEM, |
| BackgroundStartPrivileges.NONE, false, null); |
| } |
| |
| private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue, |
| BroadcastRecord record, int recordIndex, long enqueueTime) { |
| queue.enqueueOrReplaceBroadcast(record, recordIndex, false); |
| record.enqueueTime = enqueueTime; |
| } |
| |
| @Test |
| public void testRunnableList_Simple() { |
| assertRunnableList(List.of(), mHead); |
| |
| mHead = insertIntoRunnableList(mHead, mQueue1); |
| assertRunnableList(List.of(mQueue1), mHead); |
| |
| mHead = removeFromRunnableList(mHead, mQueue1); |
| assertRunnableList(List.of(), mHead); |
| } |
| |
| @Test |
| public void testRunnableList_InsertLast() { |
| mHead = insertIntoRunnableList(mHead, mQueue1); |
| mHead = insertIntoRunnableList(mHead, mQueue2); |
| mHead = insertIntoRunnableList(mHead, mQueue3); |
| mHead = insertIntoRunnableList(mHead, mQueue4); |
| assertRunnableList(List.of(mQueue1, mQueue2, mQueue3, mQueue4), mHead); |
| } |
| |
| @Test |
| public void testRunnableList_InsertFirst() { |
| mHead = insertIntoRunnableList(mHead, mQueue4); |
| mHead = insertIntoRunnableList(mHead, mQueue3); |
| mHead = insertIntoRunnableList(mHead, mQueue2); |
| mHead = insertIntoRunnableList(mHead, mQueue1); |
| assertRunnableList(List.of(mQueue1, mQueue2, mQueue3, mQueue4), mHead); |
| } |
| |
| @Test |
| public void testRunnableList_InsertMiddle() { |
| mHead = insertIntoRunnableList(mHead, mQueue1); |
| mHead = insertIntoRunnableList(mHead, mQueue3); |
| mHead = insertIntoRunnableList(mHead, mQueue2); |
| assertRunnableList(List.of(mQueue1, mQueue2, mQueue3), mHead); |
| } |
| |
| @Test |
| public void testRunnableList_Remove() { |
| mHead = insertIntoRunnableList(mHead, mQueue1); |
| mHead = insertIntoRunnableList(mHead, mQueue2); |
| mHead = insertIntoRunnableList(mHead, mQueue3); |
| mHead = insertIntoRunnableList(mHead, mQueue4); |
| |
| mHead = removeFromRunnableList(mHead, mQueue3); |
| assertRunnableList(List.of(mQueue1, mQueue2, mQueue4), mHead); |
| |
| mHead = removeFromRunnableList(mHead, mQueue1); |
| assertRunnableList(List.of(mQueue2, mQueue4), mHead); |
| |
| mHead = removeFromRunnableList(mHead, mQueue4); |
| assertRunnableList(List.of(mQueue2), mHead); |
| |
| mHead = removeFromRunnableList(mHead, mQueue2); |
| assertRunnableList(List.of(), mHead); |
| |
| // Verify all links cleaned up during removal |
| assertOrphan(mQueue1); |
| assertOrphan(mQueue2); |
| assertOrphan(mQueue3); |
| assertOrphan(mQueue4); |
| } |
| |
| @Test |
| public void testProcessQueue_Complex() { |
| BroadcastProcessQueue red = mImpl.getOrCreateProcessQueue(PACKAGE_RED, TEST_UID); |
| BroadcastProcessQueue green = mImpl.getOrCreateProcessQueue(PACKAGE_GREEN, TEST_UID); |
| BroadcastProcessQueue blue = mImpl.getOrCreateProcessQueue(PACKAGE_BLUE, TEST_UID); |
| |
| assertEquals(PACKAGE_RED, red.processName); |
| assertEquals(PACKAGE_GREEN, green.processName); |
| assertEquals(PACKAGE_BLUE, blue.processName); |
| |
| // Verify that removing middle queue works |
| mImpl.removeProcessQueue(PACKAGE_GREEN, TEST_UID); |
| assertEquals(red, mImpl.getProcessQueue(PACKAGE_RED, TEST_UID)); |
| assertNull(mImpl.getProcessQueue(PACKAGE_GREEN, TEST_UID)); |
| assertEquals(blue, mImpl.getProcessQueue(PACKAGE_BLUE, TEST_UID)); |
| assertNull(mImpl.getProcessQueue(PACKAGE_YELLOW, TEST_UID)); |
| |
| // Verify that removing head queue works |
| mImpl.removeProcessQueue(PACKAGE_RED, TEST_UID); |
| assertNull(mImpl.getProcessQueue(PACKAGE_RED, TEST_UID)); |
| assertNull(mImpl.getProcessQueue(PACKAGE_GREEN, TEST_UID)); |
| assertEquals(blue, mImpl.getProcessQueue(PACKAGE_BLUE, TEST_UID)); |
| assertNull(mImpl.getProcessQueue(PACKAGE_YELLOW, TEST_UID)); |
| |
| // Verify that removing last queue works |
| mImpl.removeProcessQueue(PACKAGE_BLUE, TEST_UID); |
| assertNull(mImpl.getProcessQueue(PACKAGE_RED, TEST_UID)); |
| assertNull(mImpl.getProcessQueue(PACKAGE_GREEN, TEST_UID)); |
| assertNull(mImpl.getProcessQueue(PACKAGE_BLUE, TEST_UID)); |
| assertNull(mImpl.getProcessQueue(PACKAGE_YELLOW, TEST_UID)); |
| |
| // Verify that removing missing doesn't crash |
| mImpl.removeProcessQueue(PACKAGE_YELLOW, TEST_UID); |
| |
| // Verify that we can start all over again safely |
| BroadcastProcessQueue yellow = mImpl.getOrCreateProcessQueue(PACKAGE_YELLOW, TEST_UID); |
| assertEquals(yellow, mImpl.getProcessQueue(PACKAGE_YELLOW, TEST_UID)); |
| } |
| |
| /** |
| * Empty queue isn't runnable. |
| */ |
| @Test |
| public void testRunnableAt_Empty() { |
| final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, |
| PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); |
| assertFalse(queue.isRunnable()); |
| assertEquals(Long.MAX_VALUE, queue.getRunnableAt()); |
| assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); |
| } |
| |
| /** |
| * Queue with a "normal" and "deferrable" broadcast is runnable at different times depending |
| * on process cached state; when cached it's delayed indefinitely. |
| */ |
| @Test |
| public void testRunnableAt_Normal_Deferrable() { |
| final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, |
| PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); |
| |
| final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); |
| final BroadcastOptions options = BroadcastOptions.makeBasic() |
| .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE); |
| final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, options, |
| List.of(makeMockRegisteredReceiver()), false); |
| queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false); |
| |
| queue.setProcessAndUidCached(null, false); |
| final long notCachedRunnableAt = queue.getRunnableAt(); |
| queue.setProcessAndUidCached(null, true); |
| final long cachedRunnableAt = queue.getRunnableAt(); |
| assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt); |
| assertFalse(queue.isRunnable()); |
| assertEquals(BroadcastProcessQueue.REASON_CACHED_INFINITE_DEFER, |
| queue.getRunnableAtReason()); |
| assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); |
| } |
| |
| /** |
| * Queue with a "normal" broadcast is runnable at different times depending |
| * on process cached state; when cached it's delayed by some amount. |
| */ |
| @Test |
| public void testRunnableAt_Normal() { |
| final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, |
| PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); |
| |
| final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); |
| final BroadcastOptions options = BroadcastOptions.makeBasic() |
| .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_NONE); |
| final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, options, |
| List.of(makeMockRegisteredReceiver()), false); |
| queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false); |
| |
| queue.setProcessAndUidCached(null, false); |
| final long notCachedRunnableAt = queue.getRunnableAt(); |
| queue.setProcessAndUidCached(null, true); |
| final long cachedRunnableAt = queue.getRunnableAt(); |
| assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt); |
| assertTrue(queue.isRunnable()); |
| assertEquals(BroadcastProcessQueue.REASON_CACHED, queue.getRunnableAtReason()); |
| assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); |
| } |
| |
| /** |
| * Queue with foreground broadcast is always runnable immediately, |
| * regardless of process cached state. |
| */ |
| @Test |
| public void testRunnableAt_Foreground() { |
| final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, |
| PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); |
| |
| // enqueue a bg-priority broadcast then a fg-priority one |
| final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED); |
| final BroadcastRecord timezoneRecord = makeBroadcastRecord(timezone); |
| queue.enqueueOrReplaceBroadcast(timezoneRecord, 0, false); |
| |
| final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); |
| airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); |
| final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane); |
| queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false); |
| |
| // verify that: |
| // (a) the queue is immediately runnable by existence of a fg-priority broadcast |
| // (b) the next one up is the fg-priority broadcast despite its later enqueue time |
| queue.setProcessAndUidCached(null, false); |
| assertTrue(queue.isRunnable()); |
| assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime); |
| assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); |
| assertEquals(queue.peekNextBroadcastRecord(), airplaneRecord); |
| |
| queue.setProcessAndUidCached(null, true); |
| assertTrue(queue.isRunnable()); |
| assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime); |
| assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); |
| assertEquals(queue.peekNextBroadcastRecord(), airplaneRecord); |
| } |
| |
| /** |
| * Queue with ordered broadcast is runnable only once we've made enough |
| * progress on earlier blocking items. |
| */ |
| @Test |
| public void testRunnableAt_Ordered() { |
| final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, |
| PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); |
| |
| final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); |
| final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, null, |
| List.of(withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10), |
| withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 0)), true); |
| queue.enqueueOrReplaceBroadcast(airplaneRecord, 1, false); |
| |
| assertFalse(queue.isRunnable()); |
| assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason()); |
| |
| // Bumping past barrier makes us now runnable |
| airplaneRecord.setDeliveryState(0, BroadcastRecord.DELIVERY_DELIVERED, |
| "testRunnableAt_Ordered"); |
| queue.invalidateRunnableAt(); |
| assertTrue(queue.isRunnable()); |
| assertNotEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason()); |
| } |
| |
| /** |
| * Queue with too many pending broadcasts is runnable. |
| */ |
| @Test |
| public void testRunnableAt_Huge() { |
| BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, |
| PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); |
| |
| final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); |
| final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, |
| List.of(makeMockRegisteredReceiver())); |
| queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false); |
| |
| mConstants.MAX_PENDING_BROADCASTS = 128; |
| queue.invalidateRunnableAt(); |
| assertThat(queue.getRunnableAt()).isGreaterThan(airplaneRecord.enqueueTime); |
| assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason()); |
| |
| mConstants.MAX_PENDING_BROADCASTS = 1; |
| queue.invalidateRunnableAt(); |
| assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueTime); |
| assertEquals(BroadcastProcessQueue.REASON_MAX_PENDING, queue.getRunnableAtReason()); |
| } |
| |
| /** |
| * Verify that a cached process that would normally be delayed becomes |
| * immediately runnable when the given broadcast is enqueued. |
| */ |
| private void doRunnableAt_Cached(BroadcastRecord testRecord, int testRunnableAtReason) { |
| final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, |
| PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); |
| queue.setProcessAndUidCached(null, true); |
| |
| final BroadcastRecord lazyRecord = makeBroadcastRecord( |
| new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), |
| List.of(makeMockRegisteredReceiver())); |
| |
| queue.enqueueOrReplaceBroadcast(lazyRecord, 0, false); |
| assertThat(queue.getRunnableAt()).isGreaterThan(lazyRecord.enqueueTime); |
| assertThat(queue.getRunnableAtReason()).isNotEqualTo(testRunnableAtReason); |
| |
| queue.enqueueOrReplaceBroadcast(testRecord, 0, false); |
| assertThat(queue.getRunnableAt()).isAtMost(testRecord.enqueueTime); |
| assertThat(queue.getRunnableAtReason()).isEqualTo(testRunnableAtReason); |
| } |
| |
| @Test |
| public void testRunnableAt_Cached_Manifest() { |
| doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), null, |
| List.of(makeMockManifestReceiver()), null, false), REASON_CONTAINS_MANIFEST); |
| } |
| |
| @Test |
| public void testRunnableAt_Cached_Ordered() { |
| doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), null, |
| List.of(makeMockRegisteredReceiver()), null, true), REASON_CONTAINS_ORDERED); |
| } |
| |
| @Test |
| public void testRunnableAt_Cached_Foreground() { |
| final Intent foregroundIntent = new Intent(); |
| foregroundIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); |
| doRunnableAt_Cached(makeBroadcastRecord(foregroundIntent, null, |
| List.of(makeMockRegisteredReceiver()), null, false), REASON_CONTAINS_FOREGROUND); |
| } |
| |
| @Test |
| public void testRunnableAt_Cached_Interactive() { |
| final BroadcastOptions options = BroadcastOptions.makeBasic(); |
| options.setInteractive(true); |
| doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), options, |
| List.of(makeMockRegisteredReceiver()), null, false), REASON_CONTAINS_INTERACTIVE); |
| } |
| |
| @Test |
| public void testRunnableAt_Cached_Alarm() { |
| final BroadcastOptions options = BroadcastOptions.makeBasic(); |
| options.setAlarmBroadcast(true); |
| doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), options, |
| List.of(makeMockRegisteredReceiver()), null, false), REASON_CONTAINS_ALARM); |
| } |
| |
| @Test |
| public void testRunnableAt_Cached_Prioritized_NonDeferrable() { |
| final List receivers = List.of( |
| withPriority(makeManifestReceiver(PACKAGE_RED, PACKAGE_RED), 10), |
| withPriority(makeManifestReceiver(PACKAGE_GREEN, PACKAGE_GREEN), -10)); |
| final BroadcastOptions options = BroadcastOptions.makeBasic() |
| .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_NONE); |
| doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), options, |
| receivers, null, false), REASON_CONTAINS_PRIORITIZED); |
| } |
| |
| /** |
| * Confirm that we always prefer running pending items marked as "urgent", |
| * then "normal", then "offload", dispatching by the relative ordering |
| * within each of those clustering groups. |
| */ |
| @Test |
| public void testMakeActiveNextPending() { |
| BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, |
| PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); |
| |
| queue.enqueueOrReplaceBroadcast( |
| makeBroadcastRecord(new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED) |
| .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, false); |
| queue.enqueueOrReplaceBroadcast( |
| makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0, false); |
| queue.enqueueOrReplaceBroadcast( |
| makeBroadcastRecord(new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED) |
| .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, false); |
| queue.enqueueOrReplaceBroadcast( |
| makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED) |
| .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, false); |
| queue.enqueueOrReplaceBroadcast( |
| makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0, false); |
| queue.enqueueOrReplaceBroadcast( |
| makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED) |
| .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, false); |
| |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_LOCKED_BOOT_COMPLETED, queue.getActive().intent.getAction()); |
| |
| // To maximize test coverage, dump current state; we're not worried |
| // about the actual output, just that we don't crash |
| queue.getActive().setDeliveryState(0, BroadcastRecord.DELIVERY_SCHEDULED, "Test-driven"); |
| queue.dumpLocked(SystemClock.uptimeMillis(), |
| new IndentingPrintWriter(new PrintWriter(Writer.nullWriter()))); |
| |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction()); |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction()); |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_TIME_TICK, queue.getActive().intent.getAction()); |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_AIRPLANE_MODE_CHANGED, queue.getActive().intent.getAction()); |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction()); |
| assertTrue(queue.isEmpty()); |
| } |
| |
| /** |
| * Verify that we don't let urgent broadcasts starve delivery of non-urgent |
| */ |
| @Test |
| public void testUrgentStarvation() { |
| final BroadcastOptions optInteractive = BroadcastOptions.makeBasic(); |
| optInteractive.setInteractive(true); |
| |
| mConstants.MAX_CONSECUTIVE_URGENT_DISPATCHES = 2; |
| BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, |
| PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); |
| long timeCounter = 100; |
| |
| // mix of broadcasts, with more than 2 fg/urgent |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), |
| 0, timeCounter++); |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)), |
| 0, timeCounter++); |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0, timeCounter++); |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED) |
| .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++); |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(Intent.ACTION_APPLICATION_PREFERENCES), |
| optInteractive), 0, timeCounter++); |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE), |
| optInteractive), 0, timeCounter++); |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(Intent.ACTION_INPUT_METHOD_CHANGED) |
| .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++); |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(Intent.ACTION_NEW_OUTGOING_CALL), |
| optInteractive), 0, timeCounter++); |
| |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction()); |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_APPLICATION_PREFERENCES, queue.getActive().intent.getAction()); |
| // after MAX_CONSECUTIVE_URGENT_DISPATCHES expect an ordinary one next |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction()); |
| // and then back to prioritizing urgent ones |
| queue.makeActiveNextPending(); |
| assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE, |
| queue.getActive().intent.getAction()); |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_INPUT_METHOD_CHANGED, queue.getActive().intent.getAction()); |
| // verify the reset-count-then-resume worked too |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction()); |
| } |
| |
| /** |
| * Verify that offload broadcasts are not starved because of broadcasts in higher priority |
| * queues. |
| */ |
| @Test |
| public void testOffloadStarvation() { |
| final BroadcastOptions optInteractive = BroadcastOptions.makeBasic(); |
| optInteractive.setInteractive(true); |
| |
| mConstants.MAX_CONSECUTIVE_URGENT_DISPATCHES = 1; |
| mConstants.MAX_CONSECUTIVE_NORMAL_DISPATCHES = 2; |
| final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, |
| PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); |
| long timeCounter = 100; |
| |
| // mix of broadcasts, with more than 2 normal |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(Intent.ACTION_BOOT_COMPLETED) |
| .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++); |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), |
| 0, timeCounter++); |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(Intent.ACTION_PACKAGE_CHANGED) |
| .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++); |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)), |
| 0, timeCounter++); |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0, timeCounter++); |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED) |
| .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++); |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(Intent.ACTION_APPLICATION_PREFERENCES), |
| optInteractive), 0, timeCounter++); |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE), |
| optInteractive), 0, timeCounter++); |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(Intent.ACTION_INPUT_METHOD_CHANGED) |
| .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++); |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(Intent.ACTION_NEW_OUTGOING_CALL), |
| optInteractive), 0, timeCounter++); |
| |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction()); |
| // after MAX_CONSECUTIVE_URGENT_DISPATCHES expect an ordinary one next |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction()); |
| // and then back to prioritizing urgent ones |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_APPLICATION_PREFERENCES, queue.getActive().intent.getAction()); |
| // after MAX_CONSECUTIVE_URGENT_DISPATCHES, again an ordinary one next |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction()); |
| // and then back to prioritizing urgent ones |
| queue.makeActiveNextPending(); |
| assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE, |
| queue.getActive().intent.getAction()); |
| // after MAX_CONSECUTIVE_URGENT_DISPATCHES and MAX_CONSECUTIVE_NORMAL_DISPATCHES, |
| // expect an offload one |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_BOOT_COMPLETED, queue.getActive().intent.getAction()); |
| // and then back to prioritizing urgent ones |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_INPUT_METHOD_CHANGED, queue.getActive().intent.getAction()); |
| } |
| |
| /** |
| * Verify that BroadcastProcessQueue#setPrioritizeEarliest() works as expected. |
| */ |
| @Test |
| public void testPrioritizeEarliest() { |
| final BroadcastOptions optInteractive = BroadcastOptions.makeBasic(); |
| optInteractive.setInteractive(true); |
| |
| BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, |
| PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); |
| queue.setPrioritizeEarliest(true); |
| long timeCounter = 100; |
| |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(Intent.ACTION_BOOT_COMPLETED) |
| .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++); |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), |
| 0, timeCounter++); |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(Intent.ACTION_PACKAGE_CHANGED) |
| .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++); |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)), |
| 0, timeCounter++); |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), |
| 0, timeCounter++); |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED) |
| .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++); |
| enqueueOrReplaceBroadcast(queue, |
| makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE), |
| optInteractive), 0, timeCounter++); |
| |
| // When we mark BroadcastProcessQueue to prioritize earliest, we should |
| // expect to dispatch broadcasts in the order they were enqueued |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_BOOT_COMPLETED, queue.getActive().intent.getAction()); |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction()); |
| // after MAX_CONSECUTIVE_URGENT_DISPATCHES expect an ordinary one next |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_PACKAGE_CHANGED, queue.getActive().intent.getAction()); |
| // and then back to prioritizing urgent ones |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction()); |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_TIME_TICK, queue.getActive().intent.getAction()); |
| queue.makeActiveNextPending(); |
| assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction()); |
| // verify the reset-count-then-resume worked too |
| queue.makeActiveNextPending(); |
| assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE, |
| queue.getActive().intent.getAction()); |
| } |
| |
| /** |
| * Verify that sending a broadcast with DELIVERY_GROUP_POLICY_MOST_RECENT works as expected. |
| */ |
| @Test |
| public void testDeliveryGroupPolicy_mostRecent() { |
| final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK); |
| final BroadcastOptions optionsTimeTick = BroadcastOptions.makeBasic(); |
| optionsTimeTick.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT); |
| |
| final Intent musicVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION); |
| musicVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, |
| AudioManager.STREAM_MUSIC); |
| final BroadcastOptions optionsMusicVolumeChanged = BroadcastOptions.makeBasic(); |
| optionsMusicVolumeChanged.setDeliveryGroupPolicy( |
| BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT); |
| optionsMusicVolumeChanged.setDeliveryGroupMatchingKey("audio", |
| String.valueOf(AudioManager.STREAM_MUSIC)); |
| |
| final Intent alarmVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION); |
| alarmVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, |
| AudioManager.STREAM_ALARM); |
| final BroadcastOptions optionsAlarmVolumeChanged = BroadcastOptions.makeBasic(); |
| optionsAlarmVolumeChanged.setDeliveryGroupPolicy( |
| BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT); |
| optionsAlarmVolumeChanged.setDeliveryGroupMatchingKey("audio", |
| String.valueOf(AudioManager.STREAM_ALARM)); |
| |
| // Halt all processing so that we get a consistent view |
| mHandlerThread.getLooper().getQueue().postSyncBarrier(); |
| |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged, |
| optionsMusicVolumeChanged)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged, |
| optionsAlarmVolumeChanged)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged, |
| optionsMusicVolumeChanged)); |
| |
| final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN, |
| getUidForPackage(PACKAGE_GREEN)); |
| // Verify that the older musicVolumeChanged has been removed. |
| verifyPendingRecords(queue, |
| List.of(timeTick, alarmVolumeChanged, musicVolumeChanged)); |
| |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged, |
| optionsAlarmVolumeChanged)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged, |
| optionsMusicVolumeChanged)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged, |
| optionsAlarmVolumeChanged)); |
| // Verify that the older alarmVolumeChanged has been removed. |
| verifyPendingRecords(queue, |
| List.of(timeTick, musicVolumeChanged, alarmVolumeChanged)); |
| |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged, |
| optionsMusicVolumeChanged)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged, |
| optionsAlarmVolumeChanged)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick)); |
| // Verify that the older timeTick has been removed. |
| verifyPendingRecords(queue, |
| List.of(musicVolumeChanged, alarmVolumeChanged, timeTick)); |
| } |
| |
| /** |
| * Verify that sending a broadcast with DELIVERY_GROUP_POLICY_MERGED works as expected. |
| */ |
| @Test |
| public void testDeliveryGroupPolicy_merged() { |
| final BundleMerger extrasMerger = new BundleMerger(); |
| extrasMerger.setMergeStrategy(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, |
| BundleMerger.STRATEGY_ARRAY_APPEND); |
| |
| final Intent packageChangedForUid = createPackageChangedIntent(TEST_UID, |
| List.of("com.testuid.component1")); |
| final BroadcastOptions optionsPackageChangedForUid = BroadcastOptions.makeBasic(); |
| optionsPackageChangedForUid.setDeliveryGroupPolicy( |
| BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED); |
| optionsPackageChangedForUid.setDeliveryGroupMatchingKey("package", |
| String.valueOf(TEST_UID)); |
| optionsPackageChangedForUid.setDeliveryGroupExtrasMerger(extrasMerger); |
| |
| final Intent secondPackageChangedForUid = createPackageChangedIntent(TEST_UID, |
| List.of("com.testuid.component2", "com.testuid.component3")); |
| |
| final Intent packageChangedForUid2 = createPackageChangedIntent(TEST_UID2, |
| List.of("com.testuid2.component1")); |
| final BroadcastOptions optionsPackageChangedForUid2 = BroadcastOptions.makeBasic(); |
| optionsPackageChangedForUid.setDeliveryGroupPolicy( |
| BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED); |
| optionsPackageChangedForUid.setDeliveryGroupMatchingKey("package", |
| String.valueOf(TEST_UID2)); |
| optionsPackageChangedForUid.setDeliveryGroupExtrasMerger(extrasMerger); |
| |
| // Halt all processing so that we get a consistent view |
| mHandlerThread.getLooper().getQueue().postSyncBarrier(); |
| |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(packageChangedForUid, |
| optionsPackageChangedForUid)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(packageChangedForUid2, |
| optionsPackageChangedForUid2)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(secondPackageChangedForUid, |
| optionsPackageChangedForUid)); |
| |
| final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN, |
| getUidForPackage(PACKAGE_GREEN)); |
| final Intent expectedPackageChangedForUid = createPackageChangedIntent(TEST_UID, |
| List.of("com.testuid.component2", "com.testuid.component3", |
| "com.testuid.component1")); |
| // Verify that packageChangedForUid and secondPackageChangedForUid broadcasts |
| // have been merged. |
| verifyPendingRecords(queue, List.of(packageChangedForUid2, expectedPackageChangedForUid)); |
| } |
| |
| @Test |
| public void testDeliveryGroupPolicy_matchingFilter() { |
| final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK); |
| final BroadcastOptions optionsTimeTick = BroadcastOptions.makeBasic(); |
| optionsTimeTick.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT); |
| |
| final Intent musicVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION); |
| musicVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, |
| AudioManager.STREAM_MUSIC); |
| final IntentFilter filterMusicVolumeChanged = new IntentFilter( |
| AudioManager.VOLUME_CHANGED_ACTION); |
| filterMusicVolumeChanged.addExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, |
| AudioManager.STREAM_MUSIC); |
| final BroadcastOptions optionsMusicVolumeChanged = BroadcastOptions.makeBasic(); |
| optionsMusicVolumeChanged.setDeliveryGroupPolicy( |
| BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT); |
| optionsMusicVolumeChanged.setDeliveryGroupMatchingFilter(filterMusicVolumeChanged); |
| |
| final Intent alarmVolumeChanged = new Intent(AudioManager.VOLUME_CHANGED_ACTION); |
| alarmVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, |
| AudioManager.STREAM_ALARM); |
| final IntentFilter filterAlarmVolumeChanged = new IntentFilter( |
| AudioManager.VOLUME_CHANGED_ACTION); |
| filterAlarmVolumeChanged.addExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, |
| AudioManager.STREAM_ALARM); |
| final BroadcastOptions optionsAlarmVolumeChanged = BroadcastOptions.makeBasic(); |
| optionsAlarmVolumeChanged.setDeliveryGroupPolicy( |
| BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT); |
| optionsAlarmVolumeChanged.setDeliveryGroupMatchingFilter(filterAlarmVolumeChanged); |
| |
| // Halt all processing so that we get a consistent view |
| mHandlerThread.getLooper().getQueue().postSyncBarrier(); |
| |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged, |
| optionsMusicVolumeChanged)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged, |
| optionsAlarmVolumeChanged)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged, |
| optionsMusicVolumeChanged)); |
| |
| final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN, |
| getUidForPackage(PACKAGE_GREEN)); |
| // Verify that the older musicVolumeChanged has been removed. |
| verifyPendingRecords(queue, |
| List.of(timeTick, alarmVolumeChanged, musicVolumeChanged)); |
| |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged, |
| optionsAlarmVolumeChanged)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged, |
| optionsMusicVolumeChanged)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged, |
| optionsAlarmVolumeChanged)); |
| // Verify that the older alarmVolumeChanged has been removed. |
| verifyPendingRecords(queue, |
| List.of(timeTick, musicVolumeChanged, alarmVolumeChanged)); |
| |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged, |
| optionsMusicVolumeChanged)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(alarmVolumeChanged, |
| optionsAlarmVolumeChanged)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick)); |
| // Verify that the older timeTick has been removed. |
| verifyPendingRecords(queue, |
| List.of(musicVolumeChanged, alarmVolumeChanged, timeTick)); |
| } |
| |
| @Test |
| public void testDeliveryGroupPolicy_merged_matchingFilter() { |
| final long now = SystemClock.elapsedRealtime(); |
| final Pair<Intent, BroadcastOptions> dropboxEntryBroadcast1 = createDropboxBroadcast( |
| "TAG_A", now, 2); |
| final Pair<Intent, BroadcastOptions> dropboxEntryBroadcast2 = createDropboxBroadcast( |
| "TAG_B", now + 1000, 4); |
| final Pair<Intent, BroadcastOptions> dropboxEntryBroadcast3 = createDropboxBroadcast( |
| "TAG_A", now + 2000, 7); |
| |
| // Halt all processing so that we get a consistent view |
| mHandlerThread.getLooper().getQueue().postSyncBarrier(); |
| |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(dropboxEntryBroadcast1.first, |
| dropboxEntryBroadcast1.second)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(dropboxEntryBroadcast2.first, |
| dropboxEntryBroadcast2.second)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(dropboxEntryBroadcast3.first, |
| dropboxEntryBroadcast3.second)); |
| |
| final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN, |
| getUidForPackage(PACKAGE_GREEN)); |
| // dropboxEntryBroadcast1 and dropboxEntryBroadcast3 should be merged as they use the same |
| // tag and there shouldn't be a change to dropboxEntryBroadcast2. |
| final Pair<Intent, BroadcastOptions> expectedMergedBroadcast = createDropboxBroadcast( |
| "TAG_A", now + 2000, 10); |
| verifyPendingRecords(queue, List.of( |
| dropboxEntryBroadcast2.first, expectedMergedBroadcast.first)); |
| } |
| |
| @Test |
| public void testDeliveryGroupPolicy_sameAction_differentMatchingCriteria() { |
| final Intent closeSystemDialogs1 = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); |
| final BroadcastOptions optionsCloseSystemDialog1 = BroadcastOptions.makeBasic() |
| .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT); |
| |
| final Intent closeSystemDialogs2 = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS) |
| .putExtra("reason", "testing"); |
| final BroadcastOptions optionsCloseSystemDialog2 = BroadcastOptions.makeBasic() |
| .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT) |
| .setDeliveryGroupMatchingKey(Intent.ACTION_CLOSE_SYSTEM_DIALOGS, "testing"); |
| |
| // Halt all processing so that we get a consistent view |
| mHandlerThread.getLooper().getQueue().postSyncBarrier(); |
| |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord( |
| closeSystemDialogs1, optionsCloseSystemDialog1)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord( |
| closeSystemDialogs2, optionsCloseSystemDialog2)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord( |
| closeSystemDialogs1, optionsCloseSystemDialog1)); |
| // Verify that only the older broadcast with no extras was removed. |
| final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN, |
| getUidForPackage(PACKAGE_GREEN)); |
| verifyPendingRecords(queue, List.of(closeSystemDialogs2, closeSystemDialogs1)); |
| |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord( |
| closeSystemDialogs2, optionsCloseSystemDialog2)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord( |
| closeSystemDialogs1, optionsCloseSystemDialog1)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord( |
| closeSystemDialogs2, optionsCloseSystemDialog2)); |
| // Verify that only the older broadcast with no extras was removed. |
| verifyPendingRecords(queue, List.of(closeSystemDialogs1, closeSystemDialogs2)); |
| } |
| |
| private Pair<Intent, BroadcastOptions> createDropboxBroadcast(String tag, long timestampMs, |
| int droppedCount) { |
| final Intent dropboxEntryAdded = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED); |
| dropboxEntryAdded.putExtra(DropBoxManager.EXTRA_TAG, tag); |
| dropboxEntryAdded.putExtra(DropBoxManager.EXTRA_TIME, timestampMs); |
| dropboxEntryAdded.putExtra(DropBoxManager.EXTRA_DROPPED_COUNT, droppedCount); |
| |
| final BundleMerger extrasMerger = new BundleMerger(); |
| extrasMerger.setDefaultMergeStrategy(BundleMerger.STRATEGY_FIRST); |
| extrasMerger.setMergeStrategy(DropBoxManager.EXTRA_TIME, |
| BundleMerger.STRATEGY_COMPARABLE_MAX); |
| extrasMerger.setMergeStrategy(DropBoxManager.EXTRA_DROPPED_COUNT, |
| BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD); |
| final IntentFilter matchingFilter = new IntentFilter( |
| DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED); |
| matchingFilter.addExtra(DropBoxManager.EXTRA_TAG, tag); |
| final BroadcastOptions optionsDropboxEntryAdded = BroadcastOptions.makeBasic() |
| .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED) |
| .setDeliveryGroupMatchingFilter(matchingFilter) |
| .setDeliveryGroupExtrasMerger(extrasMerger); |
| return Pair.create(dropboxEntryAdded, optionsDropboxEntryAdded); |
| } |
| |
| @Test |
| public void testVerifyEnqueuedTime_withReplacePending() { |
| final Intent userPresent = new Intent(Intent.ACTION_USER_PRESENT); |
| userPresent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); |
| |
| // Halt all processing so that we get a consistent view |
| mHandlerThread.getLooper().getQueue().postSyncBarrier(); |
| |
| final BroadcastRecord userPresentRecord1 = makeBroadcastRecord(userPresent); |
| final BroadcastRecord userPresentRecord2 = makeBroadcastRecord(userPresent); |
| |
| mImpl.enqueueBroadcastLocked(userPresentRecord1); |
| mImpl.enqueueBroadcastLocked(userPresentRecord2); |
| |
| final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN, |
| getUidForPackage(PACKAGE_GREEN)); |
| queue.makeActiveNextPending(); |
| |
| // Verify that there is only one record pending and its enqueueTime is |
| // same as that of userPresentRecord1. |
| final BroadcastRecord activeRecord = queue.getActive(); |
| assertEquals(userPresentRecord1.enqueueTime, activeRecord.enqueueTime); |
| assertEquals(userPresentRecord1.enqueueRealTime, activeRecord.enqueueRealTime); |
| assertEquals(userPresentRecord1.enqueueClockTime, activeRecord.enqueueClockTime); |
| assertThat(activeRecord.originalEnqueueClockTime) |
| .isGreaterThan(activeRecord.enqueueClockTime); |
| assertTrue(queue.isEmpty()); |
| } |
| |
| @Test |
| public void testCleanupDisabledPackageReceiversLocked() { |
| final Intent userPresent = new Intent(Intent.ACTION_USER_PRESENT); |
| final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK); |
| |
| final BroadcastRecord record1 = makeBroadcastRecord(userPresent, List.of( |
| makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), |
| makeManifestReceiver(PACKAGE_RED, CLASS_BLUE), |
| makeManifestReceiver(PACKAGE_YELLOW, CLASS_RED), |
| makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN) |
| )); |
| final BroadcastRecord record2 = makeBroadcastRecord(timeTick, List.of( |
| makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), |
| makeManifestReceiver(PACKAGE_RED, CLASS_RED), |
| makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW) |
| )); |
| |
| // Halt all processing so that we get a consistent view |
| mHandlerThread.getLooper().getQueue().postSyncBarrier(); |
| mImpl.enqueueBroadcastLocked(record1); |
| mImpl.enqueueBroadcastLocked(record2); |
| |
| mImpl.cleanupDisabledPackageReceiversLocked(null, null, UserHandle.USER_SYSTEM); |
| |
| // Verify that all receivers have been marked as "skipped". |
| for (BroadcastRecord record : new BroadcastRecord[] {record1, record2}) { |
| for (int i = 0; i < record.receivers.size(); ++i) { |
| final String errMsg = "Unexpected delivery state for record:" + record |
| + "; receiver=" + record.receivers.get(i); |
| assertEquals(errMsg, BroadcastRecord.DELIVERY_SKIPPED, record.getDeliveryState(i)); |
| } |
| } |
| } |
| |
| @Test |
| public void testBroadcastDeliveryEventReported() throws Exception { |
| final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK); |
| final BroadcastOptions optionsTimeTick = BroadcastOptions.makeBasic(); |
| optionsTimeTick.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT); |
| |
| // Halt all processing so that we get a consistent view |
| try (SyncBarrier b = new SyncBarrier(mHandlerThread)) { |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick)); |
| mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick)); |
| } |
| mImpl.waitForIdle(LOG_WRITER_INFO); |
| |
| // Verify that there is only one delivery event reported since one of the broadcasts |
| // should have been skipped. |
| verify(() -> FrameworkStatsLog.write(eq(BROADCAST_DELIVERY_EVENT_REPORTED), |
| eq(getUidForPackage(PACKAGE_GREEN)), anyInt(), eq(Intent.ACTION_TIME_TICK), |
| eq(BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST), |
| eq(BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD), |
| anyLong(), anyLong(), anyLong(), anyInt(), nullable(String.class), anyString()), |
| times(1)); |
| } |
| |
| @Test |
| public void testGetPreferredSchedulingGroup() throws Exception { |
| final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, |
| PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); |
| |
| assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); |
| |
| final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK) |
| .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); |
| queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(timeTick, |
| List.of(makeMockRegisteredReceiver())), 0, false); |
| assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); |
| |
| // Make the foreground broadcast as active. |
| queue.makeActiveNextPending(); |
| assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked()); |
| |
| queue.makeActiveIdle(); |
| assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); |
| |
| final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); |
| queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(airplane, |
| List.of(makeMockRegisteredReceiver())), 0, false); |
| |
| // Make the background broadcast as active. |
| queue.makeActiveNextPending(); |
| assertEquals(ProcessList.SCHED_GROUP_BACKGROUND, queue.getPreferredSchedulingGroupLocked()); |
| |
| queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(timeTick, |
| List.of(makeMockRegisteredReceiver())), 0, false); |
| // Even though the active broadcast is not a foreground one, scheduling group will be |
| // DEFAULT since there is a foreground broadcast waiting to be delivered. |
| assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked()); |
| } |
| |
| private Intent createPackageChangedIntent(int uid, List<String> componentNameList) { |
| final Intent packageChangedIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED); |
| packageChangedIntent.putExtra(Intent.EXTRA_UID, uid); |
| packageChangedIntent.putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, |
| componentNameList.toArray()); |
| return packageChangedIntent; |
| } |
| |
| private void verifyPendingRecords(BroadcastProcessQueue queue, |
| List<Intent> intents) { |
| for (int i = 0; i < intents.size(); i++) { |
| queue.makeActiveNextPending(); |
| |
| // While we're here, give our health check some test coverage |
| queue.assertHealthLocked(); |
| queue.dumpLocked(0L, new IndentingPrintWriter(Writer.nullWriter())); |
| |
| final Intent actualIntent = queue.getActive().intent; |
| final Intent expectedIntent = intents.get(i); |
| final String errMsg = "actual=" + actualIntent + ", expected=" + expectedIntent |
| + ", actual_extras=" + actualIntent.getExtras() |
| + ", expected_extras=" + expectedIntent.getExtras(); |
| assertTrue(errMsg, actualIntent.filterEquals(expectedIntent)); |
| assertBundleEquals(expectedIntent.getExtras(), actualIntent.getExtras()); |
| } |
| assertTrue(queue.isEmpty()); |
| } |
| |
| private void assertBundleEquals(Bundle expected, Bundle actual) { |
| final String errMsg = "expected=" + expected + ", actual=" + actual; |
| if (expected == actual) { |
| return; |
| } else if (expected == null || actual == null) { |
| fail(errMsg); |
| } |
| if (!expected.keySet().equals(actual.keySet())) { |
| fail(errMsg); |
| } |
| for (String key : expected.keySet()) { |
| final Object expectedValue = expected.get(key); |
| final Object actualValue = actual.get(key); |
| if (expectedValue == actualValue) { |
| continue; |
| } else if (expectedValue == null || actualValue == null) { |
| fail(errMsg); |
| } |
| assertEquals(errMsg, expectedValue.getClass(), actualValue.getClass()); |
| if (expectedValue.getClass().isArray()) { |
| assertEquals(errMsg, Array.getLength(expectedValue), Array.getLength(actualValue)); |
| for (int i = 0; i < Array.getLength(expectedValue); ++i) { |
| assertEquals(errMsg, Array.get(expectedValue, i), Array.get(actualValue, i)); |
| } |
| } else if (expectedValue instanceof ArrayList) { |
| final ArrayList<?> expectedList = (ArrayList<?>) expectedValue; |
| final ArrayList<?> actualList = (ArrayList<?>) actualValue; |
| assertEquals(errMsg, expectedList.size(), actualList.size()); |
| for (int i = 0; i < expectedList.size(); ++i) { |
| assertEquals(errMsg, expectedList.get(i), actualList.get(i)); |
| } |
| } else { |
| assertEquals(errMsg, expectedValue, actualValue); |
| } |
| } |
| } |
| } |