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