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.os.Build;
22import android.os.Bundle;
23import android.os.Environment;
24import android.os.StatFs;
25import android.support.v4.os.EnvironmentCompat;
26
27import java.io.File;
28
29/**
30 * Helper for accessing features in {@link android.content.Context}
31 * introduced after API level 4 in a backwards compatible fashion.
32 */
33public class ContextCompat {
34
35    private static final String DIR_ANDROID = "Android";
36    private static final String DIR_DATA = "data";
37    private static final String DIR_OBB = "obb";
38    private static final String DIR_FILES = "files";
39    private static final String DIR_CACHE = "cache";
40
41    /**
42     * Start a set of activities as a synthesized task stack, if able.
43     *
44     * <p>In API level 11 (Android 3.0/Honeycomb) the recommended conventions for
45     * app navigation using the back key changed. The back key's behavior is local
46     * to the current task and does not capture navigation across different tasks.
47     * Navigating across tasks and easily reaching the previous task is accomplished
48     * through the "recents" UI, accessible through the software-provided Recents key
49     * on the navigation or system bar. On devices with the older hardware button configuration
50     * the recents UI can be accessed with a long press on the Home key.</p>
51     *
52     * <p>When crossing from one task stack to another post-Android 3.0,
53     * the application should synthesize a back stack/history for the new task so that
54     * the user may navigate out of the new task and back to the Launcher by repeated
55     * presses of the back key. Back key presses should not navigate across task stacks.</p>
56     *
57     * <p>startActivities provides a mechanism for constructing a synthetic task stack of
58     * multiple activities. If the underlying API is not available on the system this method
59     * will return false.</p>
60     *
61     * @param context Start activities using this activity as the starting context
62     * @param intents Array of intents defining the activities that will be started. The element
63     *                length-1 will correspond to the top activity on the resulting task stack.
64     * @return true if the underlying API was available and the call was successful, false otherwise
65     */
66    public static boolean startActivities(Context context, Intent[] intents) {
67        return startActivities(context, intents, null);
68    }
69
70    /**
71     * Start a set of activities as a synthesized task stack, if able.
72     *
73     * <p>In API level 11 (Android 3.0/Honeycomb) the recommended conventions for
74     * app navigation using the back key changed. The back key's behavior is local
75     * to the current task and does not capture navigation across different tasks.
76     * Navigating across tasks and easily reaching the previous task is accomplished
77     * through the "recents" UI, accessible through the software-provided Recents key
78     * on the navigation or system bar. On devices with the older hardware button configuration
79     * the recents UI can be accessed with a long press on the Home key.</p>
80     *
81     * <p>When crossing from one task stack to another post-Android 3.0,
82     * the application should synthesize a back stack/history for the new task so that
83     * the user may navigate out of the new task and back to the Launcher by repeated
84     * presses of the back key. Back key presses should not navigate across task stacks.</p>
85     *
86     * <p>startActivities provides a mechanism for constructing a synthetic task stack of
87     * multiple activities. If the underlying API is not available on the system this method
88     * will return false.</p>
89     *
90     * @param context Start activities using this activity as the starting context
91     * @param intents Array of intents defining the activities that will be started. The element
92     *                length-1 will correspond to the top activity on the resulting task stack.
93     * @param options Additional options for how the Activity should be started.
94     * See {@link android.content.Context#startActivity(Intent, Bundle)
95     * @return true if the underlying API was available and the call was successful, false otherwise
96     */
97    public static boolean startActivities(Context context, Intent[] intents,
98            Bundle options) {
99        final int version = Build.VERSION.SDK_INT;
100        if (version >= 16) {
101            ContextCompatJellybean.startActivities(context, intents, options);
102            return true;
103        } else if (version >= 11) {
104            ContextCompatHoneycomb.startActivities(context, intents);
105            return true;
106        }
107        return false;
108    }
109
110    /**
111     * Returns absolute paths to application-specific directories on all
112     * external storage devices where the application's OBB files (if there are
113     * any) can be found. Note if the application does not have any OBB files,
114     * these directories may not exist.
115     * <p>
116     * This is like {@link Context#getFilesDir()} in that these files will be
117     * deleted when the application is uninstalled, however there are some
118     * important differences:
119     * <ul>
120     * <li>External files are not always available: they will disappear if the
121     * user mounts the external storage on a computer or removes it.
122     * <li>There is no security enforced with these files.
123     * </ul>
124     * <p>
125     * External storage devices returned here are considered a permanent part of
126     * the device, including both emulated external storage and physical media
127     * slots, such as SD cards in a battery compartment. The returned paths do
128     * not include transient devices, such as USB flash drives.
129     * <p>
130     * An application may store data on any or all of the returned devices. For
131     * example, an app may choose to store large files on the device with the
132     * most available space, as measured by {@link StatFs}.
133     * <p>
134     * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
135     * are required to write to the returned paths; they're always accessible to
136     * the calling app. Before then,
137     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} is required to
138     * write. Write access outside of these paths on secondary external storage
139     * devices is not available. To request external storage access in a
140     * backwards compatible way, consider using {@code android:maxSdkVersion}
141     * like this:
142     *
143     * <pre class="prettyprint">&lt;uses-permission
144     *     android:name="android.permission.WRITE_EXTERNAL_STORAGE"
145     *     android:maxSdkVersion="18" /&gt;</pre>
146     * <p>
147     * The first path returned is the same as {@link Context#getObbDir()}.
148     * Returned paths may be {@code null} if a storage device is unavailable.
149     *
150     * @see Context#getObbDir()
151     * @see EnvironmentCompat#getStorageState(File)
152     */
153    public static File[] getObbDirs(Context context) {
154        final int version = Build.VERSION.SDK_INT;
155        if (version >= 19) {
156            return ContextCompatKitKat.getObbDirs(context);
157        } else {
158            final File single;
159            if (version >= 11) {
160                single = ContextCompatHoneycomb.getObbDir(context);
161            } else {
162                single = buildPath(Environment.getExternalStorageDirectory(), DIR_ANDROID, DIR_OBB,
163                        context.getPackageName());
164            }
165            return new File[] { single };
166        }
167    }
168
169    /**
170     * Returns absolute paths to application-specific directories on all
171     * external storage devices where the application can place persistent files
172     * it owns. These files are internal to the application, and not typically
173     * visible to the user as media.
174     * <p>
175     * This is like {@link Context#getFilesDir()} in that these files will be
176     * deleted when the application is uninstalled, however there are some
177     * important differences:
178     * <ul>
179     * <li>External files are not always available: they will disappear if the
180     * user mounts the external storage on a computer or removes it.
181     * <li>There is no security enforced with these files.
182     * </ul>
183     * <p>
184     * External storage devices returned here are considered a permanent part of
185     * the device, including both emulated external storage and physical media
186     * slots, such as SD cards in a battery compartment. The returned paths do
187     * not include transient devices, such as USB flash drives.
188     * <p>
189     * An application may store data on any or all of the returned devices. For
190     * example, an app may choose to store large files on the device with the
191     * most available space, as measured by {@link StatFs}.
192     * <p>
193     * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
194     * are required to write to the returned paths; they're always accessible to
195     * the calling app. Before then,
196     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} is required to
197     * write. Write access outside of these paths on secondary external storage
198     * devices is not available. To request external storage access in a
199     * backwards compatible way, consider using {@code android:maxSdkVersion}
200     * like this:
201     *
202     * <pre class="prettyprint">&lt;uses-permission
203     *     android:name="android.permission.WRITE_EXTERNAL_STORAGE"
204     *     android:maxSdkVersion="18" /&gt;</pre>
205     * <p>
206     * The first path returned is the same as
207     * {@link Context#getExternalFilesDir(String)}. Returned paths may be
208     * {@code null} if a storage device is unavailable.
209     *
210     * @see Context#getExternalFilesDir(String)
211     * @see EnvironmentCompat#getStorageState(File)
212     */
213    public static File[] getExternalFilesDirs(Context context, String type) {
214        final int version = Build.VERSION.SDK_INT;
215        if (version >= 19) {
216            return ContextCompatKitKat.getExternalFilesDirs(context, type);
217        } else {
218            final File single;
219            if (version >= 8) {
220                single = ContextCompatFroyo.getExternalFilesDir(context, type);
221            } else {
222                single = buildPath(Environment.getExternalStorageDirectory(), DIR_ANDROID, DIR_DATA,
223                        context.getPackageName(), DIR_FILES, type);
224            }
225            return new File[] { single };
226        }
227    }
228
229    /**
230     * Returns absolute paths to application-specific directories on all
231     * external storage devices where the application can place cache files it
232     * owns. These files are internal to the application, and not typically
233     * visible to the user as media.
234     * <p>
235     * This is like {@link Context#getCacheDir()} in that these files will be
236     * deleted when the application is uninstalled, however there are some
237     * important differences:
238     * <ul>
239     * <li>External files are not always available: they will disappear if the
240     * user mounts the external storage on a computer or removes it.
241     * <li>There is no security enforced with these files.
242     * </ul>
243     * <p>
244     * External storage devices returned here are considered a permanent part of
245     * the device, including both emulated external storage and physical media
246     * slots, such as SD cards in a battery compartment. The returned paths do
247     * not include transient devices, such as USB flash drives.
248     * <p>
249     * An application may store data on any or all of the returned devices. For
250     * example, an app may choose to store large files on the device with the
251     * most available space, as measured by {@link StatFs}.
252     * <p>
253     * Starting in {@link android.os.Build.VERSION_CODES#KITKAT}, no permissions
254     * are required to write to the returned paths; they're always accessible to
255     * the calling app. Before then,
256     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} is required to
257     * write. Write access outside of these paths on secondary external storage
258     * devices is not available. To request external storage access in a
259     * backwards compatible way, consider using {@code android:maxSdkVersion}
260     * like this:
261     *
262     * <pre class="prettyprint">&lt;uses-permission
263     *     android:name="android.permission.WRITE_EXTERNAL_STORAGE"
264     *     android:maxSdkVersion="18" /&gt;</pre>
265     * <p>
266     * The first path returned is the same as
267     * {@link Context#getExternalCacheDir()}. Returned paths may be {@code null}
268     * if a storage device is unavailable.
269     *
270     * @see Context#getExternalCacheDir()
271     * @see EnvironmentCompat#getStorageState(File)
272     */
273    public static File[] getExternalCacheDirs(Context context) {
274        final int version = Build.VERSION.SDK_INT;
275        if (version >= 19) {
276            return ContextCompatKitKat.getExternalCacheDirs(context);
277        } else {
278            final File single;
279            if (version >= 8) {
280                single = ContextCompatFroyo.getExternalCacheDir(context);
281            } else {
282                single = buildPath(Environment.getExternalStorageDirectory(), DIR_ANDROID, DIR_DATA,
283                        context.getPackageName(), DIR_CACHE);
284            }
285            return new File[] { single };
286        }
287    }
288
289    private static File buildPath(File base, String... segments) {
290        File cur = base;
291        for (String segment : segments) {
292            if (cur == null) {
293                cur = new File(segment);
294            } else if (segment != null) {
295                cur = new File(cur, segment);
296            }
297        }
298        return cur;
299    }
300}
301