/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.messaging.util; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.os.Build; import android.os.UserHandle; import android.os.UserManager; import androidx.core.os.BuildCompat; import com.android.messaging.Factory; import java.util.ArrayList; import java.util.Hashtable; import java.util.Set; /** * Android OS version utilities */ public class OsUtil { private static boolean sIsAtLeastICS_MR1; private static boolean sIsAtLeastJB; private static boolean sIsAtLeastJB_MR1; private static boolean sIsAtLeastJB_MR2; private static boolean sIsAtLeastKLP; private static boolean sIsAtLeastL; private static boolean sIsAtLeastL_MR1; private static boolean sIsAtLeastM; private static boolean sIsAtLeastN; private static Boolean sIsSecondaryUser = null; static { final int v = getApiVersion(); sIsAtLeastICS_MR1 = v >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1; sIsAtLeastJB = v >= android.os.Build.VERSION_CODES.JELLY_BEAN; sIsAtLeastJB_MR1 = v >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; sIsAtLeastJB_MR2 = v >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; sIsAtLeastKLP = v >= android.os.Build.VERSION_CODES.KITKAT; sIsAtLeastL = v >= android.os.Build.VERSION_CODES.LOLLIPOP; sIsAtLeastL_MR1 = v >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1; sIsAtLeastM = v >= android.os.Build.VERSION_CODES.M; sIsAtLeastN = BuildCompat.isAtLeastN(); } /** * @return True if the version of Android that we're running on is at least Ice Cream Sandwich * MR1 (API level 15). */ public static boolean isAtLeastICS_MR1() { return sIsAtLeastICS_MR1; } /** * @return True if the version of Android that we're running on is at least Jelly Bean * (API level 16). */ public static boolean isAtLeastJB() { return sIsAtLeastJB; } /** * @return True if the version of Android that we're running on is at least Jelly Bean MR1 * (API level 17). */ public static boolean isAtLeastJB_MR1() { return sIsAtLeastJB_MR1; } /** * @return True if the version of Android that we're running on is at least Jelly Bean MR2 * (API level 18). */ public static boolean isAtLeastJB_MR2() { return sIsAtLeastJB_MR2; } /** * @return True if the version of Android that we're running on is at least KLP * (API level 19). */ public static boolean isAtLeastKLP() { return sIsAtLeastKLP; } /** * @return True if the version of Android that we're running on is at least L * (API level 21). */ public static boolean isAtLeastL() { return sIsAtLeastL; } /** * @return True if the version of Android that we're running on is at least L MR1 * (API level 22). */ public static boolean isAtLeastL_MR1() { return sIsAtLeastL_MR1; } /** * @return True if the version of Android that we're running on is at least M * (API level 23). */ public static boolean isAtLeastM() { return sIsAtLeastM; } /** * @return True if the version of Android that we're running on is at least N * (API level 24). */ public static boolean isAtLeastN() { return sIsAtLeastN; } /** * @return The Android API version of the OS that we're currently running on. */ public static int getApiVersion() { return android.os.Build.VERSION.SDK_INT; } public static boolean isSecondaryUser() { if (sIsSecondaryUser == null) { final Context context = Factory.get().getApplicationContext(); boolean isSecondaryUser = false; // Only check for newer devices (but not the nexus 10) if (OsUtil.sIsAtLeastJB_MR1 && !"Nexus 10".equals(Build.MODEL)) { final UserHandle uh = android.os.Process.myUserHandle(); final UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); if (userManager != null) { final long userSerialNumber = userManager.getSerialNumberForUser(uh); isSecondaryUser = (0 != userSerialNumber); } } sIsSecondaryUser = isSecondaryUser; } return sIsSecondaryUser; } /** * Creates a joined string from a Set using the given delimiter. * @param values * @param delimiter * @return */ public static String joinFromSetWithDelimiter( final Set values, final String delimiter) { if (values != null) { final StringBuilder joinedStringBuilder = new StringBuilder(); boolean firstValue = true; for (final String value : values) { if (firstValue) { firstValue = false; } else { joinedStringBuilder.append(delimiter); } joinedStringBuilder.append(value); } return joinedStringBuilder.toString(); } return null; } private static Hashtable sPermissions = new Hashtable(); /** * Check if the app has the specified permission. If it does not, the app needs to use * {@link android.app.Activity#requestPermission}. Note that if it * returns true, it cannot return false in the same process as the OS kills the process when * any permission is revoked. * @param permission A permission from {@link android.Manifest.permission} */ public static boolean hasPermission(final String permission) { if (OsUtil.isAtLeastM()) { // It is safe to cache the PERMISSION_GRANTED result as the process gets killed if the // user revokes the permission setting. However, PERMISSION_DENIED should not be // cached as the process does not get killed if the user enables the permission setting. if (!sPermissions.containsKey(permission) || sPermissions.get(permission) == PackageManager.PERMISSION_DENIED) { final Context context = Factory.get().getApplicationContext(); final int permissionState = context.checkSelfPermission(permission); sPermissions.put(permission, permissionState); } return sPermissions.get(permission) == PackageManager.PERMISSION_GRANTED; } else { return true; } } /** Does the app have all the specified permissions */ public static boolean hasPermissions(final String[] permissions) { for (final String permission : permissions) { if (!hasPermission(permission)) { return false; } } return true; } public static boolean hasPhonePermission() { return hasPermission(Manifest.permission.READ_PHONE_STATE); } public static boolean hasSmsPermission() { return hasPermission(Manifest.permission.READ_SMS); } public static boolean hasLocationPermission() { return OsUtil.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION); } public static boolean hasStoragePermission() { // Note that READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE are granted or denied // together. return OsUtil.hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE); } public static boolean hasRecordAudioPermission() { return OsUtil.hasPermission(Manifest.permission.RECORD_AUDIO); } /** * Returns array with the set of permissions that have not been granted from the given set. * The array will be empty if the app has all of the specified permissions. Note that calling * {@link Activity#requestPermissions} for an already granted permission can prompt the user * again, and its up to the app to only request permissions that are missing. */ public static String[] getMissingPermissions(final String[] permissions) { final ArrayList missingList = new ArrayList(); for (final String permission : permissions) { if (!hasPermission(permission)) { missingList.add(permission); } } final String[] missingArray = new String[missingList.size()]; missingList.toArray(missingArray); return missingArray; } private static String[] sRequiredPermissions = new String[] { // Required to read existing SMS threads Manifest.permission.READ_SMS, // Required for knowing the phone number, number of SIMs, etc. Manifest.permission.READ_PHONE_STATE, // This is not strictly required, but simplifies the contact picker scenarios Manifest.permission.READ_CONTACTS, }; /** Does the app have the minimum set of permissions required to operate. */ public static boolean hasRequiredPermissions() { return hasPermissions(sRequiredPermissions); } public static String[] getMissingRequiredPermissions() { return getMissingPermissions(sRequiredPermissions); } }