1 /*
2  * Copyright (C) 2012 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 android.support.v4.content;
18 
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.pm.ApplicationInfo;
22 import android.content.res.ColorStateList;
23 import android.graphics.drawable.Drawable;
24 import android.os.Build;
25 import android.os.Bundle;
26 import android.os.Environment;
27 import android.os.Process;
28 import android.support.annotation.NonNull;
29 import android.support.v4.os.EnvironmentCompat;
30 import android.util.Log;
31 
32 import java.io.File;
33 
34 /**
35  * Helper for accessing features in {@link android.content.Context}
36  * introduced after API level 4 in a backwards compatible fashion.
37  */
38 public class ContextCompat {
39     private static final String TAG = "ContextCompat";
40 
41     private static final String DIR_ANDROID = "Android";
42     private static final String DIR_DATA = "data";
43     private static final String DIR_OBB = "obb";
44     private static final String DIR_FILES = "files";
45     private static final String DIR_CACHE = "cache";
46 
47     /**
48      * Start a set of activities as a synthesized task stack, if able.
49      *
50      * <p>In API level 11 (Android 3.0/Honeycomb) the recommended conventions for
51      * app navigation using the back key changed. The back key's behavior is local
52      * to the current task and does not capture navigation across different tasks.
53      * Navigating across tasks and easily reaching the previous task is accomplished
54      * through the "recents" UI, accessible through the software-provided Recents key
55      * on the navigation or system bar. On devices with the older hardware button configuration
56      * the recents UI can be accessed with a long press on the Home key.</p>
57      *
58      * <p>When crossing from one task stack to another post-Android 3.0,
59      * the application should synthesize a back stack/history for the new task so that
60      * the user may navigate out of the new task and back to the Launcher by repeated
61      * presses of the back key. Back key presses should not navigate across task stacks.</p>
62      *
63      * <p>startActivities provides a mechanism for constructing a synthetic task stack of
64      * multiple activities. If the underlying API is not available on the system this method
65      * will return false.</p>
66      *
67      * @param context Start activities using this activity as the starting context
68      * @param intents Array of intents defining the activities that will be started. The element
69      *                length-1 will correspond to the top activity on the resulting task stack.
70      * @return true if the underlying API was available and the call was successful, false otherwise
71      */
startActivities(Context context, Intent[] intents)72     public static boolean startActivities(Context context, Intent[] intents) {
73         return startActivities(context, intents, null);
74     }
75 
76     /**
77      * Start a set of activities as a synthesized task stack, if able.
78      *
79      * <p>In API level 11 (Android 3.0/Honeycomb) the recommended conventions for
80      * app navigation using the back key changed. The back key's behavior is local
81      * to the current task and does not capture navigation across different tasks.
82      * Navigating across tasks and easily reaching the previous task is accomplished
83      * through the "recents" UI, accessible through the software-provided Recents key
84      * on the navigation or system bar. On devices with the older hardware button configuration
85      * the recents UI can be accessed with a long press on the Home key.</p>
86      *
87      * <p>When crossing from one task stack to another post-Android 3.0,
88      * the application should synthesize a back stack/history for the new task so that
89      * the user may navigate out of the new task and back to the Launcher by repeated
90      * presses of the back key. Back key presses should not navigate across task stacks.</p>
91      *
92      * <p>startActivities provides a mechanism for constructing a synthetic task stack of
93      * multiple activities. If the underlying API is not available on the system this method
94      * will return false.</p>
95      *
96      * @param context Start activities using this activity as the starting context
97      * @param intents Array of intents defining the activities that will be started. The element
98      *                length-1 will correspond to the top activity on the resulting task stack.
99      * @param options Additional options for how the Activity should be started.
100      * See {@link android.content.Context#startActivity(Intent, android.os.Bundle)
101      * @return true if the underlying API was available and the call was successful, false otherwise
102      */
startActivities(Context context, Intent[] intents, Bundle options)103     public static boolean startActivities(Context context, Intent[] intents,
104             Bundle options) {
105         final int version = Build.VERSION.SDK_INT;
106         if (version >= 16) {
107             ContextCompatJellybean.startActivities(context, intents, options);
108             return true;
109         } else if (version >= 11) {
110             ContextCompatHoneycomb.startActivities(context, intents);
111             return true;
112         }
113         return false;
114     }
115 
116     /**
117      * Returns absolute paths to application-specific directories on all
118      * external storage devices where the application's OBB files (if there are
119      * any) can be found. Note if the application does not have any OBB files,
120      * these directories may not exist.
121      * <p>
122      * This is like {@link Context#getFilesDir()} in that these files will be
123      * deleted when the application is uninstalled, however there are some
124      * important differences:
125      * <ul>
126      * <li>External files are not always available: they will disappear if the
127      * user mounts the external storage on a computer or removes it.
128      * <li>There is no security enforced with these files.
129      * </ul>
130      * <p>
131      * External storage devices returned here are considered a permanent part of
132      * the device, including both emulated external storage and physical media
133      * slots, such as SD cards in a battery compartment. The returned paths do
134      * not include transient devices, such as USB flash drives.
135      * <p>
136      * An application may store data on any or all of the returned devices. For
137      * example, an app may choose to store large files on the device with the
138      * most available space, as measured by {@link android.os.StatFs}.
139      * <p>
140      * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
141      * are required to write to the returned paths; they're always accessible to
142      * the calling app. Before then,
143      * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} is required to
144      * write. Write access outside of these paths on secondary external storage
145      * devices is not available. To request external storage access in a
146      * backwards compatible way, consider using {@code android:maxSdkVersion}
147      * like this:
148      *
149      * <pre class="prettyprint">&lt;uses-permission
150      *     android:name="android.permission.WRITE_EXTERNAL_STORAGE"
151      *     android:maxSdkVersion="18" /&gt;</pre>
152      * <p>
153      * The first path returned is the same as {@link Context#getObbDir()}.
154      * Returned paths may be {@code null} if a storage device is unavailable.
155      *
156      * @see Context#getObbDir()
157      * @see EnvironmentCompat#getStorageState(File)
158      */
getObbDirs(Context context)159     public static File[] getObbDirs(Context context) {
160         final int version = Build.VERSION.SDK_INT;
161         if (version >= 19) {
162             return ContextCompatKitKat.getObbDirs(context);
163         } else {
164             final File single;
165             if (version >= 11) {
166                 single = ContextCompatHoneycomb.getObbDir(context);
167             } else {
168                 single = buildPath(Environment.getExternalStorageDirectory(), DIR_ANDROID, DIR_OBB,
169                         context.getPackageName());
170             }
171             return new File[] { single };
172         }
173     }
174 
175     /**
176      * Returns absolute paths to application-specific directories on all
177      * external storage devices where the application can place persistent files
178      * it owns. These files are internal to the application, and not typically
179      * visible to the user as media.
180      * <p>
181      * This is like {@link Context#getFilesDir()} in that these files will be
182      * deleted when the application is uninstalled, however there are some
183      * important differences:
184      * <ul>
185      * <li>External files are not always available: they will disappear if the
186      * user mounts the external storage on a computer or removes it.
187      * <li>There is no security enforced with these files.
188      * </ul>
189      * <p>
190      * External storage devices returned here are considered a permanent part of
191      * the device, including both emulated external storage and physical media
192      * slots, such as SD cards in a battery compartment. The returned paths do
193      * not include transient devices, such as USB flash drives.
194      * <p>
195      * An application may store data on any or all of the returned devices. For
196      * example, an app may choose to store large files on the device with the
197      * most available space, as measured by {@link android.os.StatFs}.
198      * <p>
199      * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
200      * are required to write to the returned paths; they're always accessible to
201      * the calling app. Before then,
202      * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} is required to
203      * write. Write access outside of these paths on secondary external storage
204      * devices is not available. To request external storage access in a
205      * backwards compatible way, consider using {@code android:maxSdkVersion}
206      * like this:
207      *
208      * <pre class="prettyprint">&lt;uses-permission
209      *     android:name="android.permission.WRITE_EXTERNAL_STORAGE"
210      *     android:maxSdkVersion="18" /&gt;</pre>
211      * <p>
212      * The first path returned is the same as
213      * {@link Context#getExternalFilesDir(String)}. Returned paths may be
214      * {@code null} if a storage device is unavailable.
215      *
216      * @see Context#getExternalFilesDir(String)
217      * @see EnvironmentCompat#getStorageState(File)
218      */
getExternalFilesDirs(Context context, String type)219     public static File[] getExternalFilesDirs(Context context, String type) {
220         final int version = Build.VERSION.SDK_INT;
221         if (version >= 19) {
222             return ContextCompatKitKat.getExternalFilesDirs(context, type);
223         } else {
224             final File single;
225             if (version >= 8) {
226                 single = ContextCompatFroyo.getExternalFilesDir(context, type);
227             } else {
228                 single = buildPath(Environment.getExternalStorageDirectory(), DIR_ANDROID, DIR_DATA,
229                         context.getPackageName(), DIR_FILES, type);
230             }
231             return new File[] { single };
232         }
233     }
234 
235     /**
236      * Returns absolute paths to application-specific directories on all
237      * external storage devices where the application can place cache files it
238      * owns. These files are internal to the application, and not typically
239      * visible to the user as media.
240      * <p>
241      * This is like {@link Context#getCacheDir()} in that these files will be
242      * deleted when the application is uninstalled, however there are some
243      * important differences:
244      * <ul>
245      * <li>External files are not always available: they will disappear if the
246      * user mounts the external storage on a computer or removes it.
247      * <li>There is no security enforced with these files.
248      * </ul>
249      * <p>
250      * External storage devices returned here are considered a permanent part of
251      * the device, including both emulated external storage and physical media
252      * slots, such as SD cards in a battery compartment. The returned paths do
253      * not include transient devices, such as USB flash drives.
254      * <p>
255      * An application may store data on any or all of the returned devices. For
256      * example, an app may choose to store large files on the device with the
257      * most available space, as measured by {@link android.os.StatFs}.
258      * <p>
259      * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
260      * are required to write to the returned paths; they're always accessible to
261      * the calling app. Before then,
262      * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} is required to
263      * write. Write access outside of these paths on secondary external storage
264      * devices is not available. To request external storage access in a
265      * backwards compatible way, consider using {@code android:maxSdkVersion}
266      * like this:
267      *
268      * <pre class="prettyprint">&lt;uses-permission
269      *     android:name="android.permission.WRITE_EXTERNAL_STORAGE"
270      *     android:maxSdkVersion="18" /&gt;</pre>
271      * <p>
272      * The first path returned is the same as
273      * {@link Context#getExternalCacheDir()}. Returned paths may be {@code null}
274      * if a storage device is unavailable.
275      *
276      * @see Context#getExternalCacheDir()
277      * @see EnvironmentCompat#getStorageState(File)
278      */
getExternalCacheDirs(Context context)279     public static File[] getExternalCacheDirs(Context context) {
280         final int version = Build.VERSION.SDK_INT;
281         if (version >= 19) {
282             return ContextCompatKitKat.getExternalCacheDirs(context);
283         } else {
284             final File single;
285             if (version >= 8) {
286                 single = ContextCompatFroyo.getExternalCacheDir(context);
287             } else {
288                 single = buildPath(Environment.getExternalStorageDirectory(), DIR_ANDROID, DIR_DATA,
289                         context.getPackageName(), DIR_CACHE);
290             }
291             return new File[] { single };
292         }
293     }
294 
buildPath(File base, String... segments)295     private static File buildPath(File base, String... segments) {
296         File cur = base;
297         for (String segment : segments) {
298             if (cur == null) {
299                 cur = new File(segment);
300             } else if (segment != null) {
301                 cur = new File(cur, segment);
302             }
303         }
304         return cur;
305     }
306 
307     /**
308      * Return a drawable object associated with a particular resource ID.
309      * <p>
310      * Starting in {@link android.os.Build.VERSION_CODES#LOLLIPOP}, the returned
311      * drawable will be styled for the specified Context's theme.
312      *
313      * @param id The desired resource identifier, as generated by the aapt tool.
314      *            This integer encodes the package, type, and resource entry.
315      *            The value 0 is an invalid identifier.
316      * @return Drawable An object that can be used to draw this resource.
317      */
getDrawable(Context context, int id)318     public static final Drawable getDrawable(Context context, int id) {
319         final int version = Build.VERSION.SDK_INT;
320         if (version >= 21) {
321             return ContextCompatApi21.getDrawable(context, id);
322         } else {
323             return context.getResources().getDrawable(id);
324         }
325     }
326 
327     /**
328      * Returns a color state list associated with a particular resource ID.
329      * <p>
330      * Starting in {@link android.os.Build.VERSION_CODES#M}, the returned
331      * color state list will be styled for the specified Context's theme.
332      *
333      * @param id The desired resource identifier, as generated by the aapt
334      *           tool. This integer encodes the package, type, and resource
335      *           entry. The value 0 is an invalid identifier.
336      * @return A color state list, or {@code null} if the resource could not be
337      *         resolved.
338      * @throws android.content.res.Resources.NotFoundException if the given ID
339      *         does not exist.
340      */
getColorStateList(Context context, int id)341     public static final ColorStateList getColorStateList(Context context, int id) {
342         final int version = Build.VERSION.SDK_INT;
343         if (version >= 23) {
344             return ContextCompatApi23.getColorStateList(context, id);
345         } else {
346             return context.getResources().getColorStateList(id);
347         }
348     }
349 
350     /**
351      * Returns a color associated with a particular resource ID
352      * <p>
353      * Starting in {@link android.os.Build.VERSION_CODES#M}, the returned
354      * color will be styled for the specified Context's theme.
355      *
356      * @param id The desired resource identifier, as generated by the aapt
357      *           tool. This integer encodes the package, type, and resource
358      *           entry. The value 0 is an invalid identifier.
359      * @return A single color value in the form 0xAARRGGBB.
360      * @throws android.content.res.Resources.NotFoundException if the given ID
361      *         does not exist.
362      */
getColor(Context context, int id)363     public static final int getColor(Context context, int id) {
364         final int version = Build.VERSION.SDK_INT;
365         if (version >= 23) {
366             return ContextCompatApi23.getColor(context, id);
367         } else {
368             return context.getResources().getColor(id);
369         }
370     }
371 
372     /**
373      * Determine whether <em>you</em> have been granted a particular permission.
374      *
375      * @param permission The name of the permission being checked.
376      *
377      * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if you have the
378      * permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} if not.
379      *
380      * @see android.content.pm.PackageManager#checkPermission(String, String)
381      */
checkSelfPermission(@onNull Context context, @NonNull String permission)382     public static int checkSelfPermission(@NonNull Context context, @NonNull String permission) {
383         if (permission == null) {
384             throw new IllegalArgumentException("permission is null");
385         }
386 
387         return context.checkPermission(permission, android.os.Process.myPid(), Process.myUid());
388     }
389 
390     /**
391      * Returns the absolute path to the directory on the filesystem similar to
392      * {@link Context#getFilesDir()}.  The difference is that files placed under this
393      * directory will be excluded from automatic backup to remote storage on
394      * devices running {@link android.os.Build.VERSION_CODES#LOLLIPOP} or later.  See
395      * {@link android.app.backup.BackupAgent BackupAgent} for a full discussion
396      * of the automatic backup mechanism in Android.
397      *
398      * <p>No permissions are required to read or write to the returned path, since this
399      * path is internal storage.
400      *
401      * @return The path of the directory holding application files that will not be
402      *         automatically backed up to remote storage.
403      *
404      * @see android.content.Context.getFilesDir
405      */
getNoBackupFilesDir(Context context)406     public final File getNoBackupFilesDir(Context context) {
407         final int version = Build.VERSION.SDK_INT;
408         if (version >= 21) {
409             return ContextCompatApi21.getNoBackupFilesDir(context);
410         } else {
411             ApplicationInfo appInfo = context.getApplicationInfo();
412             return createFilesDir(new File(appInfo.dataDir, "no_backup"));
413         }
414     }
415 
416     /**
417      * Returns the absolute path to the application specific cache directory on
418      * the filesystem designed for storing cached code. On devices running
419      * {@link android.os.Build.VERSION_CODES#LOLLIPOP} or later, the system will delete
420      * any files stored in this location both when your specific application is
421      * upgraded, and when the entire platform is upgraded.
422      * <p>
423      * This location is optimal for storing compiled or optimized code generated
424      * by your application at runtime.
425      * <p>
426      * Apps require no extra permissions to read or write to the returned path,
427      * since this path lives in their private storage.
428      *
429      * @return The path of the directory holding application code cache files.
430      */
getCodeCacheDir(Context context)431     public final File getCodeCacheDir(Context context) {
432         final int version = Build.VERSION.SDK_INT;
433         if (version >= 21) {
434             return ContextCompatApi21.getCodeCacheDir(context);
435         } else {
436             ApplicationInfo appInfo = context.getApplicationInfo();
437             return createFilesDir(new File(appInfo.dataDir, "code_cache"));
438         }
439     }
440 
createFilesDir(File file)441     private synchronized static File createFilesDir(File file) {
442         if (!file.exists()) {
443             if (!file.mkdirs()) {
444                 if (file.exists()) {
445                     // spurious failure; probably racing with another process for this app
446                     return file;
447                 }
448                 Log.w(TAG, "Unable to create files subdir " + file.getPath());
449                 return null;
450             }
451         }
452         return file;
453     }
454 }
455