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.SystemUtil;
43 import com.android.compatibility.common.util.TestUtils;
44 import com.android.compatibility.common.util.ThrowingSupplier;
45 
46 import com.google.common.base.Objects;
47 
48 import org.junit.After;
49 import org.junit.Before;
50 
51 import java.lang.reflect.Array;
52 import java.util.ArrayList;
53 import java.util.Arrays;
54 import java.util.Iterator;
55 import java.util.List;
56 import java.util.concurrent.ArrayBlockingQueue;
57 import java.util.concurrent.BlockingQueue;
58 import java.util.concurrent.LinkedBlockingQueue;
59 import java.util.concurrent.TimeUnit;
60 
61 abstract class BaseBroadcastTest {
62     protected static final long TIMEOUT_BIND_SERVICE_SEC = 2;
63 
64     protected static final long SHORT_FREEZER_TIMEOUT_MS = 5000;
65     protected static final long BROADCAST_RECEIVE_TIMEOUT_MS = 5000;
66 
67     protected static final String HELPER_PKG1 = "com.android.app.cts.broadcasts.helper";
68     protected static final String HELPER_PKG2 = "com.android.app.cts.broadcasts.helper2";
69     protected static final String HELPER_SERVICE = HELPER_PKG1 + ".TestService";
70 
71     protected static final String TEST_ACTION1 = "com.android.app.cts.TEST_ACTION1";
72 
73     protected static final String TEST_EXTRA1 = "com.android.app.cts.TEST_EXTRA1";
74 
75     protected static final String TEST_VALUE1 = "value1";
76     protected static final String TEST_VALUE2 = "value2";
77 
78     protected static final long BROADCAST_FORCED_DELAYED_DURATION_MS = 120_000;
79 
80     // TODO: Avoid hardcoding the device_config constant here.
81     protected static final String KEY_FREEZE_DEBOUNCE_TIMEOUT = "freeze_debounce_timeout";
82 
83     protected Context mContext;
84     protected ActivityManager mAm;
85 
86     @Before
setUp()87     public void setUp() {
88         mContext = getContext();
89         mAm = mContext.getSystemService(ActivityManager.class);
90         AmUtils.waitForBroadcastBarrier();
91     }
92 
93     @Before
unsetPackageStoppedState()94     public void unsetPackageStoppedState() {
95         // Bring test apps out of the stopped state so that they can receive broadcasts
96         SystemUtil.runWithShellPermissionIdentity(() -> {
97             for (String pkg : new String[] {HELPER_PKG1, HELPER_PKG2}) {
98                 AppGlobals.getPackageManager().setPackageStoppedState(pkg, false,
99                         Process.myUserHandle().getIdentifier());
100             }
101         });
102     }
103 
104     @After
tearDown()105     public void tearDown() throws Exception {
106         SystemUtil.runWithShellPermissionIdentity(() -> {
107             for (String pkg : new String[] {HELPER_PKG1, HELPER_PKG2}) {
108                 mAm.forceDelayBroadcastDelivery(pkg, 0);
109                 mAm.forceStopPackage(pkg);
110             }
111         });
112     }
113 
getContext()114     protected static Context getContext() {
115         return InstrumentationRegistry.getInstrumentation().getContext();
116     }
117 
forceDelayBroadcasts(String targetPackage)118     protected void forceDelayBroadcasts(String targetPackage) {
119         forceDelayBroadcasts(targetPackage, BROADCAST_FORCED_DELAYED_DURATION_MS);
120     }
121 
forceDelayBroadcasts(String targetPackage, long delayedDurationMs)122     private void forceDelayBroadcasts(String targetPackage, long delayedDurationMs) {
123         SystemUtil.runWithShellPermissionIdentity(() ->
124                 mAm.forceDelayBroadcastDelivery(targetPackage, delayedDurationMs));
125     }
126 
isAppFreezerEnabled()127     protected boolean isAppFreezerEnabled() throws Exception {
128         final ActivityManager am = mContext.getSystemService(ActivityManager.class);
129         return am.getService().isAppFreezerEnabled();
130     }
131 
waitForProcessFreeze(int pid, long timeoutMs)132     protected void waitForProcessFreeze(int pid, long timeoutMs) {
133         // TODO: Add a listener to monitor freezer state changes.
134         SystemUtil.runWithShellPermissionIdentity(() -> {
135             TestUtils.waitUntil("Timed out waiting for test process to be frozen; pid=" + pid,
136                     (int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs),
137                     () -> mAm.isProcessFrozen(pid));
138         });
139     }
140 
isProcessFrozen(int pid)141     protected boolean isProcessFrozen(int pid) {
142         return SystemUtil.runWithShellPermissionIdentity(() -> mAm.isProcessFrozen(pid));
143     }
144 
runShellCmd(String cmdFormat, Object... args)145     protected String runShellCmd(String cmdFormat, Object... args) {
146         final String cmd = String.format(cmdFormat, args);
147         final String output = SystemUtil.runShellCommand(cmd);
148         Log.d(TAG, String.format("Output of '%s': '%s'", cmd, output));
149         return output;
150     }
151 
152     /**
153      * @param matchExact If {@code matchExact} is {@code true}, then it is verified that
154      *                   expected broadcasts exactly match the actual received broadcasts.
155      *                   Otherwise, it is verified that expected broadcasts are part of the
156      *                   actual received broadcasts.
157      */
verifyReceivedBroadcasts(ICommandReceiver cmdReceiver, String cookie, List<Intent> expectedBroadcasts, boolean matchExact, BroadcastReceiptVerifier verifier)158     protected void verifyReceivedBroadcasts(ICommandReceiver cmdReceiver, String cookie,
159             List<Intent> expectedBroadcasts, boolean matchExact,
160             BroadcastReceiptVerifier verifier) throws Exception {
161         verifyReceivedBroadcasts(() -> cmdReceiver.getReceivedBroadcasts(cookie),
162                 expectedBroadcasts, matchExact, verifier);
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( ThrowingSupplier<List<BroadcastReceipt>> actualBroadcastsSupplier, List<Intent> expectedBroadcasts, boolean matchExact)171     protected void verifyReceivedBroadcasts(
172             ThrowingSupplier<List<BroadcastReceipt>> actualBroadcastsSupplier,
173             List<Intent> expectedBroadcasts, boolean matchExact) throws Exception {
174         verifyReceivedBroadcasts(actualBroadcastsSupplier, expectedBroadcasts, matchExact, null);
175     }
176 
177     /**
178      * @param matchExact If {@code matchExact} is {@code true}, then it is verified that
179      *                   expected broadcasts exactly match the actual received broadcasts.
180      *                   Otherwise, it is verified that expected broadcasts are part of the
181      *                   actual received broadcasts.
182      */
verifyReceivedBroadcasts( ThrowingSupplier<List<BroadcastReceipt>> actualBroadcastsSupplier, List<Intent> expectedBroadcasts, boolean matchExact, BroadcastReceiptVerifier verifier)183     protected void verifyReceivedBroadcasts(
184             ThrowingSupplier<List<BroadcastReceipt>> actualBroadcastsSupplier,
185             List<Intent> expectedBroadcasts, boolean matchExact,
186             BroadcastReceiptVerifier verifier) throws Exception {
187         waitForBroadcastBarrier();
188 
189         final List<BroadcastReceipt> actualBroadcasts = actualBroadcastsSupplier.get();
190         final String errorMsg = "Expected: " + toString(expectedBroadcasts)
191                 + "; Actual: " + toString(actualBroadcasts);
192         if (matchExact) {
193             assertWithMessage(errorMsg).that(actualBroadcasts.size())
194                     .isEqualTo(expectedBroadcasts.size());
195             for (int i = 0; i < expectedBroadcasts.size(); ++i) {
196                 final Intent expected = expectedBroadcasts.get(i);
197                 final BroadcastReceipt receipt = actualBroadcasts.get(i);
198                 final Intent actual = receipt.intent;
199                 assertWithMessage(errorMsg).that(compareIntents(expected, actual))
200                         .isEqualTo(true);
201                 if (verifier != null) {
202                     assertNull(verifier.verify(receipt));
203                 }
204             }
205         } else {
206             assertWithMessage(errorMsg).that(actualBroadcasts.size())
207                     .isAtLeast(expectedBroadcasts.size());
208             final Iterator<BroadcastReceipt> it = actualBroadcasts.iterator();
209             int countMatches = 0;
210             while (it.hasNext()) {
211                 final BroadcastReceipt receipt = it.next();
212                 final Intent actual = receipt.intent;
213                 final Intent expected = expectedBroadcasts.get(countMatches);
214                 if (compareIntents(expected, actual)
215                         && (verifier == null || verifier.verify(receipt) == null)) {
216                     countMatches++;
217                 }
218                 if (countMatches == expectedBroadcasts.size()) {
219                     break;
220                 }
221             }
222             assertWithMessage(errorMsg).that(countMatches).isEqualTo(expectedBroadcasts.size());
223         }
224     }
225 
226     @FunctionalInterface
227     public interface BroadcastReceiptVerifier {
verify(BroadcastReceipt receipt)228         String verify(BroadcastReceipt receipt);
229     }
230 
toString(List<T> list)231     private static <T> String toString(List<T> list) {
232         return list == null ? null : Arrays.toString(list.toArray());
233     }
234 
compareIntents(Intent expected, Intent actual)235     private boolean compareIntents(Intent expected, Intent actual) {
236         if (!actual.getAction().equals(expected.getAction())) {
237             return false;
238         }
239         if (!compareExtras(expected.getExtras(), actual.getExtras())) {
240             return false;
241         }
242         return true;
243     }
244 
compareExtras(Bundle expected, Bundle actual)245     private boolean compareExtras(Bundle expected, Bundle actual) {
246         if (expected == actual) {
247             return true;
248         } else if (expected == null || actual == null) {
249             return false;
250         }
251         if (!expected.keySet().equals(actual.keySet())) {
252             return false;
253         }
254         for (String key : expected.keySet()) {
255             final Object expectedValue = expected.get(key);
256             final Object actualValue = actual.get(key);
257             if (expectedValue == actualValue) {
258                 continue;
259             } else if (expectedValue == null || actualValue == null) {
260                 return false;
261             }
262             if (actualValue.getClass() != expectedValue.getClass()) {
263                 return false;
264             }
265             if (expectedValue.getClass().isArray()) {
266                 if (Array.getLength(actualValue) != Array.getLength(expectedValue)) {
267                     return false;
268                 }
269                 for (int i = 0; i < Array.getLength(expectedValue); ++i) {
270                     if (!Objects.equal(Array.get(actualValue, i), Array.get(expectedValue, i))) {
271                         return false;
272                     }
273                 }
274             } else if (expectedValue instanceof ArrayList) {
275                 final ArrayList<?> expectedList = (ArrayList<?>) expectedValue;
276                 final ArrayList<?> actualList = (ArrayList<?>) actualValue;
277                 if (actualList.size() != expectedList.size()) {
278                     return false;
279                 }
280                 for (int i = 0; i < expectedList.size(); ++i) {
281                     if (!Objects.equal(actualList.get(i), expectedList.get(i))) {
282                         return false;
283                     }
284                 }
285             } else {
286                 if (!Objects.equal(actualValue, expectedValue)) {
287                     return false;
288                 }
289             }
290         }
291         return true;
292     }
293 
waitForBroadcastBarrier()294     protected void waitForBroadcastBarrier() {
295         SystemUtil.runCommandAndPrintOnLogcat(TAG,
296                 "cmd activity wait-for-broadcast-barrier --flush-application-threads");
297     }
298 
bindToHelperService(String packageName)299     protected TestServiceConnection bindToHelperService(String packageName) {
300         final TestServiceConnection
301                 connection = new TestServiceConnection(mContext);
302         final Intent intent = new Intent().setComponent(
303                 new ComponentName(packageName, HELPER_SERVICE));
304         mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
305         return connection;
306     }
307 
308     protected class TestServiceConnection implements ServiceConnection {
309         private final Context mContext;
310         private final BlockingQueue<IBinder> mBlockingQueue = new LinkedBlockingQueue<>();
311         private ICommandReceiver mCommandReceiver;
312 
TestServiceConnection(Context context)313         TestServiceConnection(Context context) {
314             mContext = context;
315         }
316 
onServiceConnected(ComponentName componentName, IBinder service)317         public void onServiceConnected(ComponentName componentName, IBinder service) {
318             Log.i(TAG, "Service got connected: " + componentName);
319             mBlockingQueue.offer(service);
320         }
321 
onServiceDisconnected(ComponentName componentName)322         public void onServiceDisconnected(ComponentName componentName) {
323             Log.e(TAG, "Service got disconnected: " + componentName);
324         }
325 
getService()326         public IBinder getService() throws Exception {
327             final IBinder service = mBlockingQueue.poll(TIMEOUT_BIND_SERVICE_SEC,
328                     TimeUnit.SECONDS);
329             return service;
330         }
331 
getCommandReceiver()332         public ICommandReceiver getCommandReceiver() throws Exception {
333             if (mCommandReceiver == null) {
334                 mCommandReceiver = ICommandReceiver.Stub.asInterface(getService());
335             }
336             return mCommandReceiver;
337         }
338 
unbind()339         public void unbind() {
340             if (mCommandReceiver != null) {
341                 mContext.unbindService(this);
342             }
343             mCommandReceiver = null;
344         }
345     }
346 
347     static class ResultReceiver extends BroadcastReceiver {
348         private final BlockingQueue<String> mResultData = new ArrayBlockingQueue<>(1);
349 
350         @Override
onReceive(Context context, Intent intent)351         public void onReceive(Context context, Intent intent) {
352             mResultData.offer(getResultData());
353         }
354 
getResult()355         public String getResult() throws Exception {
356             return mResultData.poll(BROADCAST_RECEIVE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
357         }
358     }
359 }
360