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