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