Commit 06b152e9 authored by Xing Liu's avatar Xing Liu Committed by Commit Bot

Download: Add a method to get secondary storage Download directory on R.

Android R starts to restrict other apps to access app's private
directory on R. Based on suggestion from Android engineer, we can use
a few new APIs added in Q and R to move the storage path a non private
directory.

Old path:
/storage/$volume_id/$app_private_directory/Download
New path:
/storage/&volume_id/Download

Bug: 1116905
Change-Id: Iecb34a08648678dbd1ff986c7ee60f3a4647c725
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2515262Reviewed-by: default avatarTommy Nyquist <nyquist@chromium.org>
Reviewed-by: default avatarMatt Menke <mmenke@chromium.org>
Reviewed-by: default avatarMin Qin <qinmin@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Commit-Queue: Xing Liu <xingliu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#826685}
parent 938e32d0
......@@ -9,10 +9,13 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Build;
import android.os.Environment;
import android.os.storage.StorageManager;
import android.provider.MediaStore;
import android.system.Os;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.MainDex;
......@@ -20,6 +23,9 @@ import org.chromium.base.task.AsyncTask;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicBoolean;
......@@ -202,7 +208,7 @@ public abstract class PathUtils {
try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
if (BuildInfo.isAtLeastQ()) {
// https://developer.android.com/preview/privacy/scoped-storage
// In Q+, Android has bugun sandboxing external storage. Chrome may not have
// In Q+, Android has begun sandboxing external storage. Chrome may not have
// permission to write to Environment.getExternalStoragePublicDirectory(). Instead
// using Context.getExternalFilesDir() will return a path to sandboxed external
// storage for which no additional permissions are required.
......@@ -221,20 +227,56 @@ public abstract class PathUtils {
@SuppressWarnings("unused")
@CalledByNative
public static String[] getAllPrivateDownloadsDirectories() {
File[] files;
List<File> files = new ArrayList<>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try (StrictModeContext ignored = StrictModeContext.allowDiskWrites()) {
files = ContextUtils.getApplicationContext().getExternalFilesDirs(
Environment.DIRECTORY_DOWNLOADS);
files = Arrays.asList(ContextUtils.getApplicationContext().getExternalFilesDirs(
Environment.DIRECTORY_DOWNLOADS));
}
} else {
files = new File[] {Environment.getExternalStorageDirectory()};
files.add(Environment.getExternalStorageDirectory());
}
return toAbsolutePathStrings(files);
}
/**
* @return The download directory for secondary storage on Q+, returned by
* {@link MediaStore#getExternalVolumeNames(Context)}. Notices on Android R, apps can no longer
* expose app's private directory for secondary storage. Apps should put files to
* /storage/$volume_id/Download/ directory instead.
*/
@RequiresApi(Build.VERSION_CODES.R)
@CalledByNative
public static String[] getExternalDownloadVolumesNames() {
ArrayList<File> files = new ArrayList<>();
Set<String> volumes =
MediaStore.getExternalVolumeNames(ContextUtils.getApplicationContext());
for (String vol : volumes) {
if (!TextUtils.isEmpty(vol) && !vol.contains(MediaStore.VOLUME_EXTERNAL_PRIMARY)) {
File volumeDir = ContextUtils.getApplicationContext()
.getSystemService(StorageManager.class)
.getStorageVolume(MediaStore.Files.getContentUri(vol))
.getDirectory();
assert volumeDir.isDirectory();
assert volumeDir.exists();
File volumeDownloadDir =
new File(volumeDir.getAbsolutePath(), Environment.DIRECTORY_DOWNLOADS);
assert volumeDownloadDir.isDirectory();
assert volumeDownloadDir.exists();
files.add(volumeDownloadDir);
}
}
return toAbsolutePathStrings(files);
}
private static String[] toAbsolutePathStrings(List<File> files) {
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());
for (File file : files) {
if (file == null || TextUtils.isEmpty(file.getAbsolutePath())) continue;
absolutePaths.add(file.getAbsolutePath());
}
return absolutePaths.toArray(new String[absolutePaths.size()]);
......
......@@ -60,6 +60,18 @@ std::vector<FilePath> GetAllPrivateDownloadsDirectories() {
return file_paths;
}
std::vector<FilePath> GetSecondaryStorageDownloadDirectories() {
std::vector<std::string> dirs;
JNIEnv* env = AttachCurrentThread();
auto jarray = Java_PathUtils_getExternalDownloadVolumesNames(env);
base::android::AppendJavaStringArrayToStringVector(env, jarray, &dirs);
std::vector<base::FilePath> file_paths;
for (const auto& dir : dirs)
file_paths.emplace_back(dir);
return file_paths;
}
bool GetNativeLibraryDirectory(FilePath* result) {
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jstring> path =
......
......@@ -40,6 +40,10 @@ BASE_EXPORT bool GetDownloadsDirectory(FilePath* result);
// directory, and a private directory on external SD card.
BASE_EXPORT std::vector<FilePath> GetAllPrivateDownloadsDirectories();
// Retrieves the paths to all secondary storage download directories. e.g.
// /storage/1AEF-1A1E/Download/.
BASE_EXPORT std::vector<FilePath> GetSecondaryStorageDownloadDirectories();
// Retrieves the path to the native JNI libraries via
// ApplicationInfo.nativeLibraryDir on the Java side. The result is placed in
// the FilePath pointed to by 'result'.
......
......@@ -15,6 +15,7 @@
#endif
#if defined(OS_ANDROID)
#include "base/android/build_info.h"
#include "base/android/path_utils.h"
#endif
......@@ -76,9 +77,19 @@ bool IsAccessAllowedInternal(const base::FilePath& path,
if (external_storage_path.IsParent(path))
return true;
auto all_download_dirs = base::android::GetAllPrivateDownloadsDirectories();
for (const auto& dir : all_download_dirs)
allowlist.push_back(dir);
std::vector<base::FilePath> all_download_dirs =
base::android::GetAllPrivateDownloadsDirectories();
allowlist.insert(allowlist.end(), all_download_dirs.begin(),
all_download_dirs.end());
base::android::BuildInfo* build_info =
base::android::BuildInfo::GetInstance();
if (build_info->sdk_int() > base::android::SDK_VERSION_Q) {
std::vector<base::FilePath> all_external_download_volumes =
base::android::GetSecondaryStorageDownloadDirectories();
allowlist.insert(allowlist.end(), all_external_download_volumes.begin(),
all_external_download_volumes.end());
}
// allowlist of other allowed directories.
static const base::FilePath::CharType* const kLocalAccessAllowList[] = {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment