/* * Copyright (C) 2012 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 android.support.v4.content; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Process; import android.support.annotation.NonNull; import android.support.v4.os.BuildCompat; import android.support.v4.os.EnvironmentCompat; import android.util.Log; import android.util.TypedValue; import java.io.File; /** * Helper for accessing features in {@link android.content.Context} * introduced after API level 4 in a backwards compatible fashion. */ public class ContextCompat { private static final String TAG = "ContextCompat"; private static final String DIR_ANDROID = "Android"; private static final String DIR_DATA = "data"; private static final String DIR_OBB = "obb"; private static final String DIR_FILES = "files"; private static final String DIR_CACHE = "cache"; private static final Object sLock = new Object(); private static TypedValue sTempValue; /** * Start a set of activities as a synthesized task stack, if able. * *
In API level 11 (Android 3.0/Honeycomb) the recommended conventions for * app navigation using the back key changed. The back key's behavior is local * to the current task and does not capture navigation across different tasks. * Navigating across tasks and easily reaching the previous task is accomplished * through the "recents" UI, accessible through the software-provided Recents key * on the navigation or system bar. On devices with the older hardware button configuration * the recents UI can be accessed with a long press on the Home key.
* *When crossing from one task stack to another post-Android 3.0, * the application should synthesize a back stack/history for the new task so that * the user may navigate out of the new task and back to the Launcher by repeated * presses of the back key. Back key presses should not navigate across task stacks.
* *startActivities provides a mechanism for constructing a synthetic task stack of * multiple activities. If the underlying API is not available on the system this method * will return false.
* * @param context Start activities using this activity as the starting context * @param intents Array of intents defining the activities that will be started. The element * length-1 will correspond to the top activity on the resulting task stack. * @return true if the underlying API was available and the call was successful, false otherwise */ public static boolean startActivities(Context context, Intent[] intents) { return startActivities(context, intents, null); } /** * Start a set of activities as a synthesized task stack, if able. * *In API level 11 (Android 3.0/Honeycomb) the recommended conventions for * app navigation using the back key changed. The back key's behavior is local * to the current task and does not capture navigation across different tasks. * Navigating across tasks and easily reaching the previous task is accomplished * through the "recents" UI, accessible through the software-provided Recents key * on the navigation or system bar. On devices with the older hardware button configuration * the recents UI can be accessed with a long press on the Home key.
* *When crossing from one task stack to another post-Android 3.0, * the application should synthesize a back stack/history for the new task so that * the user may navigate out of the new task and back to the Launcher by repeated * presses of the back key. Back key presses should not navigate across task stacks.
* *startActivities provides a mechanism for constructing a synthetic task stack of * multiple activities. If the underlying API is not available on the system this method * will return false.
* * @param context Start activities using this activity as the starting context * @param intents Array of intents defining the activities that will be started. The element * length-1 will correspond to the top activity on the resulting task stack. * @param options Additional options for how the Activity should be started. * See {@link android.content.Context#startActivity(Intent, android.os.Bundle) * @return true if the underlying API was available and the call was successful, false otherwise */ public static boolean startActivities(Context context, Intent[] intents, Bundle options) { final int version = Build.VERSION.SDK_INT; if (version >= 16) { ContextCompatJellybean.startActivities(context, intents, options); return true; } else if (version >= 11) { ContextCompatHoneycomb.startActivities(context, intents); return true; } return false; } /** * Returns the absolute path to the directory on the filesystem where all * private files belonging to this app are stored. Apps should not use this * path directly; they should instead use {@link Context#getFilesDir()}, * {@link Context#getCacheDir()}, {@link Context#getDir(String, int)}, or * other storage APIs on {@link Context}. ** The returned path may change over time if the calling app is moved to an * adopted storage device, so only relative paths should be persisted. *
* No additional permissions are required for the calling app to read or * write files under the returned path. * * @see ApplicationInfo#dataDir */ public static File getDataDir(Context context) { if (BuildCompat.isAtLeastN()) { return ContextCompatApi24.getDataDir(context); } else { final String dataDir = context.getApplicationInfo().dataDir; return dataDir != null ? new File(dataDir) : null; } } /** * Returns absolute paths to application-specific directories on all * external storage devices where the application's OBB files (if there are * any) can be found. Note if the application does not have any OBB files, * these directories may not exist. *
* This is like {@link Context#getFilesDir()} in that these files will be * deleted when the application is uninstalled, however there are some * important differences: *
* External storage devices returned here are considered a permanent part of * the device, including both emulated external storage and physical media * slots, such as SD cards in a battery compartment. The returned paths do * not include transient devices, such as USB flash drives. *
* An application may store data on any or all of the returned devices. For * example, an app may choose to store large files on the device with the * most available space, as measured by {@link android.os.StatFs}. *
* Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions * are required to write to the returned paths; they're always accessible to * the calling app. Before then, * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} is required to * write. Write access outside of these paths on secondary external storage * devices is not available. To request external storage access in a * backwards compatible way, consider using {@code android:maxSdkVersion} * like this: * *
<uses-permission * android:name="android.permission.WRITE_EXTERNAL_STORAGE" * android:maxSdkVersion="18" />*
* The first path returned is the same as {@link Context#getObbDir()}. * Returned paths may be {@code null} if a storage device is unavailable. * * @see Context#getObbDir() * @see EnvironmentCompat#getStorageState(File) */ public static File[] getObbDirs(Context context) { final int version = Build.VERSION.SDK_INT; if (version >= 19) { return ContextCompatKitKat.getObbDirs(context); } else { final File single; if (version >= 11) { single = ContextCompatHoneycomb.getObbDir(context); } else { single = buildPath(Environment.getExternalStorageDirectory(), DIR_ANDROID, DIR_OBB, context.getPackageName()); } return new File[] { single }; } } /** * Returns absolute paths to application-specific directories on all * external storage devices where the application can place persistent files * it owns. These files are internal to the application, and not typically * visible to the user as media. *
* This is like {@link Context#getFilesDir()} in that these files will be * deleted when the application is uninstalled, however there are some * important differences: *
* External storage devices returned here are considered a permanent part of * the device, including both emulated external storage and physical media * slots, such as SD cards in a battery compartment. The returned paths do * not include transient devices, such as USB flash drives. *
* An application may store data on any or all of the returned devices. For * example, an app may choose to store large files on the device with the * most available space, as measured by {@link android.os.StatFs}. *
* Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions * are required to write to the returned paths; they're always accessible to * the calling app. Before then, * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} is required to * write. Write access outside of these paths on secondary external storage * devices is not available. To request external storage access in a * backwards compatible way, consider using {@code android:maxSdkVersion} * like this: * *
<uses-permission * android:name="android.permission.WRITE_EXTERNAL_STORAGE" * android:maxSdkVersion="18" />*
* The first path returned is the same as * {@link Context#getExternalFilesDir(String)}. Returned paths may be * {@code null} if a storage device is unavailable. * * @see Context#getExternalFilesDir(String) * @see EnvironmentCompat#getStorageState(File) */ public static File[] getExternalFilesDirs(Context context, String type) { final int version = Build.VERSION.SDK_INT; if (version >= 19) { return ContextCompatKitKat.getExternalFilesDirs(context, type); } else { final File single; if (version >= 8) { single = ContextCompatFroyo.getExternalFilesDir(context, type); } else { single = buildPath(Environment.getExternalStorageDirectory(), DIR_ANDROID, DIR_DATA, context.getPackageName(), DIR_FILES, type); } return new File[] { single }; } } /** * Returns absolute paths to application-specific directories on all * external storage devices where the application can place cache files it * owns. These files are internal to the application, and not typically * visible to the user as media. *
* This is like {@link Context#getCacheDir()} in that these files will be * deleted when the application is uninstalled, however there are some * important differences: *
* External storage devices returned here are considered a permanent part of * the device, including both emulated external storage and physical media * slots, such as SD cards in a battery compartment. The returned paths do * not include transient devices, such as USB flash drives. *
* An application may store data on any or all of the returned devices. For * example, an app may choose to store large files on the device with the * most available space, as measured by {@link android.os.StatFs}. *
* Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions * are required to write to the returned paths; they're always accessible to * the calling app. Before then, * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} is required to * write. Write access outside of these paths on secondary external storage * devices is not available. To request external storage access in a * backwards compatible way, consider using {@code android:maxSdkVersion} * like this: * *
<uses-permission * android:name="android.permission.WRITE_EXTERNAL_STORAGE" * android:maxSdkVersion="18" />*
* The first path returned is the same as * {@link Context#getExternalCacheDir()}. Returned paths may be {@code null} * if a storage device is unavailable. * * @see Context#getExternalCacheDir() * @see EnvironmentCompat#getStorageState(File) */ public static File[] getExternalCacheDirs(Context context) { final int version = Build.VERSION.SDK_INT; if (version >= 19) { return ContextCompatKitKat.getExternalCacheDirs(context); } else { final File single; if (version >= 8) { single = ContextCompatFroyo.getExternalCacheDir(context); } else { single = buildPath(Environment.getExternalStorageDirectory(), DIR_ANDROID, DIR_DATA, context.getPackageName(), DIR_CACHE); } return new File[] { single }; } } private static File buildPath(File base, String... segments) { File cur = base; for (String segment : segments) { if (cur == null) { cur = new File(segment); } else if (segment != null) { cur = new File(cur, segment); } } return cur; } /** * Returns a drawable object associated with a particular resource ID. *
* Starting in {@link android.os.Build.VERSION_CODES#LOLLIPOP}, the * returned drawable will be styled for the specified Context's theme. * * @param id The desired resource identifier, as generated by the aapt tool. * This integer encodes the package, type, and resource entry. * The value 0 is an invalid identifier. * @return Drawable An object that can be used to draw this resource. */ public static final Drawable getDrawable(Context context, int id) { final int version = Build.VERSION.SDK_INT; if (version >= 21) { return ContextCompatApi21.getDrawable(context, id); } else if (version >= 16) { return context.getResources().getDrawable(id); } else { // Prior to JELLY_BEAN, Resources.getDrawable() would not correctly // retrieve the final configuration density when the resource ID // is a reference another Drawable resource. As a workaround, try // to resolve the drawable reference manually. final int resolvedId; synchronized (sLock) { if (sTempValue == null) { sTempValue = new TypedValue(); } context.getResources().getValue(id, sTempValue, true); resolvedId = sTempValue.resourceId; } return context.getResources().getDrawable(resolvedId); } } /** * Returns a color state list associated with a particular resource ID. *
* Starting in {@link android.os.Build.VERSION_CODES#M}, the returned * color state list will be styled for the specified Context's theme. * * @param id The desired resource identifier, as generated by the aapt * tool. This integer encodes the package, type, and resource * entry. The value 0 is an invalid identifier. * @return A color state list, or {@code null} if the resource could not be * resolved. * @throws android.content.res.Resources.NotFoundException if the given ID * does not exist. */ public static final ColorStateList getColorStateList(Context context, int id) { final int version = Build.VERSION.SDK_INT; if (version >= 23) { return ContextCompatApi23.getColorStateList(context, id); } else { return context.getResources().getColorStateList(id); } } /** * Returns a color associated with a particular resource ID *
* Starting in {@link android.os.Build.VERSION_CODES#M}, the returned * color will be styled for the specified Context's theme. * * @param id The desired resource identifier, as generated by the aapt * tool. This integer encodes the package, type, and resource * entry. The value 0 is an invalid identifier. * @return A single color value in the form 0xAARRGGBB. * @throws android.content.res.Resources.NotFoundException if the given ID * does not exist. */ public static final int getColor(Context context, int id) { final int version = Build.VERSION.SDK_INT; if (version >= 23) { return ContextCompatApi23.getColor(context, id); } else { return context.getResources().getColor(id); } } /** * Determine whether you have been granted a particular permission. * * @param permission The name of the permission being checked. * * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if you have the * permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} if not. * * @see android.content.pm.PackageManager#checkPermission(String, String) */ public static int checkSelfPermission(@NonNull Context context, @NonNull String permission) { if (permission == null) { throw new IllegalArgumentException("permission is null"); } return context.checkPermission(permission, android.os.Process.myPid(), Process.myUid()); } /** * Returns the absolute path to the directory on the filesystem similar to * {@link Context#getFilesDir()}. The difference is that files placed under this * directory will be excluded from automatic backup to remote storage on * devices running {@link android.os.Build.VERSION_CODES#LOLLIPOP} or later. See * {@link android.app.backup.BackupAgent BackupAgent} for a full discussion * of the automatic backup mechanism in Android. * *
No permissions are required to read or write to the returned path, since this * path is internal storage. * * @return The path of the directory holding application files that will not be * automatically backed up to remote storage. * * @see android.content.Context.getFilesDir */ public static final File getNoBackupFilesDir(Context context) { final int version = Build.VERSION.SDK_INT; if (version >= 21) { return ContextCompatApi21.getNoBackupFilesDir(context); } else { ApplicationInfo appInfo = context.getApplicationInfo(); return createFilesDir(new File(appInfo.dataDir, "no_backup")); } } /** * Returns the absolute path to the application specific cache directory on * the filesystem designed for storing cached code. On devices running * {@link android.os.Build.VERSION_CODES#LOLLIPOP} or later, the system will delete * any files stored in this location both when your specific application is * upgraded, and when the entire platform is upgraded. *
* This location is optimal for storing compiled or optimized code generated * by your application at runtime. *
* Apps require no extra permissions to read or write to the returned path, * since this path lives in their private storage. * * @return The path of the directory holding application code cache files. */ public static File getCodeCacheDir(Context context) { final int version = Build.VERSION.SDK_INT; if (version >= 21) { return ContextCompatApi21.getCodeCacheDir(context); } else { ApplicationInfo appInfo = context.getApplicationInfo(); return createFilesDir(new File(appInfo.dataDir, "code_cache")); } } private synchronized static File createFilesDir(File file) { if (!file.exists()) { if (!file.mkdirs()) { if (file.exists()) { // spurious failure; probably racing with another process for this app return file; } Log.w(TAG, "Unable to create files subdir " + file.getPath()); return null; } } return file; } /** * Return a new Context object for the current Context but whose storage * APIs are backed by device-protected storage. *
* On devices with direct boot, data stored in this location is encrypted * with a key tied to the physical device, and it can be accessed * immediately after the device has booted successfully, both * before and after the user has authenticated with their * credentials (such as a lock pattern or PIN). *
* Because device-protected data is available without user authentication, * you should carefully limit the data you store using this Context. For * example, storing sensitive authentication tokens or passwords in the * device-protected area is strongly discouraged. *
* If the underlying device does not have the ability to store * device-protected and credential-protected data using different keys, then * both storage areas will become available at the same time. They remain as * two distinct storage locations on disk, and only the window of * availability changes. *
* Each call to this method returns a new instance of a Context object; * Context objects are not shared, however common state (ClassLoader, other * Resources for the same configuration) may be so the Context itself can be * fairly lightweight. *
* Prior to {@link BuildCompat#isAtLeastN()} this method returns * {@code null}, since device-protected storage is not available. * * @see ContextCompat#isDeviceProtectedStorage(Context) */ public static Context createDeviceProtectedStorageContext(Context context) { if (BuildCompat.isAtLeastN()) { return ContextCompatApi24.createDeviceProtectedStorageContext(context); } else { return null; } } /** * @removed * @deprecated Removed. Do not use. */ @Deprecated public static Context createDeviceEncryptedStorageContext(Context context) { return createDeviceProtectedStorageContext(context); } /** * Indicates if the storage APIs of this Context are backed by * device-encrypted storage. * * @see ContextCompat#createDeviceProtectedStorageContext(Context) */ public static boolean isDeviceProtectedStorage(Context context) { if (BuildCompat.isAtLeastN()) { return ContextCompatApi24.isDeviceProtectedStorage(context); } else { return false; } } /** * @removed * @deprecated Removed. Do not use. */ @Deprecated public static boolean isDeviceEncryptedStorage(Context context) { return isDeviceProtectedStorage(context); } }