summaryrefslogtreecommitdiff
path: root/base/android/java/src/org/chromium/base/PathUtils.java
diff options
context:
space:
mode:
Diffstat (limited to 'base/android/java/src/org/chromium/base/PathUtils.java')
-rw-r--r--base/android/java/src/org/chromium/base/PathUtils.java263
1 files changed, 263 insertions, 0 deletions
diff --git a/base/android/java/src/org/chromium/base/PathUtils.java b/base/android/java/src/org/chromium/base/PathUtils.java
new file mode 100644
index 0000000000..e6fc8029b8
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/PathUtils.java
@@ -0,0 +1,263 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Build;
+import android.os.Environment;
+import android.os.SystemClock;
+import android.system.Os;
+import android.text.TextUtils;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.MainDex;
+import org.chromium.base.metrics.RecordHistogram;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This class provides the path related methods for the native library.
+ */
+@MainDex
+public abstract class PathUtils {
+ private static final String TAG = "PathUtils";
+ private static final String THUMBNAIL_DIRECTORY_NAME = "textures";
+
+ private static final int DATA_DIRECTORY = 0;
+ private static final int THUMBNAIL_DIRECTORY = 1;
+ private static final int CACHE_DIRECTORY = 2;
+ private static final int NUM_DIRECTORIES = 3;
+ private static final AtomicBoolean sInitializationStarted = new AtomicBoolean();
+ private static AsyncTask<Void, Void, String[]> sDirPathFetchTask;
+
+ // If the AsyncTask started in setPrivateDataDirectorySuffix() fails to complete by the time we
+ // need the values, we will need the suffix so that we can restart the task synchronously on
+ // the UI thread.
+ private static String sDataDirectorySuffix;
+ private static String sCacheSubDirectory;
+
+ // Prevent instantiation.
+ private PathUtils() {}
+
+ /**
+ * Initialization-on-demand holder. This exists for thread-safe lazy initialization. It will
+ * cause getOrComputeDirectoryPaths() to be called (safely) the first time DIRECTORY_PATHS is
+ * accessed.
+ *
+ * <p>See https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom.
+ */
+ private static class Holder {
+ private static final String[] DIRECTORY_PATHS = getOrComputeDirectoryPaths();
+ }
+
+ /**
+ * Get the directory paths from sDirPathFetchTask if available, or compute it synchronously
+ * on the UI thread otherwise. This should only be called as part of Holder's initialization
+ * above to guarantee thread-safety as part of the initialization-on-demand holder idiom.
+ */
+ private static String[] getOrComputeDirectoryPaths() {
+ try {
+ // We need to call sDirPathFetchTask.cancel() here to prevent races. If it returns
+ // true, that means that the task got canceled successfully (and thus, it did not
+ // finish running its task). Otherwise, it failed to cancel, meaning that it was
+ // already finished.
+ if (sDirPathFetchTask.cancel(false)) {
+ // Allow disk access here because we have no other choice.
+ try (StrictModeContext unused = StrictModeContext.allowDiskWrites()) {
+ // sDirPathFetchTask did not complete. We have to run the code it was supposed
+ // to be responsible for synchronously on the UI thread.
+ return PathUtils.setPrivateDataDirectorySuffixInternal();
+ }
+ } else {
+ // sDirPathFetchTask succeeded, and the values we need should be ready to access
+ // synchronously in its internal future.
+ return sDirPathFetchTask.get();
+ }
+ } catch (InterruptedException e) {
+ } catch (ExecutionException e) {
+ }
+
+ return null;
+ }
+
+ @SuppressLint("NewApi")
+ private static void chmod(String path, int mode) {
+ // Both Os.chmod and ErrnoException require SDK >= 21. But while Dalvik on < 21 tolerates
+ // Os.chmod, it throws VerifyError for ErrnoException, so catch Exception instead.
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return;
+ try {
+ Os.chmod(path, mode);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to set permissions for path \"" + path + "\"");
+ }
+ }
+
+ /**
+ * Fetch the path of the directory where private data is to be stored by the application. This
+ * is meant to be called in an AsyncTask in setPrivateDataDirectorySuffix(), but if we need the
+ * result before the AsyncTask has had a chance to finish, then it's best to cancel the task
+ * and run it on the UI thread instead, inside getOrComputeDirectoryPaths().
+ *
+ * @see Context#getDir(String, int)
+ */
+ private static String[] setPrivateDataDirectorySuffixInternal() {
+ String[] paths = new String[NUM_DIRECTORIES];
+ Context appContext = ContextUtils.getApplicationContext();
+ paths[DATA_DIRECTORY] = appContext.getDir(
+ sDataDirectorySuffix, Context.MODE_PRIVATE).getPath();
+ // MODE_PRIVATE results in rwxrwx--x, but we want rwx------, as a defence-in-depth measure.
+ chmod(paths[DATA_DIRECTORY], 0700);
+ paths[THUMBNAIL_DIRECTORY] = appContext.getDir(
+ THUMBNAIL_DIRECTORY_NAME, Context.MODE_PRIVATE).getPath();
+ if (appContext.getCacheDir() != null) {
+ if (sCacheSubDirectory == null) {
+ paths[CACHE_DIRECTORY] = appContext.getCacheDir().getPath();
+ } else {
+ paths[CACHE_DIRECTORY] =
+ new File(appContext.getCacheDir(), sCacheSubDirectory).getPath();
+ }
+ }
+ return paths;
+ }
+
+ /**
+ * Starts an asynchronous task to fetch the path of the directory where private data is to be
+ * stored by the application.
+ *
+ * <p>This task can run long (or more likely be delayed in a large task queue), in which case we
+ * want to cancel it and run on the UI thread instead. Unfortunately, this means keeping a bit
+ * of extra static state - we need to store the suffix and the application context in case we
+ * need to try to re-execute later.
+ *
+ * @param suffix The private data directory suffix.
+ * @param cacheSubDir The subdirectory in the cache directory to use, if non-null.
+ * @see Context#getDir(String, int)
+ */
+ public static void setPrivateDataDirectorySuffix(String suffix, String cacheSubDir) {
+ // This method should only be called once, but many tests end up calling it multiple times,
+ // so adding a guard here.
+ if (!sInitializationStarted.getAndSet(true)) {
+ assert ContextUtils.getApplicationContext() != null;
+ sDataDirectorySuffix = suffix;
+ sCacheSubDirectory = cacheSubDir;
+ sDirPathFetchTask = new AsyncTask<Void, Void, String[]>() {
+ @Override
+ protected String[] doInBackground(Void... unused) {
+ return PathUtils.setPrivateDataDirectorySuffixInternal();
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+ }
+
+ public static void setPrivateDataDirectorySuffix(String suffix) {
+ setPrivateDataDirectorySuffix(suffix, null);
+ }
+
+ /**
+ * @param index The index of the cached directory path.
+ * @return The directory path requested.
+ */
+ private static String getDirectoryPath(int index) {
+ return Holder.DIRECTORY_PATHS[index];
+ }
+
+ /**
+ * @return the private directory that is used to store application data.
+ */
+ @CalledByNative
+ public static String getDataDirectory() {
+ assert sDirPathFetchTask != null : "setDataDirectorySuffix must be called first.";
+ return getDirectoryPath(DATA_DIRECTORY);
+ }
+
+ /**
+ * @return the cache directory.
+ */
+ @CalledByNative
+ public static String getCacheDirectory() {
+ assert sDirPathFetchTask != null : "setDataDirectorySuffix must be called first.";
+ return getDirectoryPath(CACHE_DIRECTORY);
+ }
+
+ @CalledByNative
+ public static String getThumbnailCacheDirectory() {
+ assert sDirPathFetchTask != null : "setDataDirectorySuffix must be called first.";
+ return getDirectoryPath(THUMBNAIL_DIRECTORY);
+ }
+
+ /**
+ * @return the public downloads directory.
+ */
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private static String getDownloadsDirectory() {
+ // Temporarily allowing disk access while fixing. TODO: http://crbug.com/508615
+ try (StrictModeContext unused = StrictModeContext.allowDiskReads()) {
+ long time = SystemClock.elapsedRealtime();
+ String downloadsPath =
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
+ .getPath();
+ RecordHistogram.recordTimesHistogram("Android.StrictMode.DownloadsDir",
+ SystemClock.elapsedRealtime() - time, TimeUnit.MILLISECONDS);
+ return downloadsPath;
+ }
+ }
+
+ /**
+ * @return Download directories including the default storage directory on SD card, and a
+ * private directory on external SD card.
+ */
+ @SuppressWarnings("unused")
+ @CalledByNative
+ public static String[] getAllPrivateDownloadsDirectories() {
+ File[] files;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ try (StrictModeContext unused = StrictModeContext.allowDiskWrites()) {
+ files = ContextUtils.getApplicationContext().getExternalFilesDirs(
+ Environment.DIRECTORY_DOWNLOADS);
+ }
+ } else {
+ files = new File[] {Environment.getExternalStorageDirectory()};
+ }
+
+ ArrayList<String> absolutePaths = new ArrayList<String>();
+ for (int i = 0; i < files.length; ++i) {
+ if (files[i] == null || TextUtils.isEmpty(files[i].getAbsolutePath())) continue;
+ absolutePaths.add(files[i].getAbsolutePath());
+ }
+
+ return absolutePaths.toArray(new String[absolutePaths.size()]);
+ }
+
+ /**
+ * @return the path to native libraries.
+ */
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private static String getNativeLibraryDirectory() {
+ ApplicationInfo ai = ContextUtils.getApplicationContext().getApplicationInfo();
+ if ((ai.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
+ || (ai.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ return ai.nativeLibraryDir;
+ }
+
+ return "/system/lib/";
+ }
+
+ /**
+ * @return the external storage directory.
+ */
+ @SuppressWarnings("unused")
+ @CalledByNative
+ public static String getExternalStorageDirectory() {
+ return Environment.getExternalStorageDirectory().getAbsolutePath();
+ }
+}