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