• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 com.android.cts.install.lib;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assert.fail;
22 
23 import android.app.UiAutomation;
24 import android.content.BroadcastReceiver;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.pm.ApplicationInfo;
29 import android.content.pm.PackageInfo;
30 import android.content.pm.PackageInstaller;
31 import android.content.pm.PackageManager;
32 import android.os.Handler;
33 import android.os.HandlerThread;
34 import android.os.SystemClock;
35 
36 import androidx.test.InstrumentationRegistry;
37 
38 import com.android.modules.utils.build.SdkLevel;
39 
40 import com.google.common.annotations.VisibleForTesting;
41 
42 import java.io.IOException;
43 import java.lang.reflect.Field;
44 import java.util.List;
45 import java.util.concurrent.BlockingQueue;
46 import java.util.concurrent.CountDownLatch;
47 import java.util.concurrent.LinkedBlockingQueue;
48 
49 /**
50  * Utilities to facilitate installation in tests.
51  */
52 public class InstallUtils {
53     private static final int NUM_MAX_POLLS = 5;
54     private static final int POLL_WAIT_TIME_MILLIS = 200;
55     private static final long GET_UIAUTOMATION_TIMEOUT_MS = 60000;
56     private static final String CLASS_NAME_PROCESS_BROADCAST =
57             "com.android.cts.install.lib.testapp.ProcessBroadcast";
58 
getUiAutomation()59     private static UiAutomation getUiAutomation() {
60         final long start = SystemClock.uptimeMillis();
61         while (SystemClock.uptimeMillis() - start < GET_UIAUTOMATION_TIMEOUT_MS) {
62             UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation();
63             if (ui != null) {
64                 return ui;
65             }
66         }
67         throw new AssertionError("Failed to get UiAutomation");
68     }
69 
70     /**
71      * Adopts the given shell permissions.
72      */
adoptShellPermissionIdentity(String... permissions)73     public static void adoptShellPermissionIdentity(String... permissions) {
74         getUiAutomation().adoptShellPermissionIdentity(permissions);
75     }
76 
77     /**
78      * Drops all shell permissions.
79      */
dropShellPermissionIdentity()80     public static void dropShellPermissionIdentity() {
81         getUiAutomation().dropShellPermissionIdentity();
82     }
83     /**
84      * Returns the version of the given package installed on device.
85      * Returns -1 if the package is not currently installed.
86      */
getInstalledVersion(String packageName)87     public static long getInstalledVersion(String packageName) {
88         Context context = InstrumentationRegistry.getTargetContext();
89         PackageManager pm = context.getPackageManager();
90         try {
91             if (SdkLevel.isAtLeastT()) {
92                 PackageInfo info = pm.getPackageInfo(packageName,
93                         PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
94                 return info.getLongVersionCode();
95             } else {
96                 PackageInfo info = pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
97                 return info.getLongVersionCode();
98             }
99         } catch (PackageManager.NameNotFoundException e) {
100             return -1;
101         }
102     }
103 
104     /**
105      * Returns the info for the given package name.
106      */
getPackageInfo(String packageName)107     public static PackageInfo getPackageInfo(String packageName) {
108         Context context = InstrumentationRegistry.getTargetContext();
109         PackageManager pm = context.getPackageManager();
110         try {
111             if (SdkLevel.isAtLeastT()) {
112                 return pm.getPackageInfo(packageName,
113                         PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
114             } else {
115                 return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
116             }
117         } catch (PackageManager.NameNotFoundException e) {
118             return null;
119         }
120     }
121 
122     /**
123      * Returns the PackageInstaller instance of the current {@code Context}
124      */
getPackageInstaller()125     public static PackageInstaller getPackageInstaller() {
126         return InstrumentationRegistry.getTargetContext().getPackageManager().getPackageInstaller();
127     }
128 
129     /**
130      * Returns an existing session to actively perform work.
131      * {@see PackageInstaller#openSession}
132      */
openPackageInstallerSession(int sessionId)133     public static PackageInstaller.Session openPackageInstallerSession(int sessionId)
134             throws IOException {
135         return getPackageInstaller().openSession(sessionId);
136     }
137 
138     /**
139      * Asserts that {@code result} intent has a success status.
140      */
assertStatusSuccess(Intent result)141     public static void assertStatusSuccess(Intent result) {
142         int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
143                 PackageInstaller.STATUS_FAILURE);
144         if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) {
145             throw new AssertionError("PENDING USER ACTION");
146         } else if (status != PackageInstaller.STATUS_SUCCESS) {
147             String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
148             throw new AssertionError(message == null ? "UNKNOWN FAILURE" : message);
149         }
150     }
151 
152     /**
153      * Asserts that {@code result} intent has a failure status.
154      */
assertStatusFailure(Intent result)155     public static void assertStatusFailure(Intent result) {
156         // Pass SUCCESS as default to ensure that this doesn't accidentally pass
157         int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
158                 PackageInstaller.STATUS_SUCCESS);
159         switch (status) {
160             case -2: // PackageInstaller.STATUS_PENDING_STREAMING
161             case PackageInstaller.STATUS_PENDING_USER_ACTION:
162                 throw new AssertionError("PENDING " + status);
163             case PackageInstaller.STATUS_SUCCESS:
164                 throw new AssertionError("INCORRECT SUCCESS ");
165             case PackageInstaller.STATUS_FAILURE:
166             case PackageInstaller.STATUS_FAILURE_BLOCKED:
167             case PackageInstaller.STATUS_FAILURE_ABORTED:
168             case PackageInstaller.STATUS_FAILURE_INVALID:
169             case PackageInstaller.STATUS_FAILURE_CONFLICT:
170             case PackageInstaller.STATUS_FAILURE_STORAGE:
171             case PackageInstaller.STATUS_FAILURE_INCOMPATIBLE:
172             case PackageInstaller.STATUS_FAILURE_TIMEOUT:
173                 break;
174             default:
175                 String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
176                 throw new AssertionError(message == null ? "UNKNOWN STATUS" : message);
177         }
178     }
179 
180     /**
181      * Commits {@link Install} but expects to fail.
182      *
183      * @param expectedThrowableClass class or superclass of the expected throwable.
184      *
185      */
commitExpectingFailure(Class expectedThrowableClass, String expectedFailMessage, Install install)186     public static void commitExpectingFailure(Class expectedThrowableClass,
187             String expectedFailMessage, Install install) {
188         assertThrows(expectedThrowableClass, expectedFailMessage, () -> install.commit());
189     }
190 
191     /**
192      * Mutates {@code installFlags} field of {@code params} by adding {@code
193      * additionalInstallFlags} to it.
194      */
195     @VisibleForTesting
mutateInstallFlags(PackageInstaller.SessionParams params, int additionalInstallFlags)196     public static void mutateInstallFlags(PackageInstaller.SessionParams params,
197             int additionalInstallFlags) {
198         final Class<?> clazz = params.getClass();
199         Field installFlagsField;
200         try {
201             installFlagsField = clazz.getDeclaredField("installFlags");
202         } catch (NoSuchFieldException e) {
203             throw new AssertionError("Unable to reflect over SessionParams.installFlags", e);
204         }
205 
206         try {
207             int flags = installFlagsField.getInt(params);
208             flags |= additionalInstallFlags;
209             installFlagsField.setAccessible(true);
210             installFlagsField.setInt(params, flags);
211         } catch (IllegalAccessException e) {
212             throw new AssertionError("Unable to reflect over SessionParams.installFlags", e);
213         }
214     }
215 
216     private static final String NO_RESPONSE = "NO RESPONSE";
217 
218     /**
219      * Calls into the test app to process user data.
220      * Asserts if the user data could not be processed or was version
221      * incompatible with the previously processed user data.
222      */
processUserData(String packageName)223     public static void processUserData(String packageName) {
224         Intent intent = new Intent();
225         intent.setComponent(new ComponentName(packageName, CLASS_NAME_PROCESS_BROADCAST));
226         intent.setAction("PROCESS_USER_DATA");
227         Context context = InstrumentationRegistry.getTargetContext();
228 
229         HandlerThread handlerThread = new HandlerThread("RollbackTestHandlerThread");
230         handlerThread.start();
231 
232         // It can sometimes take a while after rollback before the app will
233         // receive this broadcast, so try a few times in a loop.
234         String result = NO_RESPONSE;
235         for (int i = 0; i < NUM_MAX_POLLS; ++i) {
236             BlockingQueue<String> resultQueue = new LinkedBlockingQueue<>();
237             context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
238                 @Override
239                 public void onReceive(Context context, Intent intent) {
240                     if (getResultCode() == 1) {
241                         resultQueue.add("OK");
242                     } else {
243                         // If the test app doesn't receive the broadcast or
244                         // fails to set the result data, then getResultData
245                         // here returns the initial NO_RESPONSE data passed to
246                         // the sendOrderedBroadcast call.
247                         resultQueue.add(getResultData());
248                     }
249                 }
250             }, new Handler(handlerThread.getLooper()), 0, NO_RESPONSE, null);
251 
252             try {
253                 result = resultQueue.take();
254                 if (!result.equals(NO_RESPONSE)) {
255                     break;
256                 }
257                 Thread.sleep(POLL_WAIT_TIME_MILLIS);
258             } catch (InterruptedException e) {
259                 throw new AssertionError(e);
260             }
261         }
262 
263         assertThat(result).isEqualTo("OK");
264     }
265 
266     /**
267      * Retrieves the app's user data version from userdata.txt.
268      * @return -1 if userdata.txt doesn't exist or -2 if the app doesn't handle the broadcast which
269      * could happen when the app crashes or doesn't start at all.
270      */
getUserDataVersion(String packageName)271     public static int getUserDataVersion(String packageName) {
272         Intent intent = new Intent();
273         intent.setComponent(new ComponentName(packageName, CLASS_NAME_PROCESS_BROADCAST));
274         intent.setAction("GET_USER_DATA_VERSION");
275         Context context = InstrumentationRegistry.getTargetContext();
276 
277         HandlerThread handlerThread = new HandlerThread("RollbackTestHandlerThread");
278         handlerThread.start();
279 
280         // The response code returned when the broadcast is not received by the app or when the app
281         // crashes during handling the broadcast. We will retry when this code is returned.
282         final int noResponse = -2;
283         // It can sometimes take a while after rollback before the app will
284         // receive this broadcast, so try a few times in a loop.
285         BlockingQueue<Integer> resultQueue = new LinkedBlockingQueue<>();
286         for (int i = 0; i < NUM_MAX_POLLS; ++i) {
287             context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
288                 @Override
289                 public void onReceive(Context context, Intent intent) {
290                     resultQueue.add(getResultCode());
291                 }
292             }, new Handler(handlerThread.getLooper()), noResponse, null, null);
293 
294             try {
295                 int userDataVersion = resultQueue.take();
296                 if (userDataVersion != noResponse) {
297                     return userDataVersion;
298                 }
299                 Thread.sleep(POLL_WAIT_TIME_MILLIS);
300             } catch (InterruptedException e) {
301                 throw new AssertionError(e);
302             }
303         }
304 
305         return noResponse;
306     }
307 
sendBroadcastAndWait(Intent intent)308     private static void sendBroadcastAndWait(Intent intent) throws Exception {
309         var handlerThread = new HandlerThread("TestHandlerThread");
310         handlerThread.start();
311 
312         var latch = new CountDownLatch(1);
313         var context = InstrumentationRegistry.getTargetContext();
314         context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
315             @Override
316             public void onReceive(Context context, Intent intent) {
317                 latch.countDown();
318             }
319         }, new Handler(handlerThread.getLooper()), 0, null, null);
320         latch.await();
321     }
322 
323     /**
324      * Tells the app to request audio focus.
325      */
requestAudioFocus(String packageName)326     public static void requestAudioFocus(String packageName) throws Exception {
327         Intent intent = new Intent();
328         intent.setComponent(new ComponentName(packageName, CLASS_NAME_PROCESS_BROADCAST));
329         intent.setAction("REQUEST_AUDIO_FOCUS");
330         sendBroadcastAndWait(intent);
331     }
332 
333     /**
334      * Tells the app to abandon audio focus.
335      */
abandonAudioFocus(String packageName)336     public static void abandonAudioFocus(String packageName) throws Exception {
337         Intent intent = new Intent();
338         intent.setComponent(new ComponentName(packageName, CLASS_NAME_PROCESS_BROADCAST));
339         intent.setAction("ABANDON_AUDIO_FOCUS");
340         sendBroadcastAndWait(intent);
341     }
342 
343     /**
344      * Checks whether the given package is installed on /system and was not updated.
345      */
isSystemAppWithoutUpdate(String packageName)346     static boolean isSystemAppWithoutUpdate(String packageName) {
347         PackageInfo pi = getPackageInfo(packageName);
348         if (pi == null) {
349             return false;
350         } else {
351             return ((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0)
352                     && ((pi.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0);
353         }
354     }
355 
356     /**
357      * Checks whether a given package is installed for only the given user, from a list of users.
358      * @param packageName the package to check
359      * @param userIdToCheck the user id of the user to check
360      * @param userIds a list of user ids to check
361      * @return {@code true} if the package is only installed for the given user,
362      *         {@code false} otherwise.
363      */
isOnlyInstalledForUser(String packageName, int userIdToCheck, List<Integer> userIds)364     public static boolean isOnlyInstalledForUser(String packageName, int userIdToCheck,
365             List<Integer> userIds) {
366         Context context = InstrumentationRegistry.getTargetContext();
367         PackageManager pm = context.getPackageManager();
368         for (int userId: userIds) {
369             List<PackageInfo> installedPackages;
370             if (userId != userIdToCheck) {
371                 installedPackages = pm.getInstalledPackagesAsUser(
372                         PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX),
373                         userId);
374                 for (PackageInfo pi : installedPackages) {
375                     if (pi.packageName.equals(packageName)) {
376                         return false;
377                     }
378                 }
379             }
380         }
381         return true;
382     }
383 
384     /**
385      * Returns the session by session Id, or null if no session is found.
386      */
getStagedSessionInfo(int sessionId)387     public static PackageInstaller.SessionInfo getStagedSessionInfo(int sessionId) {
388         PackageInstaller packageInstaller = getPackageInstaller();
389         for (PackageInstaller.SessionInfo session : packageInstaller.getStagedSessions()) {
390             if (session.getSessionId() == sessionId) {
391                 return session;
392             }
393         }
394         return null;
395     }
396 
397     /**
398      * Assert that the given staged session is abandoned. The method assumes that the given session
399      * is staged.
400      * @param sessionId of the staged session
401      */
assertStagedSessionIsAbandoned(int sessionId)402     public static void assertStagedSessionIsAbandoned(int sessionId) {
403         assertThat(getStagedSessionInfo(sessionId)).isNull();
404     }
405 
406     /**
407      * A functional interface representing an operation that takes no arguments,
408      * returns no arguments and might throw a {@link Throwable} of any kind.
409      */
410     @FunctionalInterface
411     private interface Operation {
412         /**
413          * This is the method that gets called for any object that implements this interface.
414          */
run()415         void run() throws Throwable;
416     }
417 
418     /**
419      * Runs {@link Operation} and expects a {@link Throwable} of the given class to be thrown.
420      *
421      * @param expectedThrowableClass class or superclass of the expected throwable.
422      */
assertThrows(Class expectedThrowableClass, String expectedFailMessage, Operation operation)423     private static void assertThrows(Class expectedThrowableClass, String expectedFailMessage,
424             Operation operation) {
425         try {
426             operation.run();
427         } catch (Throwable expected) {
428             assertThat(expectedThrowableClass.isAssignableFrom(expected.getClass())).isTrue();
429             assertThat(expected.getMessage()).containsMatch(expectedFailMessage);
430             return;
431         }
432         fail("Operation was expected to fail!");
433     }
434 }
435