• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.base;
6 
7 import android.content.Context;
8 import android.content.pm.ApplicationInfo;
9 import android.os.AsyncTask;
10 import android.os.Environment;
11 import android.os.StrictMode;
12 import android.os.SystemClock;
13 
14 import org.chromium.base.annotations.CalledByNative;
15 import org.chromium.base.annotations.MainDex;
16 import org.chromium.base.metrics.RecordHistogram;
17 
18 import java.util.concurrent.ExecutionException;
19 import java.util.concurrent.TimeUnit;
20 import java.util.concurrent.atomic.AtomicBoolean;
21 
22 /**
23  * This class provides the path related methods for the native library.
24  */
25 @MainDex
26 public abstract class PathUtils {
27     private static final String THUMBNAIL_DIRECTORY_NAME = "textures";
28 
29     private static final int DATA_DIRECTORY = 0;
30     private static final int THUMBNAIL_DIRECTORY = 1;
31     private static final int DATABASE_DIRECTORY = 2;
32     private static final int CACHE_DIRECTORY = 3;
33     private static final int NUM_DIRECTORIES = 4;
34     private static final AtomicBoolean sInitializationStarted = new AtomicBoolean();
35     private static AsyncTask<Void, Void, String[]> sDirPathFetchTask;
36 
37     // In setPrivateDataDirectorySuffix(), we store the app's context. If the AsyncTask started in
38     // setPrivateDataDirectorySuffix() fails to complete by the time we need the values, we will
39     // need the context so that we can restart the task synchronously on the UI thread.
40     private static Context sDataDirectoryAppContext;
41 
42     // We also store the directory path suffix from setPrivateDataDirectorySuffix() for the same
43     // reason as above.
44     private static String sDataDirectorySuffix;
45 
46     // Prevent instantiation.
PathUtils()47     private PathUtils() {}
48 
49     /**
50      * Initialization-on-demand holder. This exists for thread-safe lazy initialization. It will
51      * cause getOrComputeDirectoryPaths() to be called (safely) the first time DIRECTORY_PATHS is
52      * accessed.
53      *
54      * <p>See https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom.
55      */
56     private static class Holder {
57         private static final String[] DIRECTORY_PATHS = getOrComputeDirectoryPaths();
58     }
59 
60     /**
61      * Get the directory paths from sDirPathFetchTask if available, or compute it synchronously
62      * on the UI thread otherwise. This should only be called as part of Holder's initialization
63      * above to guarantee thread-safety as part of the initialization-on-demand holder idiom.
64      */
getOrComputeDirectoryPaths()65     private static String[] getOrComputeDirectoryPaths() {
66         try {
67             // We need to call sDirPathFetchTask.cancel() here to prevent races. If it returns
68             // true, that means that the task got canceled successfully (and thus, it did not
69             // finish running its task). Otherwise, it failed to cancel, meaning that it was
70             // already finished.
71             if (sDirPathFetchTask.cancel(false)) {
72                 // Allow disk access here because we have no other choice.
73                 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
74                 StrictMode.allowThreadDiskWrites();
75                 try {
76                     // sDirPathFetchTask did not complete. We have to run the code it was supposed
77                     // to be responsible for synchronously on the UI thread.
78                     return PathUtils.setPrivateDataDirectorySuffixInternal();
79                 } finally {
80                     StrictMode.setThreadPolicy(oldPolicy);
81                 }
82             } else {
83                 // sDirPathFetchTask succeeded, and the values we need should be ready to access
84                 // synchronously in its internal future.
85                 return sDirPathFetchTask.get();
86             }
87         } catch (InterruptedException e) {
88         } catch (ExecutionException e) {
89         }
90 
91         return null;
92     }
93 
94     /**
95      * Fetch the path of the directory where private data is to be stored by the application. This
96      * is meant to be called in an AsyncTask in setPrivateDataDirectorySuffix(), but if we need the
97      * result before the AsyncTask has had a chance to finish, then it's best to cancel the task
98      * and run it on the UI thread instead, inside getOrComputeDirectoryPaths().
99      *
100      * @see Context#getDir(String, int)
101      */
setPrivateDataDirectorySuffixInternal()102     private static String[] setPrivateDataDirectorySuffixInternal() {
103         String[] paths = new String[NUM_DIRECTORIES];
104         paths[DATA_DIRECTORY] = sDataDirectoryAppContext.getDir(sDataDirectorySuffix,
105                 Context.MODE_PRIVATE).getPath();
106         paths[THUMBNAIL_DIRECTORY] = sDataDirectoryAppContext.getDir(
107                 THUMBNAIL_DIRECTORY_NAME, Context.MODE_PRIVATE).getPath();
108         paths[DATABASE_DIRECTORY] = sDataDirectoryAppContext.getDatabasePath("foo").getParent();
109         if (sDataDirectoryAppContext.getCacheDir() != null) {
110             paths[CACHE_DIRECTORY] = sDataDirectoryAppContext.getCacheDir().getPath();
111         }
112         return paths;
113     }
114 
115     /**
116      * Starts an asynchronous task to fetch the path of the directory where private data is to be
117      * stored by the application.
118      *
119      * <p>This task can run long (or more likely be delayed in a large task queue), in which case we
120      * want to cancel it and run on the UI thread instead. Unfortunately, this means keeping a bit
121      * of extra static state - we need to store the suffix and the application context in case we
122      * need to try to re-execute later.
123      *
124      * @param suffix The private data directory suffix.
125      * @see Context#getDir(String, int)
126      */
setPrivateDataDirectorySuffix(String suffix, Context context)127     public static void setPrivateDataDirectorySuffix(String suffix, Context context) {
128         // This method should only be called once, but many tests end up calling it multiple times,
129         // so adding a guard here.
130         if (!sInitializationStarted.getAndSet(true)) {
131             sDataDirectorySuffix = suffix;
132             sDataDirectoryAppContext = context.getApplicationContext();
133             sDirPathFetchTask = new AsyncTask<Void, Void, String[]>() {
134                 @Override
135                 protected String[] doInBackground(Void... unused) {
136                     return PathUtils.setPrivateDataDirectorySuffixInternal();
137                 }
138             }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
139         }
140     }
141 
142     /**
143      * @param index The index of the cached directory path.
144      * @return The directory path requested.
145      */
getDirectoryPath(int index)146     private static String getDirectoryPath(int index) {
147         return Holder.DIRECTORY_PATHS[index];
148     }
149 
150     /**
151      * @return the private directory that is used to store application data.
152      */
153     @CalledByNative
getDataDirectory(Context appContext)154     public static String getDataDirectory(Context appContext) {
155         assert sDirPathFetchTask != null : "setDataDirectorySuffix must be called first.";
156         return getDirectoryPath(DATA_DIRECTORY);
157     }
158 
159     /**
160      * @return the private directory that is used to store application database.
161      */
162     @CalledByNative
getDatabaseDirectory(Context appContext)163     public static String getDatabaseDirectory(Context appContext) {
164         assert sDirPathFetchTask != null : "setDataDirectorySuffix must be called first.";
165         return getDirectoryPath(DATABASE_DIRECTORY);
166     }
167 
168     /**
169      * @return the cache directory.
170      */
171     @SuppressWarnings("unused")
172     @CalledByNative
getCacheDirectory(Context appContext)173     public static String getCacheDirectory(Context appContext) {
174         assert sDirPathFetchTask != null : "setDataDirectorySuffix must be called first.";
175         return getDirectoryPath(CACHE_DIRECTORY);
176     }
177 
178     @CalledByNative
getThumbnailCacheDirectory(Context appContext)179     public static String getThumbnailCacheDirectory(Context appContext) {
180         assert sDirPathFetchTask != null : "setDataDirectorySuffix must be called first.";
181         return getDirectoryPath(THUMBNAIL_DIRECTORY);
182     }
183 
184     /**
185      * @return the public downloads directory.
186      */
187     @SuppressWarnings("unused")
188     @CalledByNative
getDownloadsDirectory(Context appContext)189     private static String getDownloadsDirectory(Context appContext) {
190         // Temporarily allowing disk access while fixing. TODO: http://crbug.com/508615
191         StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
192         String downloadsPath;
193         try {
194             long time = SystemClock.elapsedRealtime();
195             downloadsPath = Environment.getExternalStoragePublicDirectory(
196                     Environment.DIRECTORY_DOWNLOADS).getPath();
197             RecordHistogram.recordTimesHistogram("Android.StrictMode.DownloadsDir",
198                     SystemClock.elapsedRealtime() - time, TimeUnit.MILLISECONDS);
199         } finally {
200             StrictMode.setThreadPolicy(oldPolicy);
201         }
202         return downloadsPath;
203     }
204 
205     /**
206      * @return the path to native libraries.
207      */
208     @SuppressWarnings("unused")
209     @CalledByNative
getNativeLibraryDirectory(Context appContext)210     private static String getNativeLibraryDirectory(Context appContext) {
211         ApplicationInfo ai = appContext.getApplicationInfo();
212         if ((ai.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
213                 || (ai.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
214             return ai.nativeLibraryDir;
215         }
216 
217         return "/system/lib/";
218     }
219 
220     /**
221      * @return the external storage directory.
222      */
223     @SuppressWarnings("unused")
224     @CalledByNative
getExternalStorageDirectory()225     public static String getExternalStorageDirectory() {
226         return Environment.getExternalStorageDirectory().getAbsolutePath();
227     }
228 }
229