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
17package com.android.messaging.util;
18
19import android.Manifest;
20import android.content.Context;
21import android.content.pm.PackageManager;
22import android.os.Build;
23import android.os.UserHandle;
24import android.os.UserManager;
25import android.support.v4.os.BuildCompat;
26
27import com.android.messaging.Factory;
28
29import java.util.ArrayList;
30import java.util.Hashtable;
31import java.util.Set;
32
33/**
34 * Android OS version utilities
35 */
36public 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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
137    public static int getApiVersion() {
138        return android.os.Build.VERSION.SDK_INT;
139    }
140
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     */
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     */
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 */
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
221    public static boolean hasPhonePermission() {
222        return hasPermission(Manifest.permission.READ_PHONE_STATE);
223    }
224
225    public static boolean hasSmsPermission() {
226        return hasPermission(Manifest.permission.READ_SMS);
227    }
228
229    public static boolean hasLocationPermission() {
230        return OsUtil.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION);
231    }
232
233
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
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     */
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. */
273    public static boolean hasRequiredPermissions() {
274        return hasPermissions(sRequiredPermissions);
275    }
276
277    public static String[] getMissingRequiredPermissions() {
278        return getMissingPermissions(sRequiredPermissions);
279    }
280}
281