1 /* 2 * Copyright (C) 2015 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.messaging.util; 18 19 import android.Manifest; 20 import android.content.Context; 21 import android.content.pm.PackageManager; 22 import android.os.Build; 23 import android.os.UserHandle; 24 import android.os.UserManager; 25 26 import com.android.messaging.Factory; 27 28 import java.util.ArrayList; 29 import java.util.Hashtable; 30 import java.util.Set; 31 32 /** 33 * Android OS version utilities 34 */ 35 public class OsUtil { 36 private static boolean sIsAtLeastICS_MR1; 37 private static boolean sIsAtLeastJB; 38 private static boolean sIsAtLeastJB_MR1; 39 private static boolean sIsAtLeastJB_MR2; 40 private static boolean sIsAtLeastKLP; 41 private static boolean sIsAtLeastL; 42 private static boolean sIsAtLeastL_MR1; 43 private static boolean sIsAtLeastM; 44 45 private static Boolean sIsSecondaryUser = null; 46 47 static { 48 final int v = getApiVersion(); 49 sIsAtLeastICS_MR1 = v >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1; 50 sIsAtLeastJB = v >= android.os.Build.VERSION_CODES.JELLY_BEAN; 51 sIsAtLeastJB_MR1 = v >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; 52 sIsAtLeastJB_MR2 = v >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; 53 sIsAtLeastKLP = v >= android.os.Build.VERSION_CODES.KITKAT; 54 sIsAtLeastL = v >= android.os.Build.VERSION_CODES.LOLLIPOP; 55 sIsAtLeastL_MR1 = v >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1; 56 sIsAtLeastM = v >= android.os.Build.VERSION_CODES.M; 57 } 58 59 /** 60 * @return True if the version of Android that we're running on is at least Ice Cream Sandwich 61 * MR1 (API level 15). 62 */ isAtLeastICS_MR1()63 public static boolean isAtLeastICS_MR1() { 64 return sIsAtLeastICS_MR1; 65 } 66 67 /** 68 * @return True if the version of Android that we're running on is at least Jelly Bean 69 * (API level 16). 70 */ isAtLeastJB()71 public static boolean isAtLeastJB() { 72 return sIsAtLeastJB; 73 } 74 75 /** 76 * @return True if the version of Android that we're running on is at least Jelly Bean MR1 77 * (API level 17). 78 */ isAtLeastJB_MR1()79 public static boolean isAtLeastJB_MR1() { 80 return sIsAtLeastJB_MR1; 81 } 82 83 /** 84 * @return True if the version of Android that we're running on is at least Jelly Bean MR2 85 * (API level 18). 86 */ isAtLeastJB_MR2()87 public static boolean isAtLeastJB_MR2() { 88 return sIsAtLeastJB_MR2; 89 } 90 91 /** 92 * @return True if the version of Android that we're running on is at least KLP 93 * (API level 19). 94 */ isAtLeastKLP()95 public static boolean isAtLeastKLP() { 96 return sIsAtLeastKLP; 97 } 98 99 /** 100 * @return True if the version of Android that we're running on is at least L 101 * (API level 21). 102 */ isAtLeastL()103 public static boolean isAtLeastL() { 104 return sIsAtLeastL; 105 } 106 107 /** 108 * @return True if the version of Android that we're running on is at least L MR1 109 * (API level 22). 110 */ isAtLeastL_MR1()111 public static boolean isAtLeastL_MR1() { 112 return sIsAtLeastL_MR1; 113 } 114 115 /** 116 * @return True if the version of Android that we're running on is at least M 117 * (API level 23). 118 */ isAtLeastM()119 public static boolean isAtLeastM() { 120 return sIsAtLeastM; 121 } 122 123 /** 124 * @return The Android API version of the OS that we're currently running on. 125 */ getApiVersion()126 public static int getApiVersion() { 127 return android.os.Build.VERSION.SDK_INT; 128 } 129 isSecondaryUser()130 public static boolean isSecondaryUser() { 131 if (sIsSecondaryUser == null) { 132 final Context context = Factory.get().getApplicationContext(); 133 boolean isSecondaryUser = false; 134 135 // Only check for newer devices (but not the nexus 10) 136 if (OsUtil.sIsAtLeastJB_MR1 && !"Nexus 10".equals(Build.MODEL)) { 137 final UserHandle uh = android.os.Process.myUserHandle(); 138 final UserManager userManager = 139 (UserManager) context.getSystemService(Context.USER_SERVICE); 140 if (userManager != null) { 141 final long userSerialNumber = userManager.getSerialNumberForUser(uh); 142 isSecondaryUser = (0 != userSerialNumber); 143 } 144 } 145 sIsSecondaryUser = isSecondaryUser; 146 } 147 return sIsSecondaryUser; 148 } 149 150 /** 151 * Creates a joined string from a Set<String> using the given delimiter. 152 * @param values 153 * @param delimiter 154 * @return 155 */ joinFromSetWithDelimiter( final Set<String> values, final String delimiter)156 public static String joinFromSetWithDelimiter( 157 final Set<String> values, final String delimiter) { 158 if (values != null) { 159 final StringBuilder joinedStringBuilder = new StringBuilder(); 160 boolean firstValue = true; 161 for (final String value : values) { 162 if (firstValue) { 163 firstValue = false; 164 } else { 165 joinedStringBuilder.append(delimiter); 166 } 167 joinedStringBuilder.append(value); 168 } 169 return joinedStringBuilder.toString(); 170 } 171 return null; 172 } 173 174 private static Hashtable<String, Integer> sPermissions = new Hashtable<String, Integer>(); 175 176 /** 177 * Check if the app has the specified permission. If it does not, the app needs to use 178 * {@link android.app.Activity#requestPermission}. Note that if it 179 * returns true, it cannot return false in the same process as the OS kills the process when 180 * any permission is revoked. 181 * @param permission A permission from {@link android.Manifest.permission} 182 */ hasPermission(final String permission)183 public static boolean hasPermission(final String permission) { 184 if (OsUtil.isAtLeastM()) { 185 // It is safe to cache the PERMISSION_GRANTED result as the process gets killed if the 186 // user revokes the permission setting. However, PERMISSION_DENIED should not be 187 // cached as the process does not get killed if the user enables the permission setting. 188 if (!sPermissions.containsKey(permission) 189 || sPermissions.get(permission) == PackageManager.PERMISSION_DENIED) { 190 final Context context = Factory.get().getApplicationContext(); 191 final int permissionState = context.checkSelfPermission(permission); 192 sPermissions.put(permission, permissionState); 193 } 194 return sPermissions.get(permission) == PackageManager.PERMISSION_GRANTED; 195 } else { 196 return true; 197 } 198 } 199 200 /** Does the app have all the specified permissions */ hasPermissions(final String[] permissions)201 public static boolean hasPermissions(final String[] permissions) { 202 for (final String permission : permissions) { 203 if (!hasPermission(permission)) { 204 return false; 205 } 206 } 207 return true; 208 } 209 hasPhonePermission()210 public static boolean hasPhonePermission() { 211 return hasPermission(Manifest.permission.READ_PHONE_STATE); 212 } 213 hasSmsPermission()214 public static boolean hasSmsPermission() { 215 return hasPermission(Manifest.permission.READ_SMS); 216 } 217 hasLocationPermission()218 public static boolean hasLocationPermission() { 219 return OsUtil.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION); 220 } 221 222 hasStoragePermission()223 public static boolean hasStoragePermission() { 224 // Note that READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE are granted or denied 225 // together. 226 return OsUtil.hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE); 227 } 228 hasRecordAudioPermission()229 public static boolean hasRecordAudioPermission() { 230 return OsUtil.hasPermission(Manifest.permission.RECORD_AUDIO); 231 } 232 233 /** 234 * Returns array with the set of permissions that have not been granted from the given set. 235 * The array will be empty if the app has all of the specified permissions. Note that calling 236 * {@link Activity#requestPermissions} for an already granted permission can prompt the user 237 * again, and its up to the app to only request permissions that are missing. 238 */ getMissingPermissions(final String[] permissions)239 public static String[] getMissingPermissions(final String[] permissions) { 240 final ArrayList<String> missingList = new ArrayList<String>(); 241 for (final String permission : permissions) { 242 if (!hasPermission(permission)) { 243 missingList.add(permission); 244 } 245 } 246 247 final String[] missingArray = new String[missingList.size()]; 248 missingList.toArray(missingArray); 249 return missingArray; 250 } 251 252 private static String[] sRequiredPermissions = new String[] { 253 // Required to read existing SMS threads 254 Manifest.permission.READ_SMS, 255 // Required for knowing the phone number, number of SIMs, etc. 256 Manifest.permission.READ_PHONE_STATE, 257 // This is not strictly required, but simplifies the contact picker scenarios 258 Manifest.permission.READ_CONTACTS, 259 }; 260 261 /** Does the app have the minimum set of permissions required to operate. */ hasRequiredPermissions()262 public static boolean hasRequiredPermissions() { 263 return hasPermissions(sRequiredPermissions); 264 } 265 getMissingRequiredPermissions()266 public static String[] getMissingRequiredPermissions() { 267 return getMissingPermissions(sRequiredPermissions); 268 } 269 } 270