Merge "Support transcode path RRO for OEMs" into sc-mainline-prod
diff --git a/jni/FuseDaemon.cpp b/jni/FuseDaemon.cpp
index 1e2a82b..53f86cd 100755
--- a/jni/FuseDaemon.cpp
+++ b/jni/FuseDaemon.cpp
@@ -247,14 +247,16 @@
/* Single FUSE mount */
struct fuse {
- explicit fuse(const std::string& _path, ino_t _ino)
+ explicit fuse(const std::string& _path, const ino_t _ino,
+ const std::vector<string>& _supported_transcoding_relative_paths)
: path(_path),
tracker(mediaprovider::fuse::NodeTracker(&lock)),
root(node::CreateRoot(_path, &lock, _ino, &tracker)),
mp(0),
zero_addr(0),
disable_dentry_cache(false),
- passthrough(false) {}
+ passthrough(false),
+ supported_transcoding_relative_paths(_supported_transcoding_relative_paths) {}
inline bool IsRoot(const node* node) const { return node == root; }
@@ -287,6 +289,22 @@
return node::ToInode(node);
}
+ inline bool IsTranscodeSupportedPath(const string& path) {
+ // Keep in sync with MediaProvider#supportsTranscode
+ if (!android::base::EndsWithIgnoreCase(path, ".mp4")) {
+ return false;
+ }
+
+ const std::string& base_path = GetEffectiveRootPath() + "/";
+ for (const std::string& relative_path : supported_transcoding_relative_paths) {
+ if (android::base::StartsWithIgnoreCase(path, base_path + relative_path)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
std::recursive_mutex lock;
const string path;
// The Inode tracker associated with this FUSE instance.
@@ -314,6 +332,7 @@
std::atomic_bool passthrough;
// FUSE device id.
std::atomic_uint dev;
+ const std::vector<string> supported_transcoding_relative_paths;
};
enum class FuseOp { lookup, readdir, mknod, mkdir, create };
@@ -437,13 +456,6 @@
path, fuse->GetTransformsDir() + "/" + TRANSFORM_SYNTHETIC_DIR);
}
-static inline bool is_transcode_supported_path(const string& path, struct fuse* fuse) {
- // Keep in sync with MediaProvider#supportsTranscode
- return android::base::EndsWithIgnoreCase(path, ".mp4") &&
- android::base::StartsWithIgnoreCase(path,
- fuse->GetEffectiveRootPath() + "/dcim/camera/");
-}
-
static inline bool is_transforms_dir_path(const string& path, struct fuse* fuse) {
return android::base::StartsWithIgnoreCase(path, fuse->GetTransformsDir());
}
@@ -484,7 +496,7 @@
return std::make_unique<mediaprovider::fuse::FileLookupResult>(0, 0, 0, true, false, "");
}
- if (!synthetic_path && !is_transcode_supported_path(path, fuse)) {
+ if (!synthetic_path && !fuse->IsTranscodeSupportedPath(path)) {
// Transforms are only supported for synthetic or transcode-supported paths
return std::make_unique<mediaprovider::fuse::FileLookupResult>(0, 0, 0, true, false, "");
}
@@ -2040,7 +2052,8 @@
return active.load(std::memory_order_acquire);
}
-void FuseDaemon::Start(android::base::unique_fd fd, const std::string& path) {
+void FuseDaemon::Start(android::base::unique_fd fd, const std::string& path,
+ const std::vector<std::string>& supported_transcoding_relative_paths) {
android::base::SetDefaultTag(LOG_TAG);
struct fuse_args args;
@@ -2065,7 +2078,7 @@
return;
}
- struct fuse fuse_default(path, stat.st_ino);
+ struct fuse fuse_default(path, stat.st_ino, supported_transcoding_relative_paths);
fuse_default.mp = ∓
// fuse_default is stack allocated, but it's safe to save it as an instance variable because
// this method blocks and FuseDaemon#active tells if we are currently blocking
diff --git a/jni/FuseDaemon.h b/jni/FuseDaemon.h
index f7c5614..3164830 100644
--- a/jni/FuseDaemon.h
+++ b/jni/FuseDaemon.h
@@ -37,7 +37,8 @@
/**
* Start the FUSE daemon loop that will handle filesystem calls.
*/
- void Start(android::base::unique_fd fd, const std::string& path);
+ void Start(android::base::unique_fd fd, const std::string& path,
+ const std::vector<std::string>& supported_transcoding_relative_paths);
/**
* Checks if the FUSE daemon is started.
diff --git a/jni/com_android_providers_media_FuseDaemon.cpp b/jni/com_android_providers_media_FuseDaemon.cpp
index d67123d..46ed041 100644
--- a/jni/com_android_providers_media_FuseDaemon.cpp
+++ b/jni/com_android_providers_media_FuseDaemon.cpp
@@ -17,6 +17,7 @@
// Need to use LOGE_EX.
#define LOG_TAG "FuseDaemonJNI"
+#include <nativehelper/scoped_local_ref.h>
#include <nativehelper/scoped_utf_chars.h>
#include <string>
@@ -32,14 +33,39 @@
constexpr const char* CLASS_NAME = "com/android/providers/media/fuse/FuseDaemon";
static jclass gFuseDaemonClass;
+static std::vector<std::string> get_supported_transcoding_relative_paths(
+ JNIEnv* env, jobjectArray java_supported_transcoding_relative_paths) {
+ ScopedLocalRef<jobjectArray> j_transcoding_relative_paths(
+ env, java_supported_transcoding_relative_paths);
+ std::vector<std::string> transcoding_relative_paths;
+
+ const int transcoding_relative_paths_count =
+ env->GetArrayLength(j_transcoding_relative_paths.get());
+ for (int i = 0; i < transcoding_relative_paths_count; i++) {
+ ScopedLocalRef<jstring> j_ref_relative_path(
+ env, (jstring)env->GetObjectArrayElement(j_transcoding_relative_paths.get(), i));
+ ScopedUtfChars j_utf_relative_path(env, j_ref_relative_path.get());
+ const char* relative_path = j_utf_relative_path.c_str();
+
+ if (relative_path) {
+ transcoding_relative_paths.push_back(relative_path);
+ } else {
+ LOG(ERROR) << "Error reading supported transcoding relative path at index: " << i;
+ }
+ }
+
+ return transcoding_relative_paths;
+}
+
jlong com_android_providers_media_FuseDaemon_new(JNIEnv* env, jobject self,
jobject media_provider) {
LOG(DEBUG) << "Creating the FUSE daemon...";
return reinterpret_cast<jlong>(new fuse::FuseDaemon(env, media_provider));
}
-void com_android_providers_media_FuseDaemon_start(JNIEnv* env, jobject self, jlong java_daemon,
- jint fd, jstring java_path) {
+void com_android_providers_media_FuseDaemon_start(
+ JNIEnv* env, jobject self, jlong java_daemon, jint fd, jstring java_path,
+ jobjectArray java_supported_transcoding_relative_paths) {
LOG(DEBUG) << "Starting the FUSE daemon...";
fuse::FuseDaemon* const daemon = reinterpret_cast<fuse::FuseDaemon*>(java_daemon);
@@ -50,7 +76,11 @@
return;
}
- daemon->Start(std::move(ufd), utf_chars_path.c_str());
+ const std::vector<std::string>& transcoding_relative_paths =
+ get_supported_transcoding_relative_paths(env,
+ java_supported_transcoding_relative_paths);
+
+ daemon->Start(std::move(ufd), utf_chars_path.c_str(), transcoding_relative_paths);
}
bool com_android_providers_media_FuseDaemon_is_started(JNIEnv* env, jobject self,
@@ -127,7 +157,7 @@
const JNINativeMethod methods[] = {
{"native_new", "(Lcom/android/providers/media/MediaProvider;)J",
reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_new)},
- {"native_start", "(JILjava/lang/String;)V",
+ {"native_start", "(JILjava/lang/String;[Ljava/lang/String;)V",
reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_start)},
{"native_delete", "(J)V",
reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_delete)},
diff --git a/res/values/config.xml b/res/values/config.xml
new file mode 100644
index 0000000..d640cb7
--- /dev/null
+++ b/res/values/config.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <string-array name="config_supported_transcoding_relative_paths" translatable="false">
+ <item>DCIM/Camera/</item>
+ </string-array>
+</resources>
diff --git a/res/values/overlayable.xml b/res/values/overlayable.xml
new file mode 100644
index 0000000..6b89bf4
--- /dev/null
+++ b/res/values/overlayable.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <overlayable name="MediaProviderConfig">
+ <policy type="product|system|vendor">
+ <item type="array" name="config_supported_transcoding_relative_paths"/>
+ </policy>
+ </overlayable>
+</resources>
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 0ef9878..52acdad 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -9416,6 +9416,10 @@
}
}
+ public List<String> getSupportedTranscodingRelativePaths() {
+ return mTranscodeHelper.getSupportedRelativePaths();
+ }
+
/**
* Creating a new method for Transcoding to avoid any merge conflicts.
* TODO(b/170465810): Remove this when the code is refactored.
diff --git a/src/com/android/providers/media/TranscodeHelper.java b/src/com/android/providers/media/TranscodeHelper.java
index 488c40d..5e2b13b 100644
--- a/src/com/android/providers/media/TranscodeHelper.java
+++ b/src/com/android/providers/media/TranscodeHelper.java
@@ -19,6 +19,7 @@
import android.net.Uri;
import android.os.Bundle;
import java.io.PrintWriter;
+import java.util.List;
/** Interface over MediaTranscodeManager access */
public interface TranscodeHelper {
@@ -43,4 +44,6 @@
public boolean deleteCachedTranscodeFile(long rowId);
public void dump(PrintWriter writer);
+
+ public List<String> getSupportedRelativePaths();
}
diff --git a/src/com/android/providers/media/TranscodeHelperImpl.java b/src/com/android/providers/media/TranscodeHelperImpl.java
index 8017a59..a3e2731 100644
--- a/src/com/android/providers/media/TranscodeHelperImpl.java
+++ b/src/com/android/providers/media/TranscodeHelperImpl.java
@@ -49,6 +49,8 @@
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.Property;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
import android.content.res.XmlResourceParser;
import android.database.Cursor;
import android.media.ApplicationMediaCapabilities;
@@ -109,7 +111,10 @@
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -225,6 +230,7 @@
private final StorageManager mStorageManager;
private final ActivityManager mActivityManager;
private final File mTranscodeDirectory;
+ private final List<String> mSupportedRelativePaths;
@GuardedBy("mLock")
private UUID mTranscodeVolumeUuid;
@@ -276,6 +282,8 @@
MAX_TRANSCODE_DURATION_MS);
mTranscodeDenialController = new TranscodeDenialController(mActivityManager,
mTranscodingUiNotifier, maxTranscodeDurationMs);
+ mSupportedRelativePaths = verifySupportedRelativePaths(getStringArrayConfig(
+ R.array.config_supported_transcoding_relative_paths));
parseTranscodeCompatManifest();
// The storage namespace is a boot namespace so we actually don't expect this to be changed
@@ -803,14 +811,48 @@
}
public boolean supportsTranscode(String path) {
- File file = new File(path);
- String name = file.getName();
- final String cameraRelativePath =
- String.format("%s/%s/", Environment.DIRECTORY_DCIM, DIRECTORY_CAMERA);
+ final File file = new File(path);
+ final String name = file.getName();
+ final String relativePath = FileUtils.extractRelativePath(path);
- return !isTranscodeFile(path) && name.toLowerCase(Locale.ROOT).endsWith(".mp4")
- && path.startsWith("/storage/emulated/")
- && cameraRelativePath.equalsIgnoreCase(FileUtils.extractRelativePath(path));
+ if (isTranscodeFile(path) || !name.toLowerCase(Locale.ROOT).endsWith(".mp4")
+ || !path.startsWith("/storage/emulated/")) {
+ return false;
+ }
+
+ for (String supportedRelativePath : mSupportedRelativePaths) {
+ if (supportedRelativePath.equalsIgnoreCase(relativePath)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static List<String> verifySupportedRelativePaths(List<String> relativePaths) {
+ final List<String> verifiedPaths = new ArrayList<>();
+ final String lowerCaseDcimDir = Environment.DIRECTORY_DCIM.toLowerCase(Locale.ROOT) + "/";
+
+ for (String path : relativePaths) {
+ if (path.toLowerCase(Locale.ROOT).startsWith(lowerCaseDcimDir) && path.endsWith("/")) {
+ verifiedPaths.add(path);
+ } else {
+ Log.w(TAG, "Transcoding relative path must be a descendant of DCIM/ and end with"
+ + " '/'. Ignoring: " + path);
+ }
+ }
+
+ return verifiedPaths;
+ }
+
+ private List<String> getStringArrayConfig(int resId) {
+ final Resources res = mContext.getResources();
+ try {
+ final String[] configValue = res.getStringArray(resId);
+ return Arrays.asList(configValue);
+ } catch (NotFoundException e) {
+ return new ArrayList<String>();
+ }
}
private Optional<Boolean> checkAppCompatSupport(int uid, int fileFlags) {
@@ -1458,11 +1500,16 @@
synchronized (mLock) {
writer.println("mAppCompatMediaCapabilities=" + mAppCompatMediaCapabilities);
writer.println("mStorageTranscodingSessions=" + mStorageTranscodingSessions);
+ writer.println("mSupportedTranscodingRelativePaths=" + mSupportedRelativePaths);
dumpFinishedSessions(writer);
}
}
+ public List<String> getSupportedRelativePaths() {
+ return mSupportedRelativePaths;
+ }
+
private void dumpFinishedSessions(PrintWriter writer) {
synchronized (mLock) {
writer.println("mSuccessfulTranscodeSessions=" + mSuccessfulTranscodeSessions.keySet());
diff --git a/src/com/android/providers/media/TranscodeHelperNoOp.java b/src/com/android/providers/media/TranscodeHelperNoOp.java
index 355f21d..ee4549b 100644
--- a/src/com/android/providers/media/TranscodeHelperNoOp.java
+++ b/src/com/android/providers/media/TranscodeHelperNoOp.java
@@ -20,6 +20,9 @@
import android.os.Bundle;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* No-op transcode helper to avoid loading MediaTranscodeManager classes in Android R
*/
@@ -57,4 +60,8 @@
}
public void dump(PrintWriter writer) {}
+
+ public List<String> getSupportedRelativePaths() {
+ return new ArrayList<String>();
+ }
}
diff --git a/src/com/android/providers/media/fuse/ExternalStorageServiceImpl.java b/src/com/android/providers/media/fuse/ExternalStorageServiceImpl.java
index 1f8f3cd..956ae5d 100644
--- a/src/com/android/providers/media/fuse/ExternalStorageServiceImpl.java
+++ b/src/com/android/providers/media/fuse/ExternalStorageServiceImpl.java
@@ -70,8 +70,10 @@
// We only use the upperFileSystemPath because the media process is mounted as
// REMOUNT_MODE_PASS_THROUGH which guarantees that all /storage paths are bind
// mounts of the lower filesystem.
+ final String[] supportedTranscodingRelativePaths =
+ mediaProvider.getSupportedTranscodingRelativePaths().toArray(new String[0]);
FuseDaemon daemon = new FuseDaemon(mediaProvider, this, deviceFd, sessionId,
- upperFileSystemPath.getPath());
+ upperFileSystemPath.getPath(), supportedTranscodingRelativePaths);
daemon.start();
sFuseDaemons.put(sessionId, daemon);
}
diff --git a/src/com/android/providers/media/fuse/FuseDaemon.java b/src/com/android/providers/media/fuse/FuseDaemon.java
index 1175249..0c9ca85 100644
--- a/src/com/android/providers/media/fuse/FuseDaemon.java
+++ b/src/com/android/providers/media/fuse/FuseDaemon.java
@@ -40,18 +40,22 @@
private final MediaProvider mMediaProvider;
private final int mFuseDeviceFd;
private final String mPath;
+ private final String[] mSupportedTranscodingRelativePaths;
private final ExternalStorageServiceImpl mService;
@GuardedBy("mLock")
private long mPtr;
public FuseDaemon(@NonNull MediaProvider mediaProvider,
@NonNull ExternalStorageServiceImpl service, @NonNull ParcelFileDescriptor fd,
- @NonNull String sessionId, @NonNull String path) {
+ @NonNull String sessionId, @NonNull String path,
+ String[] supportedTranscodingRelativePaths) {
mMediaProvider = Objects.requireNonNull(mediaProvider);
mService = Objects.requireNonNull(service);
setName(Objects.requireNonNull(sessionId));
mFuseDeviceFd = Objects.requireNonNull(fd).detachFd();
mPath = Objects.requireNonNull(path);
+ mSupportedTranscodingRelativePaths
+ = Objects.requireNonNull(supportedTranscodingRelativePaths);
}
/** Starts a FUSE session. Does not return until the lower filesystem is unmounted. */
@@ -67,7 +71,7 @@
}
Log.i(TAG, "Starting thread for " + getName() + " ...");
- native_start(ptr, mFuseDeviceFd, mPath); // Blocks
+ native_start(ptr, mFuseDeviceFd, mPath, mSupportedTranscodingRelativePaths); // Blocks
Log.i(TAG, "Exiting thread for " + getName() + " ...");
synchronized (mLock) {
@@ -180,7 +184,8 @@
private native long native_new(MediaProvider mediaProvider);
// Takes ownership of the passed in file descriptor!
- private native void native_start(long daemon, int deviceFd, String path);
+ private native void native_start(long daemon, int deviceFd, String path,
+ String[] supportedTranscodingRelativePaths);
private native void native_delete(long daemon);
private native boolean native_should_open_with_fuse(long daemon, String path, boolean readLock,