| /* |
| * 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<String> using the given delimiter. |
| * @param values |
| * @param delimiter |
| * @return |
| */ |
| public static String joinFromSetWithDelimiter( |
| final Set<String> 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<String, Integer> sPermissions = new Hashtable<String, Integer>(); |
| |
| /** |
| * 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<String> missingList = new ArrayList<String>(); |
| 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); |
| } |
| } |