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