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