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