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