1 /* 2 * Copyright (C) 2022 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.appenumeration.cts; 18 19 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 20 import static android.appenumeration.cts.Constants.ACTIVITY_CLASS_TEST; 21 import static android.appenumeration.cts.Constants.EXTRA_DATA; 22 import static android.appenumeration.cts.Constants.EXTRA_ERROR; 23 import static android.appenumeration.cts.Constants.EXTRA_ID; 24 import static android.appenumeration.cts.Constants.EXTRA_PENDING_INTENT; 25 import static android.appenumeration.cts.Constants.EXTRA_REMOTE_CALLBACK; 26 import static android.appenumeration.cts.Constants.EXTRA_REMOTE_READY_CALLBACK; 27 import static android.appenumeration.cts.Utils.Result; 28 import static android.appenumeration.cts.Utils.ThrowingBiFunction; 29 import static android.appenumeration.cts.Utils.ThrowingFunction; 30 import static android.os.Process.INVALID_UID; 31 32 import static org.hamcrest.MatcherAssert.assertThat; 33 import static org.hamcrest.Matchers.hasItemInArray; 34 import static org.hamcrest.Matchers.not; 35 import static org.hamcrest.Matchers.notNullValue; 36 37 import android.app.ActivityOptions; 38 import android.app.PendingIntent; 39 import android.content.ComponentName; 40 import android.content.Context; 41 import android.content.Intent; 42 import android.content.pm.PackageInstaller; 43 import android.content.pm.PackageManager; 44 import android.net.Uri; 45 import android.os.Bundle; 46 import android.os.ConditionVariable; 47 import android.os.Handler; 48 import android.os.HandlerThread; 49 import android.os.Parcelable; 50 import android.os.RemoteCallback; 51 52 import androidx.annotation.NonNull; 53 import androidx.annotation.Nullable; 54 import androidx.test.platform.app.InstrumentationRegistry; 55 56 import com.android.compatibility.common.util.AmUtils; 57 58 import org.junit.AfterClass; 59 import org.junit.BeforeClass; 60 import org.junit.Rule; 61 import org.junit.rules.TestName; 62 63 import java.util.List; 64 import java.util.Objects; 65 import java.util.UUID; 66 import java.util.concurrent.TimeUnit; 67 import java.util.concurrent.TimeoutException; 68 import java.util.concurrent.atomic.AtomicReference; 69 70 public class AppEnumerationTestsBase { 71 private static Handler sResponseHandler; 72 private static HandlerThread sResponseThread; 73 74 static Context sContext; 75 static PackageManager sPm; 76 77 static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(15); 78 static final long DEFAULT_TIMEOUT_WAIT_FOR_READY_MS = TimeUnit.SECONDS.toMillis(30); 79 80 81 @Rule 82 public TestName name = new TestName(); 83 84 @BeforeClass setup()85 public static void setup() { 86 sResponseThread = new HandlerThread("response"); 87 sResponseThread.start(); 88 sResponseHandler = new Handler(sResponseThread.getLooper()); 89 90 sContext = InstrumentationRegistry.getInstrumentation().getContext(); 91 sPm = sContext.getPackageManager(); 92 } 93 94 @AfterClass tearDown()95 public static void tearDown() { 96 sResponseThread.quit(); 97 } 98 sendCommand(@onNull String sourcePackageName, @Nullable String targetPackageName, int targetUid, @Nullable Parcelable intentExtra, String action, boolean waitForReady)99 Result sendCommand(@NonNull String sourcePackageName, @Nullable String targetPackageName, 100 int targetUid, @Nullable Parcelable intentExtra, String action, boolean waitForReady) 101 throws Exception { 102 final Intent intent = new Intent(action) 103 .setComponent(new ComponentName(sourcePackageName, ACTIVITY_CLASS_TEST)) 104 // data uri unique to each activity start to ensure actual launch and not just 105 // redisplay 106 .setData(Uri.parse("test://" + UUID.randomUUID().toString())) 107 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT); 108 if (targetPackageName != null) { 109 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, targetPackageName); 110 } 111 if (targetUid > INVALID_UID) { 112 intent.putExtra(Intent.EXTRA_UID, targetUid); 113 } 114 if (intentExtra != null) { 115 if (intentExtra instanceof Intent) { 116 intent.putExtra(Intent.EXTRA_INTENT, intentExtra); 117 } else if (intentExtra instanceof PendingIntent) { 118 intent.putExtra(EXTRA_PENDING_INTENT, intentExtra); 119 } else if (intentExtra instanceof Bundle) { 120 intent.putExtra(EXTRA_DATA, intentExtra); 121 } 122 } 123 124 final ConditionVariable latch = new ConditionVariable(); 125 final AtomicReference<Bundle> resultReference = new AtomicReference<>(); 126 final RemoteCallback callback = new RemoteCallback( 127 bundle -> { 128 resultReference.set(bundle); 129 latch.open(); 130 }, 131 sResponseHandler); 132 intent.putExtra(EXTRA_REMOTE_CALLBACK, callback); 133 if (waitForReady) { 134 AmUtils.waitForBroadcastBarrier(); 135 startAndWaitForCommandReady(intent); 136 } else { 137 final ActivityOptions options = ActivityOptions.makeBasic(); 138 options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN); 139 sContext.startActivity(intent, options.toBundle()); 140 } 141 return () -> { 142 final long latchTimeout = 143 waitForReady ? DEFAULT_TIMEOUT_WAIT_FOR_READY_MS : DEFAULT_TIMEOUT_MS; 144 if (!latch.block(latchTimeout)) { 145 throw new TimeoutException( 146 "Latch timed out while awaiting a response from " + sourcePackageName 147 + " after " + latchTimeout + "ms"); 148 } 149 final Bundle bundle = resultReference.get(); 150 if (bundle != null && bundle.containsKey(EXTRA_ERROR)) { 151 throw Objects.requireNonNull(bundle.getSerializable(EXTRA_ERROR, Exception.class)); 152 } 153 return bundle; 154 }; 155 } 156 startAndWaitForCommandReady(Intent intent)157 private void startAndWaitForCommandReady(Intent intent) throws Exception { 158 final ConditionVariable latchForReady = new ConditionVariable(); 159 final RemoteCallback readyCallback = new RemoteCallback(bundle -> latchForReady.open(), 160 sResponseHandler); 161 intent.putExtra(EXTRA_REMOTE_READY_CALLBACK, readyCallback); 162 final ActivityOptions options = ActivityOptions.makeBasic(); 163 options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN); 164 sContext.startActivity(intent, options.toBundle()); 165 if (!latchForReady.block(DEFAULT_TIMEOUT_MS)) { 166 throw new TimeoutException( 167 "Latch timed out while awaiting a response from command " + intent.getAction()); 168 } 169 } 170 sendCommandBlocking(@onNull String sourcePackageName, @Nullable String targetPackageName, @Nullable Parcelable intentExtra, String action)171 Bundle sendCommandBlocking(@NonNull String sourcePackageName, 172 @Nullable String targetPackageName, @Nullable Parcelable intentExtra, String action) 173 throws Exception { 174 final Result result = sendCommand(sourcePackageName, targetPackageName, 175 INVALID_UID /* targetUid */, intentExtra, action, false /* waitForReady */); 176 return result.await(); 177 } 178 sendCommandBlocking(@onNull String sourcePackageName, int targetUid, @Nullable Parcelable intentExtra, String action)179 Bundle sendCommandBlocking(@NonNull String sourcePackageName, int targetUid, 180 @Nullable Parcelable intentExtra, String action) 181 throws Exception { 182 final Result result = sendCommand(sourcePackageName, null /* targetPackageName */, 183 targetUid, intentExtra, action, false /* waitForReady */); 184 return result.await(); 185 } 186 assertVisible(String sourcePackageName, String targetPackageName, ThrowingFunction<String, String[]> commandMethod)187 void assertVisible(String sourcePackageName, String targetPackageName, 188 ThrowingFunction<String, String[]> commandMethod) throws Exception { 189 final String[] packageNames = commandMethod.apply(sourcePackageName); 190 assertThat("The list of package names should not be null", 191 packageNames, notNullValue()); 192 assertThat(sourcePackageName + " should be able to see " + targetPackageName, 193 packageNames, hasItemInArray(targetPackageName)); 194 } 195 assertNotVisible(String sourcePackageName, String targetPackageName, ThrowingFunction<String, String[]> commandMethod)196 void assertNotVisible(String sourcePackageName, String targetPackageName, 197 ThrowingFunction<String, String[]> commandMethod) throws Exception { 198 final String[] packageNames = commandMethod.apply(sourcePackageName); 199 assertThat("The list of package names should not be null", 200 packageNames, notNullValue()); 201 assertThat(sourcePackageName + " should not be able to see " + targetPackageName, 202 packageNames, not(hasItemInArray(targetPackageName))); 203 } 204 assertVisible(String sourcePackageName, String targetPackageName, ThrowingBiFunction<String, String, String[]> commandMethod)205 void assertVisible(String sourcePackageName, String targetPackageName, 206 ThrowingBiFunction<String, String, String[]> commandMethod) throws Exception { 207 final String[] packageNames = commandMethod.apply(sourcePackageName, targetPackageName); 208 assertThat(sourcePackageName + " should be able to see " + targetPackageName, 209 packageNames, hasItemInArray(targetPackageName)); 210 } 211 assertNotVisible(String sourcePackageName, String targetPackageName, ThrowingBiFunction<String, String, String[]> commandMethod)212 void assertNotVisible(String sourcePackageName, String targetPackageName, 213 ThrowingBiFunction<String, String, String[]> commandMethod) throws Exception { 214 final String[] packageNames = commandMethod.apply(sourcePackageName, targetPackageName); 215 assertThat(sourcePackageName + " should not be able to see " + targetPackageName, 216 packageNames, not(hasItemInArray(targetPackageName))); 217 } 218 getSessionInfos(String action, String sourcePackageName, int sessionId)219 Integer[] getSessionInfos(String action, String sourcePackageName, int sessionId) 220 throws Exception { 221 final Bundle extraData = new Bundle(); 222 extraData.putInt(EXTRA_ID, sessionId); 223 final Bundle response = sendCommandBlocking(sourcePackageName, null /* targetPackageName */, 224 extraData, action); 225 final List<PackageInstaller.SessionInfo> infos = response.getParcelableArrayList( 226 Intent.EXTRA_RETURN_RESULT, PackageInstaller.SessionInfo.class); 227 return infos.stream() 228 .map(i -> (i == null ? PackageInstaller.SessionInfo.INVALID_ID : i.getSessionId())) 229 .distinct() 230 .toArray(Integer[]::new); 231 } 232 } 233