1 /*
2  * Copyright (C) 2017 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.compatibility.common.util;
18 
19 import android.content.pm.ApplicationInfo;
20 import android.content.pm.PackageInfo;
21 import android.content.pm.PackageManager;
22 import android.util.Log;
23 
24 import androidx.test.InstrumentationRegistry;
25 
26 import java.security.MessageDigest;
27 import java.security.NoSuchAlgorithmException;
28 
29 /**
30  * Device-side utility class for PackageManager-related operations
31  */
32 public class PackageUtil {
33 
34     private static final String TAG = PackageUtil.class.getSimpleName();
35 
36     private static final int SYSTEM_APP_MASK =
37             ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
38     private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
39 
40     /** Returns true if a package with the given name exists on the device */
exists(String packageName)41     public static boolean exists(String packageName) {
42         try {
43             return (getPackageManager().getApplicationInfo(packageName,
44                     PackageManager.GET_META_DATA) != null);
45         } catch(PackageManager.NameNotFoundException e) {
46             return false;
47         }
48     }
49 
50     /** Returns true if a package with the given name AND SHA digest exists on the device */
exists(String packageName, String sha)51     public static boolean exists(String packageName, String sha) {
52         try {
53             if (getPackageManager().getApplicationInfo(
54                     packageName, PackageManager.GET_META_DATA) == null) {
55                 return false;
56             }
57             return sha.equals(computePackageSignatureDigest(packageName));
58         } catch (NoSuchAlgorithmException | PackageManager.NameNotFoundException e) {
59             return false;
60         }
61     }
62 
63     /** Returns true if the app for the given package name is a system app for this device */
isSystemApp(String packageName)64     public static boolean isSystemApp(String packageName) {
65         try {
66             ApplicationInfo ai = getPackageManager().getApplicationInfo(packageName,
67                     PackageManager.GET_META_DATA);
68             return ai != null && ((ai.flags & SYSTEM_APP_MASK) != 0);
69         } catch(PackageManager.NameNotFoundException e) {
70             return false;
71         }
72     }
73 
74     /**
75      * Returns true if the app for the given package name is a privileged system app for this
76      * device
77      */
isPrivilegedSystemApp(String packageName)78     public static boolean isPrivilegedSystemApp(String packageName) {
79         try {
80             ApplicationInfo ai = getPackageManager().getApplicationInfo(packageName,
81                     PackageManager.GET_META_DATA);
82             return ai != null && ((ai.flags & SYSTEM_APP_MASK) != 0) && ai.isPrivilegedApp();
83         } catch(PackageManager.NameNotFoundException e) {
84             return false;
85         }
86     }
87 
88     /** Returns the version string of the package name, or null if the package can't be found */
getVersionString(String packageName)89     public static String getVersionString(String packageName) {
90         try {
91             PackageInfo info = getPackageManager().getPackageInfo(packageName,
92                     PackageManager.GET_META_DATA);
93             return info.versionName;
94         } catch (PackageManager.NameNotFoundException | NullPointerException e) {
95             Log.w(TAG, "Could not find version string for package " + packageName);
96             return null;
97         }
98     }
99 
100     /**
101      * Returns the version code for the package name, or null if the package can't be found.
102      * If before API Level 28, return a long version of the (otherwise deprecated) versionCode.
103      */
getLongVersionCode(String packageName)104     public static Long getLongVersionCode(String packageName) {
105         try {
106             PackageInfo info = getPackageManager().getPackageInfo(packageName,
107                     PackageManager.GET_META_DATA);
108             // Make no assumptions about the device's API level, and use the (now deprecated)
109             // versionCode for older devices.
110             return (ApiLevelUtil.isAtLeast(28)) ?
111                     info.getLongVersionCode() : (long) info.versionCode;
112         } catch (PackageManager.NameNotFoundException | NullPointerException e) {
113             Log.w(TAG, "Could not find version string for package " + packageName);
114             return null;
115         }
116     }
117 
118     /**
119      * Compute the signature SHA digest for a package.
120      * @param package the name of the package for which the signature SHA digest is requested
121      * @return the signature SHA digest
122      */
computePackageSignatureDigest(String packageName)123     public static String computePackageSignatureDigest(String packageName)
124             throws NoSuchAlgorithmException, PackageManager.NameNotFoundException {
125         PackageInfo packageInfo = getPackageManager()
126                 .getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
127         MessageDigest messageDigest = MessageDigest.getInstance("SHA256");
128         messageDigest.update(packageInfo.signatures[0].toByteArray());
129 
130         final byte[] digest = messageDigest.digest();
131         final int digestLength = digest.length;
132         final int charCount = 3 * digestLength - 1;
133 
134         final char[] chars = new char[charCount];
135         for (int i = 0; i < digestLength; i++) {
136             final int byteHex = digest[i] & 0xFF;
137             chars[i * 3] = HEX_ARRAY[byteHex >>> 4];
138             chars[i * 3 + 1] = HEX_ARRAY[byteHex & 0x0F];
139             if (i < digestLength - 1) {
140                 chars[i * 3 + 2] = ':';
141             }
142         }
143         return new String(chars);
144     }
145 
getPackageManager()146     private static PackageManager getPackageManager() {
147         return InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
148     }
149 
hasDeviceFeature(final String requiredFeature)150     private static boolean hasDeviceFeature(final String requiredFeature) {
151         return InstrumentationRegistry.getContext()
152                 .getPackageManager()
153                 .hasSystemFeature(requiredFeature);
154     }
155 
156     /**
157      * Rotation support is indicated by explicitly having both landscape and portrait
158      * features or not listing either at all.
159      */
supportsRotation()160     public static boolean supportsRotation() {
161         final boolean supportsLandscape = hasDeviceFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE);
162         final boolean supportsPortrait = hasDeviceFeature(PackageManager.FEATURE_SCREEN_PORTRAIT);
163         return (supportsLandscape && supportsPortrait)
164                 || (!supportsLandscape && !supportsPortrait);
165     }
166 }
167