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.content.pm.PackageManager.NameNotFoundException;
23 import android.util.Log;
24 
25 import androidx.test.InstrumentationRegistry;
26 
27 import java.io.File;
28 import java.io.FileInputStream;
29 import java.io.IOException;
30 import java.math.BigInteger;
31 import java.security.MessageDigest;
32 import java.security.NoSuchAlgorithmException;
33 
34 /**
35  * Device-side utility class for PackageManager-related operations
36  */
37 public class PackageUtil {
38 
39     private static final String TAG = PackageUtil.class.getSimpleName();
40 
41     private static final int SYSTEM_APP_MASK =
42             ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
43     private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
44     private static final int READ_BLOCK_SIZE = 1024;
45 
46     /** Returns true if a package with the given name exists on the device */
exists(String packageName)47     public static boolean exists(String packageName) {
48         try {
49             return (getPackageManager().getApplicationInfo(packageName,
50                     PackageManager.GET_META_DATA) != null);
51         } catch(PackageManager.NameNotFoundException e) {
52             return false;
53         }
54     }
55 
56     /** Returns true if a package with the given name AND SHA digest exists on the device */
exists(String packageName, String sha)57     public static boolean exists(String packageName, String sha) {
58         try {
59             if (getPackageManager().getApplicationInfo(
60                     packageName, PackageManager.GET_META_DATA) == null) {
61                 return false;
62             }
63             return sha.equals(computePackageSignatureDigest(packageName));
64         } catch (NoSuchAlgorithmException | PackageManager.NameNotFoundException e) {
65             return false;
66         }
67     }
68 
69     /** Returns true if the app for the given package name is a system app for this device */
isSystemApp(String packageName)70     public static boolean isSystemApp(String packageName) {
71         try {
72             ApplicationInfo ai = getPackageManager().getApplicationInfo(packageName,
73                     PackageManager.GET_META_DATA);
74             return ai != null && ((ai.flags & SYSTEM_APP_MASK) != 0);
75         } catch(PackageManager.NameNotFoundException e) {
76             return false;
77         }
78     }
79 
80     /**
81      * Returns true if the app for the given package name is a privileged system app for this
82      * device
83      */
isPrivilegedSystemApp(String packageName)84     public static boolean isPrivilegedSystemApp(String packageName) {
85         try {
86             ApplicationInfo ai = getPackageManager().getApplicationInfo(packageName,
87                     PackageManager.GET_META_DATA);
88             return ai != null && ((ai.flags & SYSTEM_APP_MASK) != 0) && ai.isPrivilegedApp();
89         } catch(PackageManager.NameNotFoundException e) {
90             return false;
91         }
92     }
93 
94     /** Returns the version string of the package name, or null if the package can't be found */
getVersionString(String packageName)95     public static String getVersionString(String packageName) {
96         try {
97             PackageInfo info = getPackageManager().getPackageInfo(packageName,
98                     PackageManager.GET_META_DATA);
99             return info.versionName;
100         } catch (PackageManager.NameNotFoundException | NullPointerException e) {
101             Log.w(TAG, "Could not find version string for package " + packageName);
102             return null;
103         }
104     }
105 
106     /**
107      * Returns the version code for the package name, or null if the package can't be found.
108      * If before API Level 28, return a long version of the (otherwise deprecated) versionCode.
109      */
getLongVersionCode(String packageName)110     public static Long getLongVersionCode(String packageName) {
111         try {
112             PackageInfo info = getPackageManager().getPackageInfo(packageName,
113                     PackageManager.GET_META_DATA);
114             // Make no assumptions about the device's API level, and use the (now deprecated)
115             // versionCode for older devices.
116             return (ApiLevelUtil.isAtLeast(28)) ?
117                     info.getLongVersionCode() : (long) info.versionCode;
118         } catch (PackageManager.NameNotFoundException | NullPointerException e) {
119             Log.w(TAG, "Could not find version string for package " + packageName);
120             return null;
121         }
122     }
123 
124     /**
125      * Compute the signature SHA digest for a package.
126      * @param package the name of the package for which the signature SHA digest is requested
127      * @return the signature SHA digest
128      */
computePackageSignatureDigest(String packageName)129     public static String computePackageSignatureDigest(String packageName)
130             throws NoSuchAlgorithmException, PackageManager.NameNotFoundException {
131         PackageInfo packageInfo = getPackageManager()
132                 .getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
133         MessageDigest messageDigest = MessageDigest.getInstance("SHA256");
134         messageDigest.update(packageInfo.signatures[0].toByteArray());
135 
136         final byte[] digest = messageDigest.digest();
137         final int digestLength = digest.length;
138         final int charCount = 3 * digestLength - 1;
139 
140         final char[] chars = new char[charCount];
141         for (int i = 0; i < digestLength; i++) {
142             final int byteHex = digest[i] & 0xFF;
143             chars[i * 3] = HEX_ARRAY[byteHex >>> 4];
144             chars[i * 3 + 1] = HEX_ARRAY[byteHex & 0x0F];
145             if (i < digestLength - 1) {
146                 chars[i * 3 + 2] = ':';
147             }
148         }
149         return new String(chars);
150     }
151 
getPackageManager()152     private static PackageManager getPackageManager() {
153         return InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
154     }
155 
156 
157     /**
158      * Compute the file SHA digest for a package.
159      * @param packageInfo the info of the package for which the file SHA digest is requested
160      * @return the file SHA digest
161      */
computePackageFileDigest(PackageInfo pkgInfo)162     public static String computePackageFileDigest(PackageInfo pkgInfo) {
163         ApplicationInfo applicationInfo;
164         try {
165             applicationInfo = getPackageManager().getApplicationInfo(pkgInfo.packageName, 0);
166         } catch (NameNotFoundException e) {
167             Log.e(TAG, "Exception: " + e);
168             return null;
169         }
170         File apkFile = new File(applicationInfo.publicSourceDir);
171         return computeFileHash(apkFile);
172     }
173 
computeFileHash(File srcFile)174     private static String computeFileHash(File srcFile) {
175         MessageDigest md;
176         try {
177             md = MessageDigest.getInstance("SHA-256");
178         } catch (NoSuchAlgorithmException e) {
179             Log.e(TAG, "NoSuchAlgorithmException:" + e.getMessage());
180             return null;
181         }
182         String result =  null;
183         try (FileInputStream fis = new FileInputStream(srcFile)) {
184             byte[] dataBytes = new byte[READ_BLOCK_SIZE];
185             int nread = 0;
186             while ((nread = fis.read(dataBytes)) != -1) {
187                 md.update(dataBytes, 0, nread);
188             }
189             BigInteger bigInt = new BigInteger(1, md.digest());
190             result = String.format("%32s", bigInt.toString(16)).replace(' ', '0');
191         } catch (IOException e) {
192             Log.e(TAG, "IOException:" + e.getMessage());
193         }
194         return result;
195     }
196 
hasDeviceFeature(final String requiredFeature)197     private static boolean hasDeviceFeature(final String requiredFeature) {
198         return InstrumentationRegistry.getContext()
199                 .getPackageManager()
200                 .hasSystemFeature(requiredFeature);
201     }
202 
203     /**
204      * Rotation support is indicated by explicitly having both landscape and portrait
205      * features or not listing either at all.
206      */
supportsRotation()207     public static boolean supportsRotation() {
208         final boolean supportsLandscape = hasDeviceFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE);
209         final boolean supportsPortrait = hasDeviceFeature(PackageManager.FEATURE_SCREEN_PORTRAIT);
210         return (supportsLandscape && supportsPortrait)
211                 || (!supportsLandscape && !supportsPortrait);
212     }
213 }
214