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