• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.app.cts.broadcasts;
18 
19 import static com.android.app.cts.broadcasts.Common.TAG;
20 
21 import static com.google.common.truth.Truth.assertWithMessage;
22 
23 import static org.junit.Assert.assertNull;
24 
25 import android.app.ActivityManager;
26 import android.app.AppGlobals;
27 import android.content.BroadcastReceiver;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.ServiceConnection;
32 import android.os.Bundle;
33 import android.os.IBinder;
34 import android.os.Process;
35 import android.util.Log;
36 
37 import androidx.test.platform.app.InstrumentationRegistry;
38 
39 import com.android.app.cts.broadcasts.BroadcastReceipt;
40 import com.android.app.cts.broadcasts.ICommandReceiver;
41 import com.android.compatibility.common.util.AmUtils;
42 import com.android.compatibility.common.util.PropertyUtil;
43 import com.android.compatibility.common.util.SystemUtil;
44 import com.android.compatibility.common.util.TestUtils;
45 import com.android.compatibility.common.util.ThrowingSupplier;
46 
47 import com.google.common.base.Objects;
48 
49 import org.junit.After;
50 import org.junit.Before;
51 
52 import java.lang.reflect.Array;
53 import java.util.ArrayList;
54 import java.util.Arrays;
55 import java.util.Iterator;
56 import java.util.List;
57 import java.util.concurrent.ArrayBlockingQueue;
58 import java.util.concurrent.BlockingQueue;
59 import java.util.concurrent.LinkedBlockingQueue;
60 import java.util.concurrent.TimeUnit;
61 
62 abstract class BaseBroadcastTest {
63     protected static final long TIMEOUT_BIND_SERVICE_SEC = 2;
64 
65     protected static final long SHORT_FREEZER_TIMEOUT_MS = 5000;
66     protected static final long BROADCAST_RECEIVE_TIMEOUT_MS = 5000;
67 
68     protected static final String HELPER_PKG1 = "com.android.app.cts.broadcasts.helper";
69     protected static final String HELPER_PKG2 = "com.android.app.cts.broadcasts.helper2";
70     protected static final String HELPER_SERVICE = HELPER_PKG1 + ".TestService";
71 
72     protected static final String TEST_ACTION1 = "com.android.app.cts.TEST_ACTION1";
73 
74     protected static final String TEST_EXTRA1 = "com.android.app.cts.TEST_EXTRA1";
75 
76     protected static final String TEST_VALUE1 = "value1";
77     protected static final String TEST_VALUE2 = "value2";
78 
79     protected static final long BROADCAST_FORCED_DELAYED_DURATION_MS = 120_000;
80 
81     // TODO: Avoid hardcoding the device_config constant here.
82     protected static final String KEY_FREEZE_DEBOUNCE_TIMEOUT = "freeze_debounce_timeout";
83 
84     protected Context mContext;
85     protected ActivityManager mAm;
86 
87     @Before
setUp()88     public void setUp() {
89         mContext = getContext();
90         mAm = mContext.getSystemService(ActivityManager.class);
91         AmUtils.waitForBroadcastBarrier();
92     }
93 
94     @Before
unsetPackageStoppedState()95     public void unsetPackageStoppedState() {
96         // Bring test apps out of the stopped state so that they can receive broadcasts
97         SystemUtil.runWithShellPermissionIdentity(() -> {
98             for (String pkg : new String[] {HELPER_PKG1, HELPER_PKG2}) {
99                 AppGlobals.getPackageManager().setPackageStoppedState(pkg, false,
100                         Process.myUserHandle().getIdentifier());
101             }
102         });
103     }
104 
105     @After
tearDown()106     public void tearDown() throws Exception {
107         SystemUtil.runWithShellPermissionIdentity(() -> {
108             for (String pkg : new String[] {HELPER_PKG1, HELPER_PKG2}) {
109                 mAm.forceDelayBroadcastDelivery(pkg, 0);
110                 mAm.forceStopPackage(pkg);
111             }
112         });
113     }
114 
getContext()115     protected static Context getContext() {
116         return InstrumentationRegistry.getInstrumentation().getContext();
117     }
118 
forceDelayBroadcasts(String targetPackage)119     protected void forceDelayBroadcasts(String targetPackage) {
120         forceDelayBroadcasts(targetPackage, BROADCAST_FORCED_DELAYED_DURATION_MS);
121     }
122 
forceDelayBroadcasts(String targetPackage, long delayedDurationMs)123     private void forceDelayBroadcasts(String targetPackage, long delayedDurationMs) {
124         SystemUtil.runWithShellPermissionIdentity(() ->
125                 mAm.forceDelayBroadcastDelivery(targetPackage, delayedDurationMs));
126     }
127 
isModernBroadcastQueueEnabled()128     protected boolean isModernBroadcastQueueEnabled() {
129         return SystemUtil.runWithShellPermissionIdentity(() ->
130                 mAm.isModernBroadcastQueueEnabled());
131     }
132 
isAppFreezerEnabled()133     protected boolean isAppFreezerEnabled() throws Exception {
134         // TODO (269312428): Remove this check once isAppFreezerEnabled() is updated to take
135         // care of this.
136         if (!PropertyUtil.isVendorApiLevelNewerThan(30)) {
137             // Android R vendor partition contains those outdated cgroup configuration and freeze
138             // operations will fail.
139             return false;
140         }
141         final ActivityManager am = mContext.getSystemService(ActivityManager.class);
142         return am.getService().isAppFreezerEnabled();
143     }
144 
waitForProcessFreeze(int pid, long timeoutMs)145     protected void waitForProcessFreeze(int pid, long timeoutMs) {
146         // TODO: Add a listener to monitor freezer state changes.
147         SystemUtil.runWithShellPermissionIdentity(() -> {
148             TestUtils.waitUntil("Timed out waiting for test process to be frozen; pid=" + pid,
149                     (int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs),
150                     () -> mAm.isProcessFrozen(pid));
151         });
152     }
153 
isProcessFrozen(int pid)154     protected boolean isProcessFrozen(int pid) {
155         return SystemUtil.runWithShellPermissionIdentity(() -> mAm.isProcessFrozen(pid));
156     }
157 
runShellCmd(String cmdFormat, Object... args)158     protected String runShellCmd(String cmdFormat, Object... args) {
159         final String cmd = String.format(cmdFormat, args);
160         final String output = SystemUtil.runShellCommand(cmd);
161         Log.d(TAG, String.format("Output of '%s': '%s'", cmd, output));
162         return output;
163     }
164 
165     /**
166      * @param matchExact If {@code matchExact} is {@code true}, then it is verified that
167      *                   expected broadcasts exactly match the actual received broadcasts.
168      *                   Otherwise, it is verified that expected broadcasts are part of the
169      *                   actual received broadcasts.
170      */
verifyReceivedBroadcasts(ICommandReceiver cmdReceiver, String cookie, List<Intent> expectedBroadcasts, boolean matchExact, BroadcastReceiptVerifier verifier)171     protected void verifyReceivedBroadcasts(ICommandReceiver cmdReceiver, String cookie,
172             List<Intent> expectedBroadcasts, boolean matchExact,
173             BroadcastReceiptVerifier verifier) throws Exception {
174         verifyReceivedBroadcasts(() -> cmdReceiver.getReceivedBroadcasts(cookie),
175                 expectedBroadcasts, matchExact, verifier);
176     }
177 
178     /**
179      * @param matchExact If {@code matchExact} is {@code true}, then it is verified that
180      *                   expected broadcasts exactly match the actual received broadcasts.
181      *                   Otherwise, it is verified that expected broadcasts are part of the
182      *                   actual received broadcasts.
183      */
verifyReceivedBroadcasts( ThrowingSupplier<List<BroadcastReceipt>> actualBroadcastsSupplier, List<Intent> expectedBroadcasts, boolean matchExact)184     protected void verifyReceivedBroadcasts(
185             ThrowingSupplier<List<BroadcastReceipt>> actualBroadcastsSupplier,
186             List<Intent> expectedBroadcasts, boolean matchExact) throws Exception {
187         verifyReceivedBroadcasts(actualBroadcastsSupplier, expectedBroadcasts, matchExact, null);
188     }
189 
190     /**
191      * @param matchExact If {@code matchExact} is {@code true}, then it is verified that
192      *                   expected broadcasts exactly match the actual received broadcasts.
193      *                   Otherwise, it is verified that expected broadcasts are part of the
194      *                   actual received broadcasts.
195      */
verifyReceivedBroadcasts( ThrowingSupplier<List<BroadcastReceipt>> actualBroadcastsSupplier, List<Intent> expectedBroadcasts, boolean matchExact, BroadcastReceiptVerifier verifier)196     protected void verifyReceivedBroadcasts(
197             ThrowingSupplier<List<BroadcastReceipt>> actualBroadcastsSupplier,
198             List<Intent> expectedBroadcasts, boolean matchExact,
199             BroadcastReceiptVerifier verifier) throws Exception {
200         waitForBroadcastBarrier();
201 
202         final List<BroadcastReceipt> actualBroadcasts = actualBroadcastsSupplier.get();
203         final String errorMsg = "Expected: " + toString(expectedBroadcasts)
204                 + "; Actual: " + toString(actualBroadcasts);
205         if (matchExact) {
206             assertWithMessage(errorMsg).that(actualBroadcasts.size())
207                     .isEqualTo(expectedBroadcasts.size());
208             for (int i = 0; i < expectedBroadcasts.size(); ++i) {
209                 final Intent expected = expectedBroadcasts.get(i);
210                 final BroadcastReceipt receipt = actualBroadcasts.get(i);
211                 final Intent actual = receipt.intent;
212                 assertWithMessage(errorMsg).that(compareIntents(expected, actual))
213                         .isEqualTo(true);
214                 if (verifier != null) {
215                     assertNull(verifier.verify(receipt));
216                 }
217             }
218         } else {
219             assertWithMessage(errorMsg).that(actualBroadcasts.size())
220                     .isAtLeast(expectedBroadcasts.size());
221             final Iterator<BroadcastReceipt> it = actualBroadcasts.iterator();
222             int countMatches = 0;
223             while (it.hasNext()) {
224                 final BroadcastReceipt receipt = it.next();
225                 final Intent actual = receipt.intent;
226                 final Intent expected = expectedBroadcasts.get(countMatches);
227                 if (compareIntents(expected, actual)
228                         && (verifier == null || verifier.verify(receipt) == null)) {
229                     countMatches++;
230                 }
231                 if (countMatches == expectedBroadcasts.size()) {
232                     break;
233                 }
234             }
235             assertWithMessage(errorMsg).that(countMatches).isEqualTo(expectedBroadcasts.size());
236         }
237     }
238 
239     @FunctionalInterface
240     public interface BroadcastReceiptVerifier {
verify(BroadcastReceipt receipt)241         String verify(BroadcastReceipt receipt);
242     }
243 
toString(List<T> list)244     private static <T> String toString(List<T> list) {
245         return list == null ? null : Arrays.toString(list.toArray());
246     }
247 
compareIntents(Intent expected, Intent actual)248     private boolean compareIntents(Intent expected, Intent actual) {
249         if (!actual.getAction().equals(expected.getAction())) {
250             return false;
251         }
252         if (!compareExtras(expected.getExtras(), actual.getExtras())) {
253             return false;
254         }
255         return true;
256     }
257 
compareExtras(Bundle expected, Bundle actual)258     private boolean compareExtras(Bundle expected, Bundle actual) {
259         if (expected == actual) {
260             return true;
261         } else if (expected == null || actual == null) {
262             return false;
263         }
264         if (!expected.keySet().equals(actual.keySet())) {
265             return false;
266         }
267         for (String key : expected.keySet()) {
268             final Object expectedValue = expected.get(key);
269             final Object actualValue = actual.get(key);
270             if (expectedValue == actualValue) {
271                 continue;
272             } else if (expectedValue == null || actualValue == null) {
273                 return false;
274             }
275             if (actualValue.getClass() != expectedValue.getClass()) {
276                 return false;
277             }
278             if (expectedValue.getClass().isArray()) {
279                 if (Array.getLength(actualValue) != Array.getLength(expectedValue)) {
280                     return false;
281                 }
282                 for (int i = 0; i < Array.getLength(expectedValue); ++i) {
283                     if (!Objects.equal(Array.get(actualValue, i), Array.get(expectedValue, i))) {
284                         return false;
285                     }
286                 }
287             } else if (expectedValue instanceof ArrayList) {
288                 final ArrayList<?> expectedList = (ArrayList<?>) expectedValue;
289                 final ArrayList<?> actualList = (ArrayList<?>) actualValue;
290                 if (actualList.size() != expectedList.size()) {
291                     return false;
292                 }
293                 for (int i = 0; i < expectedList.size(); ++i) {
294                     if (!Objects.equal(actualList.get(i), expectedList.get(i))) {
295                         return false;
296                     }
297                 }
298             } else {
299                 if (!Objects.equal(actualValue, expectedValue)) {
300                     return false;
301                 }
302             }
303         }
304         return true;
305     }
306 
waitForBroadcastBarrier()307     protected void waitForBroadcastBarrier() {
308         SystemUtil.runCommandAndPrintOnLogcat(TAG,
309                 "cmd activity wait-for-broadcast-barrier --flush-application-threads");
310     }
311 
bindToHelperService(String packageName)312     protected TestServiceConnection bindToHelperService(String packageName) {
313         final TestServiceConnection
314                 connection = new TestServiceConnection(mContext);
315         final Intent intent = new Intent().setComponent(
316                 new ComponentName(packageName, HELPER_SERVICE));
317         mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
318         return connection;
319     }
320 
321     protected class TestServiceConnection implements ServiceConnection {
322         private final Context mContext;
323         private final BlockingQueue<IBinder> mBlockingQueue = new LinkedBlockingQueue<>();
324         private ICommandReceiver mCommandReceiver;
325 
TestServiceConnection(Context context)326         TestServiceConnection(Context context) {
327             mContext = context;
328         }
329 
onServiceConnected(ComponentName componentName, IBinder service)330         public void onServiceConnected(ComponentName componentName, IBinder service) {
331             Log.i(TAG, "Service got connected: " + componentName);
332             mBlockingQueue.offer(service);
333         }
334 
onServiceDisconnected(ComponentName componentName)335         public void onServiceDisconnected(ComponentName componentName) {
336             Log.e(TAG, "Service got disconnected: " + componentName);
337         }
338 
getService()339         private IBinder getService() throws Exception {
340             final IBinder service = mBlockingQueue.poll(TIMEOUT_BIND_SERVICE_SEC,
341                     TimeUnit.SECONDS);
342             return service;
343         }
344 
getCommandReceiver()345         public ICommandReceiver getCommandReceiver() throws Exception {
346             if (mCommandReceiver == null) {
347                 mCommandReceiver = ICommandReceiver.Stub.asInterface(getService());
348             }
349             return mCommandReceiver;
350         }
351 
unbind()352         public void unbind() {
353             if (mCommandReceiver != null) {
354                 mContext.unbindService(this);
355             }
356             mCommandReceiver = null;
357         }
358     }
359 
360     static class ResultReceiver extends BroadcastReceiver {
361         private final BlockingQueue<String> mResultData = new ArrayBlockingQueue<>(1);
362 
363         @Override
onReceive(Context context, Intent intent)364         public void onReceive(Context context, Intent intent) {
365             mResultData.offer(getResultData());
366         }
367 
getResult()368         public String getResult() throws Exception {
369             return mResultData.poll(BROADCAST_RECEIVE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
370         }
371     }
372 }
373