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