Merge "Add AUTO_FILL_MANAGE metrics action."
diff --git a/api/current.txt b/api/current.txt
index 849861a..cc8e709 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -29900,6 +29900,15 @@
field public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; // 0xfffffff8
}
+ public abstract class ProxyFileDescriptorCallback {
+ ctor public ProxyFileDescriptorCallback();
+ method public void onFsync() throws android.system.ErrnoException;
+ method public long onGetSize() throws android.system.ErrnoException;
+ method public int onRead(long, int, byte[]) throws android.system.ErrnoException;
+ method public abstract void onRelease();
+ method public int onWrite(long, int, byte[]) throws android.system.ErrnoException;
+ }
+
public class RecoverySystem {
method public static void installPackage(android.content.Context, java.io.File) throws java.io.IOException;
method public static void rebootWipeCache(android.content.Context) throws java.io.IOException;
@@ -30317,6 +30326,7 @@
method public boolean isEncrypted(java.io.File);
method public boolean isObbMounted(java.lang.String);
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
+ method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
field public static final java.lang.String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 683374a..d5d84a0 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -32521,6 +32521,15 @@
field public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; // 0xfffffff8
}
+ public abstract class ProxyFileDescriptorCallback {
+ ctor public ProxyFileDescriptorCallback();
+ method public void onFsync() throws android.system.ErrnoException;
+ method public long onGetSize() throws android.system.ErrnoException;
+ method public int onRead(long, int, byte[]) throws android.system.ErrnoException;
+ method public abstract void onRelease();
+ method public int onWrite(long, int, byte[]) throws android.system.ErrnoException;
+ }
+
public class RecoverySystem {
method public static void cancelScheduledUpdate(android.content.Context) throws java.io.IOException;
method public static void installPackage(android.content.Context, java.io.File) throws java.io.IOException;
@@ -33033,6 +33042,7 @@
method public boolean isEncrypted(java.io.File);
method public boolean isObbMounted(java.lang.String);
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
+ method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
field public static final java.lang.String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 6f8a96f..7933890 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -30010,6 +30010,15 @@
field public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; // 0xfffffff8
}
+ public abstract class ProxyFileDescriptorCallback {
+ ctor public ProxyFileDescriptorCallback();
+ method public void onFsync() throws android.system.ErrnoException;
+ method public long onGetSize() throws android.system.ErrnoException;
+ method public int onRead(long, int, byte[]) throws android.system.ErrnoException;
+ method public abstract void onRelease();
+ method public int onWrite(long, int, byte[]) throws android.system.ErrnoException;
+ }
+
public class RecoverySystem {
method public static void installPackage(android.content.Context, java.io.File) throws java.io.IOException;
method public static void rebootWipeCache(android.content.Context) throws java.io.IOException;
@@ -30428,6 +30437,7 @@
method public boolean isEncrypted(java.io.File);
method public boolean isObbMounted(java.lang.String);
method public boolean mountObb(java.lang.String, java.lang.String, android.os.storage.OnObbStateChangeListener);
+ method public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback) throws java.io.IOException;
method public boolean unmountObb(java.lang.String, boolean, android.os.storage.OnObbStateChangeListener);
field public static final java.lang.String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
}
diff --git a/core/java/android/os/IProxyFileDescriptorCallback.java b/core/java/android/os/ProxyFileDescriptorCallback.java
similarity index 69%
rename from core/java/android/os/IProxyFileDescriptorCallback.java
rename to core/java/android/os/ProxyFileDescriptorCallback.java
index e41e194..2e9f8d9 100644
--- a/core/java/android/os/IProxyFileDescriptorCallback.java
+++ b/core/java/android/os/ProxyFileDescriptorCallback.java
@@ -17,18 +17,20 @@
package android.os;
import android.system.ErrnoException;
+import android.system.OsConstants;
/**
* Callback that handles file system requests from ProxyFileDescriptor.
- * @hide
*/
-public interface IProxyFileDescriptorCallback {
+public abstract class ProxyFileDescriptorCallback {
/**
* Returns size of bytes provided by the file descriptor.
* @return Size of bytes
* @throws ErrnoException
*/
- long onGetSize() throws ErrnoException;
+ public long onGetSize() throws ErrnoException {
+ throw new ErrnoException("onGetSize", OsConstants.EBADF);
+ }
/**
* Provides bytes read from file descriptor.
@@ -39,7 +41,9 @@
* @return Size of bytes returned by the function.
* @throws ErrnoException
*/
- int onRead(long offset, int size, byte[] data) throws ErrnoException;
+ public int onRead(long offset, int size, byte[] data) throws ErrnoException {
+ throw new ErrnoException("onRead", OsConstants.EBADF);
+ }
/**
* Handles bytes written to file descriptor.
@@ -49,11 +53,20 @@
* @return Size of bytes processed by the function.
* @throws ErrnoException
*/
- int onWrite(long offset, int size, byte[] data) throws ErrnoException;
+ public int onWrite(long offset, int size, byte[] data) throws ErrnoException {
+ throw new ErrnoException("onWrite", OsConstants.EBADF);
+ }
/**
* Processes fsync request.
* @throws ErrnoException
*/
- void onFsync() throws ErrnoException;
+ public void onFsync() throws ErrnoException {
+ throw new ErrnoException("onFsync", OsConstants.EINVAL);
+ }
+
+ /**
+ * Invoked after the file is closed.
+ */
+ abstract public void onRelease();
}
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 27c0526..59394b2 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -25,6 +25,7 @@
import android.os.storage.StorageVolume;
import android.os.storage.VolumeInfo;
import android.os.storage.VolumeRecord;
+import com.android.internal.os.AppFuseMount;
/**
* WARNING! Update IMountService.h and IMountService.cpp if you change this
@@ -289,4 +290,6 @@
void addUserKeyAuth(int userId, int serialNumber, in byte[] token, in byte[] secret) = 70;
void fixateNewestUserKeyAuth(int userId) = 71;
void fstrim(int flags) = 72;
+ AppFuseMount mountProxyFileDescriptorBridge() = 73;
+ ParcelFileDescriptor openProxyFileDescriptor(int mountPointId, int fileId, int mode) = 74;
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 03fd8d3..85df48f 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -30,6 +30,7 @@
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
+import android.os.ProxyFileDescriptorCallback;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
@@ -44,7 +45,10 @@
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
-
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.AppFuseMount;
+import com.android.internal.os.FuseAppLoop;
import com.android.internal.os.RoSystemProperties;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.Preconditions;
@@ -62,6 +66,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -1322,6 +1327,90 @@
}
}
+
+ /** {@hide} */
+ @VisibleForTesting
+ public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
+ int mode, ProxyFileDescriptorCallback callback, ThreadFactory factory)
+ throws IOException {
+ // Retry is needed because the mount point mFuseAppLoop is using may be unmounted before
+ // invoking StorageManagerService#openProxyFileDescriptor. In this case, we need to re-mount
+ // the bridge by calling mountProxyFileDescriptorBridge.
+ int retry = 3;
+ while (retry-- > 0) {
+ try {
+ synchronized (mFuseAppLoopLock) {
+ if (mFuseAppLoop == null) {
+ final AppFuseMount mount = mStorageManager.mountProxyFileDescriptorBridge();
+ if (mount == null) {
+ Log.e(TAG, "Failed to open proxy file bridge.");
+ throw new IOException("Failed to open proxy file bridge.");
+ }
+ mFuseAppLoop = FuseAppLoop.open(mount.mountPointId, mount.fd, factory);
+ }
+
+ try {
+ final int fileId = mFuseAppLoop.registerCallback(callback);
+ final ParcelFileDescriptor pfd =
+ mStorageManager.openProxyFileDescriptor(
+ mFuseAppLoop.getMountPointId(), fileId, mode);
+ if (pfd != null) {
+ return pfd;
+ }
+ // Probably the bridge is being unmounted but mFuseAppLoop has not been
+ // noticed it yet.
+ mFuseAppLoop.unregisterCallback(fileId);
+ } catch (FuseAppLoop.UnmountedException error) {
+ Log.d(TAG, "mFuseAppLoop has been already unmounted.");
+ mFuseAppLoop = null;
+ continue;
+ }
+ }
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ break;
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ throw new IOException("Failed to mount bridge.");
+ }
+
+ /**
+ * Opens seekable ParcelFileDescriptor that routes file operation requests to
+ * ProxyFileDescriptorCallback.
+ *
+ * @param mode The desired access mode, must be one of
+ * {@link ParcelFileDescriptor#MODE_READ_ONLY},
+ * {@link ParcelFileDescriptor#MODE_WRITE_ONLY}, or
+ * {@link ParcelFileDescriptor#MODE_READ_WRITE}
+ * @param callback Callback to process file operation requests issued on returned file
+ * descriptor. The callback is invoked on a thread managed by the framework.
+ * @return Seekable ParcelFileDescriptor.
+ * @throws IOException
+ */
+ public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
+ int mode, ProxyFileDescriptorCallback callback)
+ throws IOException {
+ return openProxyFileDescriptor(mode, callback, null);
+ }
+
+ /** {@hide} */
+ @VisibleForTesting
+ public int getProxyFileDescriptorMountPointId() {
+ synchronized (mFuseAppLoopLock) {
+ return mFuseAppLoop != null ? mFuseAppLoop.getMountPointId() : -1;
+ }
+ }
+
+ private final Object mFuseAppLoopLock = new Object();
+
+ @GuardedBy("mFuseAppLoopLock")
+ private @Nullable FuseAppLoop mFuseAppLoop = null;
+
/// Consts to match the password types in cryptfs.h
/** @hide */
public static final int CRYPT_TYPE_PASSWORD = 0;
diff --git a/core/java/com/android/internal/os/AppFuseMount.aidl b/core/java/com/android/internal/os/AppFuseMount.aidl
new file mode 100644
index 0000000..66cf95b
--- /dev/null
+++ b/core/java/com/android/internal/os/AppFuseMount.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package com.android.internal.os;
+
+parcelable AppFuseMount;
diff --git a/core/java/com/android/internal/os/AppFuseMount.java b/core/java/com/android/internal/os/AppFuseMount.java
index b392186..04d7211 100644
--- a/core/java/com/android/internal/os/AppFuseMount.java
+++ b/core/java/com/android/internal/os/AppFuseMount.java
@@ -19,14 +19,26 @@
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
-import java.io.File;
+import android.os.storage.IStorageManager;
+import com.android.internal.util.Preconditions;
+/**
+ * Parcelable class representing AppFuse mount.
+ * This conveys the result for IStorageManager#openProxyFileDescriptor.
+ * @see IStorageManager#openProxyFileDescriptor
+ */
public class AppFuseMount implements Parcelable {
- final public File mountPoint;
+ final public int mountPointId;
final public ParcelFileDescriptor fd;
- public AppFuseMount(File mountPoint, ParcelFileDescriptor fd) {
- this.mountPoint = mountPoint;
+ /**
+ * @param mountPointId Integer number for mount point that is unique in the lifetime of
+ * StorageManagerService.
+ * @param fd File descriptor pointing /dev/fuse and tagged with the mount point.
+ */
+ public AppFuseMount(int mountPointId, ParcelFileDescriptor fd) {
+ Preconditions.checkNotNull(fd);
+ this.mountPointId = mountPointId;
this.fd = fd;
}
@@ -37,7 +49,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(this.mountPoint.getPath());
+ dest.writeInt(this.mountPointId);
dest.writeParcelable(fd, flags);
}
@@ -45,7 +57,7 @@
new Parcelable.Creator<AppFuseMount>() {
@Override
public AppFuseMount createFromParcel(Parcel in) {
- return new AppFuseMount(new File(in.readString()), in.readParcelable(null));
+ return new AppFuseMount(in.readInt(), in.readParcelable(null));
}
@Override
diff --git a/core/java/com/android/internal/os/FuseAppLoop.java b/core/java/com/android/internal/os/FuseAppLoop.java
index 34253ce..3603b6d 100644
--- a/core/java/com/android/internal/os/FuseAppLoop.java
+++ b/core/java/com/android/internal/os/FuseAppLoop.java
@@ -18,34 +18,38 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.os.IProxyFileDescriptorCallback;
+import android.os.ProxyFileDescriptorCallback;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.OsConstants;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
-import java.io.File;
-import java.io.FileNotFoundException;
import java.io.IOException;
+import java.util.concurrent.ThreadFactory;
public class FuseAppLoop {
private static final String TAG = "FuseAppLoop";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
public static final int ROOT_INODE = 1;
private static final int MIN_INODE = 2;
+ private static final ThreadFactory sDefaultThreadFactory = new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ return new Thread(r, TAG);
+ }
+ };
private final Object mLock = new Object();
- private final File mParent;
+ private final int mMountPointId;
+ private final Thread mThread;
@GuardedBy("mLock")
private final SparseArray<CallbackEntry> mCallbackMap = new SparseArray<>();
- @GuardedBy("mLock")
- private boolean mActive = true;
-
/**
* Sequential number can be used as file name and inode in AppFuse.
* 0 is regarded as an error, 1 is mount point. So we start the number from 2.
@@ -53,35 +57,40 @@
@GuardedBy("mLock")
private int mNextInode = MIN_INODE;
- private FuseAppLoop(@NonNull File parent) {
- mParent = parent;
- }
-
- public static @NonNull FuseAppLoop open(
- @NonNull File parent, @NonNull ParcelFileDescriptor fd) {
- Preconditions.checkNotNull(parent);
- Preconditions.checkNotNull(fd);
- final FuseAppLoop bridge = new FuseAppLoop(parent);
+ private FuseAppLoop(
+ int mountPointId, @NonNull ParcelFileDescriptor fd, @Nullable ThreadFactory factory) {
+ mMountPointId = mountPointId;
final int rawFd = fd.detachFd();
- new Thread(new Runnable() {
+ if (factory == null) {
+ factory = sDefaultThreadFactory;
+ }
+ mThread = factory.newThread(new Runnable() {
@Override
public void run() {
- bridge.native_start_loop(rawFd);
+ // rawFd is closed by native_start_loop. Java code does not need to close it.
+ native_start_loop(rawFd);
}
- }, TAG).start();
- return bridge;
+ });
}
- public @NonNull ParcelFileDescriptor openFile(int mode, IProxyFileDescriptorCallback callback)
+ public static @NonNull FuseAppLoop open(int mountPointId, @NonNull ParcelFileDescriptor fd,
+ @Nullable ThreadFactory factory) {
+ Preconditions.checkNotNull(fd);
+ final FuseAppLoop loop = new FuseAppLoop(mountPointId, fd, factory);
+ loop.mThread.start();
+ return loop;
+ }
+
+ public int registerCallback(@NonNull ProxyFileDescriptorCallback callback)
throws UnmountedException, IOException {
- int id;
+ if (mThread.getState() == Thread.State.TERMINATED) {
+ throw new UnmountedException();
+ }
synchronized (mLock) {
- if (!mActive) {
- throw new UnmountedException();
- }
if (mCallbackMap.size() >= Integer.MAX_VALUE - MIN_INODE) {
throw new IOException("Too many opened files.");
}
+ int id;
while (true) {
id = mNextInode;
mNextInode++;
@@ -92,24 +101,17 @@
break;
}
}
-
- // Register callback after we succeed to create pfd.
mCallbackMap.put(id, new CallbackEntry(callback));
- }
- try {
- return ParcelFileDescriptor.open(new File(mParent, String.valueOf(id)), mode);
- } catch (FileNotFoundException error) {
- synchronized (mLock) {
- mCallbackMap.remove(id);
- }
- throw error;
+ return id;
}
}
- public @Nullable File getMountPoint() {
- synchronized (mLock) {
- return mActive ? mParent : null;
- }
+ public void unregisterCallback(int id) {
+ mCallbackMap.remove(id);
+ }
+
+ public int getMountPointId() {
+ return mMountPointId;
}
private CallbackEntry getCallbackEntryOrThrowLocked(long inode) throws ErrnoException {
@@ -128,7 +130,7 @@
try {
return getCallbackEntryOrThrowLocked(inode).callback.onGetSize();
} catch (ErrnoException exp) {
- return -exp.errno;
+ return getError(exp);
}
}
}
@@ -147,7 +149,7 @@
// file twice.
return (int) inode;
} catch (ErrnoException exp) {
- return -exp.errno;
+ return getError(exp);
}
}
}
@@ -160,7 +162,7 @@
getCallbackEntryOrThrowLocked(inode).callback.onFsync();
return 0;
} catch (ErrnoException exp) {
- return -exp.errno;
+ return getError(exp);
}
}
}
@@ -169,12 +171,14 @@
@SuppressWarnings("unused")
private int onRelease(long inode) {
synchronized(mLock) {
- mCallbackMap.remove(checkInode(inode));
- if (mCallbackMap.size() == 0) {
- mActive = false;
- return -1;
+ try {
+ getCallbackEntryOrThrowLocked(inode).callback.onRelease();
+ return 0;
+ } catch (ErrnoException exp) {
+ return getError(exp);
+ } finally {
+ mCallbackMap.remove(checkInode(inode));
}
- return 0;
}
}
@@ -185,7 +189,7 @@
try {
return getCallbackEntryOrThrowLocked(inode).callback.onRead(offset, size, bytes);
} catch (ErrnoException exp) {
- return -exp.errno;
+ return getError(exp);
}
}
}
@@ -197,11 +201,17 @@
try {
return getCallbackEntryOrThrowLocked(inode).callback.onWrite(offset, size, bytes);
} catch (ErrnoException exp) {
- return -exp.errno;
+ return getError(exp);
}
}
}
+ private static int getError(@NonNull ErrnoException exp) {
+ // Should not return ENOSYS because the kernel stops
+ // dispatching the FUSE action once FUSE implementation returns ENOSYS for the action.
+ return exp.errno != OsConstants.ENOSYS ? -exp.errno : -OsConstants.EIO;
+ }
+
native boolean native_start_loop(int fd);
private static int checkInode(long inode) {
@@ -212,9 +222,9 @@
public static class UnmountedException extends Exception {}
private static class CallbackEntry {
- final IProxyFileDescriptorCallback callback;
+ final ProxyFileDescriptorCallback callback;
boolean opened;
- CallbackEntry(IProxyFileDescriptorCallback callback) {
+ CallbackEntry(ProxyFileDescriptorCallback callback) {
Preconditions.checkNotNull(callback);
this.callback = callback;
}
diff --git a/core/jni/com_android_internal_os_FuseAppLoop.cpp b/core/jni/com_android_internal_os_FuseAppLoop.cpp
index 92a6934..dd003eb 100644
--- a/core/jni/com_android_internal_os_FuseAppLoop.cpp
+++ b/core/jni/com_android_internal_os_FuseAppLoop.cpp
@@ -51,7 +51,6 @@
JNIEnv* const mEnv;
jobject const mSelf;
ScopedLocalRef<jbyteArray> mJniBuffer;
- bool mActive;
template <typename T>
T checkException(T result) const {
@@ -67,8 +66,7 @@
Callback(JNIEnv* env, jobject self) :
mEnv(env),
mSelf(self),
- mJniBuffer(env, nullptr),
- mActive(true) {}
+ mJniBuffer(env, nullptr) {}
bool Init() {
mJniBuffer.reset(mEnv->NewByteArray(kBufferSize));
@@ -76,7 +74,7 @@
}
bool IsActive() override {
- return mActive;
+ return true;
}
int64_t OnGetSize(uint64_t inode) override {
@@ -92,10 +90,7 @@
}
int32_t OnRelease(uint64_t inode) override {
- if (checkException(mEnv->CallIntMethod(mSelf, gOnReleaseMethod, inode)) == -1) {
- mActive = false;
- }
- return fuse::kFuseSuccess;
+ return checkException(mEnv->CallIntMethod(mSelf, gOnReleaseMethod, inode));
}
int32_t OnRead(uint64_t inode, uint64_t offset, uint32_t size, void* buffer) override {
diff --git a/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java b/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java
index 37f0007..ff98eb7 100644
--- a/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java
+++ b/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java
@@ -18,15 +18,20 @@
import android.content.Context;
import android.os.Environment;
+import android.os.ProxyFileDescriptorCallback;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.Suppress;
import android.util.Log;
import com.android.frameworks.coretests.R;
-
+import com.android.internal.os.FuseAppLoop;
import java.io.DataInputStream;
import java.io.IOException;
+import java.util.concurrent.ThreadFactory;
import java.io.File;
import java.io.FileInputStream;
@@ -243,4 +248,47 @@
verifyObb1Contents(filePath);
unmountObb(filePath, DONT_FORCE);
}
+
+ @LargeTest
+ public void testOpenProxyFileDescriptor() throws Exception {
+ final ProxyFileDescriptorCallback callback = new ProxyFileDescriptorCallback() {
+ @Override
+ public long onGetSize() throws ErrnoException {
+ return 0;
+ }
+
+ @Override
+ public void onRelease() {}
+ };
+
+ final MyThreadFactory factory = new MyThreadFactory();
+ int firstMountId;
+ try (final ParcelFileDescriptor fd = mSm.openProxyFileDescriptor(
+ ParcelFileDescriptor.MODE_READ_ONLY, callback, factory)) {
+ assertNotSame(Thread.State.TERMINATED, factory.thread.getState());
+ firstMountId = mSm.getProxyFileDescriptorMountPointId();
+ assertNotSame(-1, firstMountId);
+ }
+
+ // After closing descriptor, the loop should terminate.
+ factory.thread.join(3000);
+ assertEquals(Thread.State.TERMINATED, factory.thread.getState());
+
+ // StorageManager should mount another bridge on the next open request.
+ try (final ParcelFileDescriptor fd = mSm.openProxyFileDescriptor(
+ ParcelFileDescriptor.MODE_WRITE_ONLY, callback, factory)) {
+ assertNotSame(Thread.State.TERMINATED, factory.thread.getState());
+ assertNotSame(firstMountId, mSm.getProxyFileDescriptorMountPointId());
+ }
+ }
+
+ private static class MyThreadFactory implements ThreadFactory {
+ Thread thread = null;
+
+ @Override
+ public Thread newThread(Runnable r) {
+ thread = new Thread(r);
+ return thread;
+ }
+ }
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 55d31c3..f9b9d6f 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -88,11 +88,13 @@
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IMediaContainerService;
+import com.android.internal.os.AppFuseMount;
import com.android.internal.os.SomeArgs;
import com.android.internal.os.Zygote;
import com.android.internal.util.ArrayUtils;
@@ -104,7 +106,7 @@
import com.android.server.NativeDaemonConnector.Command;
import com.android.server.NativeDaemonConnector.SensitiveArg;
import com.android.server.pm.PackageManagerService;
-
+import com.android.server.storage.AppFuseBridge;
import libcore.io.IoUtils;
import libcore.util.EmptyArray;
@@ -135,6 +137,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
+import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -337,6 +340,15 @@
private volatile int mCurrentUserId = UserHandle.USER_SYSTEM;
+ /** Holding lock for AppFuse business */
+ private final Object mAppFuseLock = new Object();
+
+ @GuardedBy("mAppFuseLock")
+ private int mNextAppFuseName = 0;
+
+ @GuardedBy("mAppFuseLock")
+ private final SparseArray<Integer> mAppFusePids = new SparseArray<>();
+
private VolumeInfo findVolumeByIdOrThrow(String id) {
synchronized (mLock) {
final VolumeInfo vol = mVolumes.get(id);
@@ -3010,6 +3022,128 @@
}
}
+
+ class CloseableHolder<T extends AutoCloseable> implements AutoCloseable {
+ @Nullable T mCloseable;
+
+ CloseableHolder(T closeable) {
+ mCloseable = closeable;
+ }
+
+ @Nullable T get() {
+ return mCloseable;
+ }
+
+ @Nullable T release() {
+ final T result = mCloseable;
+ mCloseable = null;
+ return result;
+ }
+
+ @Override
+ public void close() {
+ if (mCloseable != null) {
+ IoUtils.closeQuietly(mCloseable);
+ }
+ }
+ }
+
+ class AppFuseMountScope implements AppFuseBridge.IMountScope {
+ final int mUid;
+ final int mName;
+ final ParcelFileDescriptor mDeviceFd;
+
+ AppFuseMountScope(int uid, int pid, int name) throws NativeDaemonConnectorException {
+ final NativeDaemonEvent event = mConnector.execute(
+ "appfuse", "mount", uid, Process.myPid(), name);
+ mUid = uid;
+ mName = name;
+ synchronized (mLock) {
+ mAppFusePids.put(name, pid);
+ }
+ if (event.getFileDescriptors() != null &&
+ event.getFileDescriptors().length > 0) {
+ mDeviceFd = new ParcelFileDescriptor(event.getFileDescriptors()[0]);
+ } else {
+ mDeviceFd = null;
+ }
+ }
+
+ @Override
+ public void close() throws NativeDaemonConnectorException {
+ try {
+ IoUtils.closeQuietly(mDeviceFd);
+ mConnector.execute(
+ "appfuse", "unmount", mUid, Process.myPid(), mName);
+ } finally {
+ synchronized (mLock) {
+ mAppFusePids.delete(mName);
+ }
+ }
+ }
+
+ @Override
+ public ParcelFileDescriptor getDeviceFileDescriptor() {
+ return mDeviceFd;
+ }
+ }
+
+ @Override
+ public AppFuseMount mountProxyFileDescriptorBridge() throws RemoteException {
+ final int uid = Binder.getCallingUid();
+ final int pid = Binder.getCallingPid();
+ final int name;
+ synchronized (mAppFuseLock) {
+ name = mNextAppFuseName++;
+ }
+ try (CloseableHolder<AppFuseMountScope> mountScope =
+ new CloseableHolder<>(new AppFuseMountScope(uid, pid, name))) {
+ if (mountScope.get().getDeviceFileDescriptor() == null) {
+ throw new RemoteException("Failed to obtain device FD");
+ }
+
+ // Create communication channel.
+ final ArrayBlockingQueue<Boolean> channel = new ArrayBlockingQueue<>(1);
+ final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createSocketPair();
+ try (CloseableHolder<ParcelFileDescriptor> remote = new CloseableHolder<>(fds[0])) {
+ new Thread(
+ new AppFuseBridge(mountScope.release(), fds[1], channel),
+ AppFuseBridge.TAG).start();
+ if (!channel.take()) {
+ throw new RemoteException("Failed to init AppFuse mount point");
+ }
+
+ return new AppFuseMount(name, remote.release());
+ }
+ } catch (NativeDaemonConnectorException e){
+ throw e.rethrowAsParcelableException();
+ } catch (IOException | InterruptedException error) {
+ throw new RemoteException(error.getMessage());
+ }
+ }
+
+ @Override
+ public ParcelFileDescriptor openProxyFileDescriptor(int mountId, int fileId, int mode) {
+ final int uid = Binder.getCallingUid();
+ final int pid = Binder.getCallingPid();
+ try {
+ synchronized (mAppFuseLock) {
+ final int expectedPid = mAppFusePids.get(mountId, -1);
+ if (expectedPid == -1) {
+ Slog.i(TAG, "The mount point has already been unmounted");
+ return null;
+ }
+ if (expectedPid != pid) {
+ throw new SecurityException("Mount point was not created by this process.");
+ }
+ }
+ return AppFuseBridge.openFile(uid, mountId, fileId, mode);
+ } catch (FileNotFoundException error) {
+ Slog.e(TAG, "Failed to openProxyFileDescriptor", error);
+ return null;
+ }
+ }
+
@Override
public int mkdirs(String callingPkg, String appPath) {
final int userId = UserHandle.getUserId(Binder.getCallingUid());
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0f3f9ce..df5f01d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -144,7 +144,8 @@
*/
public class AudioService extends IAudioService.Stub
implements AccessibilityManager.TouchExplorationStateChangeListener,
- AccessibilityManager.AccessibilityStateChangeListener{
+ AccessibilityManager.AccessibilityStateChangeListener,
+ AccessibilityManager.AccessibilityServicesStateChangeListener {
private static final String TAG = "AudioService";
@@ -780,7 +781,7 @@
TAG,
SAFE_VOLUME_CONFIGURE_TIMEOUT_MS);
- initA11yMonitoring(mContext);
+ initA11yMonitoring();
onIndicateSystemReady();
}
@@ -5925,13 +5926,25 @@
//==========================================================================================
// Accessibility
- private void initA11yMonitoring(Context ctxt) {
- AccessibilityManager accessibilityManager =
- (AccessibilityManager) ctxt.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ /**
+ * Compile-time constant to enable the use of an independent a11y volume:
+ * - set to true to listen to a11y services state changes and read
+ * the whether any exposes the FLAG_ENABLE_ACCESSIBILITY_VOLUME flag
+ * - set to false to listen to when accessibility services are started (e.g. "TalkBack started")
+ */
+ private static final boolean USE_FLAG_ENABLE_ACCESSIBILITY_VOLUME = true;
+
+ private void initA11yMonitoring() {
+ final AccessibilityManager accessibilityManager =
+ (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
updateDefaultStreamOverrideDelay(accessibilityManager.isTouchExplorationEnabled());
updateA11yVolumeAlias(accessibilityManager.isEnabled());
accessibilityManager.addTouchExplorationStateChangeListener(this);
- accessibilityManager.addAccessibilityStateChangeListener(this);
+ if (USE_FLAG_ENABLE_ACCESSIBILITY_VOLUME) {
+ accessibilityManager.addAccessibilityServicesStateChangeListener(this);
+ } else {
+ accessibilityManager.addAccessibilityStateChangeListener(this);
+ }
}
//---------------------------------------------------------------------------------
@@ -5969,21 +5982,31 @@
private static boolean sIndependentA11yVolume = false;
+ // implementation of AccessibilityStateChangeListener
@Override
public void onAccessibilityStateChanged(boolean enabled) {
updateA11yVolumeAlias(enabled);
}
- private void updateA11yVolumeAlias(boolean a11Enabled) {
- if (DEBUG_VOL) Log.d(TAG, "Accessibility mode changed to " + a11Enabled);
- // a11y has its own volume stream when a11y service is enabled
- sIndependentA11yVolume = a11Enabled;
- // update the volume mapping scheme
- updateStreamVolumeAlias(true /*updateVolumes*/, TAG);
- // update the volume controller behavior
- mVolumeController.setA11yMode(sIndependentA11yVolume ?
- VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME :
- VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME);
+ // implementation of AccessibilityServicesStateChangeListener
+ @Override
+ public void onAccessibilityServicesStateChanged() {
+ final AccessibilityManager accessibilityManager =
+ (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ updateA11yVolumeAlias(accessibilityManager.isAccessibilityVolumeStreamActive());
+ }
+
+ private void updateA11yVolumeAlias(boolean a11VolEnabled) {
+ if (DEBUG_VOL) Log.d(TAG, "Accessibility volume enabled = " + a11VolEnabled);
+ if (sIndependentA11yVolume != a11VolEnabled) {
+ sIndependentA11yVolume = a11VolEnabled;
+ // update the volume mapping scheme
+ updateStreamVolumeAlias(true /*updateVolumes*/, TAG);
+ // update the volume controller behavior
+ mVolumeController.setA11yMode(sIndependentA11yVolume ?
+ VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME :
+ VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME);
+ }
}
//==========================================================================================
diff --git a/services/core/java/com/android/server/storage/AppFuseBridge.java b/services/core/java/com/android/server/storage/AppFuseBridge.java
index 23be9a3..5a1f473 100644
--- a/services/core/java/com/android/server/storage/AppFuseBridge.java
+++ b/services/core/java/com/android/server/storage/AppFuseBridge.java
@@ -16,79 +16,95 @@
package com.android.server.storage;
-import android.annotation.CallSuper;
-import android.annotation.WorkerThread;
-import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.Os;
-import android.system.OsConstants;
-import android.util.Log;
-import com.android.internal.os.AppFuseMount;
+import com.android.internal.util.Preconditions;
import libcore.io.IoUtils;
-
import java.io.File;
-import java.io.FileDescriptor;
import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.BlockingQueue;
-public class AppFuseBridge implements Runnable {
- private static final String TAG = AppFuseBridge.class.getSimpleName();
-
- private final FileDescriptor mDeviceFd;
- private final FileDescriptor mProxyFd;
- private final CountDownLatch mMountLatch = new CountDownLatch(1);
+/**
+ * Runnable that delegates FUSE command from the kernel to application.
+ * run() blocks until all opened files on the FUSE mount point are closed. So this should be run in
+ * a separated thread.
+ */
+public class AppFuseBridge implements Runnable, AutoCloseable {
+ public static final String TAG = "AppFuseBridge";
/**
- * @param deviceFd FD of /dev/fuse. Ownership of fd is taken by AppFuseBridge.
- * @param proxyFd FD of socket pair. Ownership of fd is taken by AppFuseBridge.
+ * The path AppFuse is mounted to.
+ * The first number is UID who is mounting the FUSE.
+ * THe second number is mount ID.
+ * The path must be sync with vold.
*/
- private AppFuseBridge(FileDescriptor deviceFd, FileDescriptor proxyFd) {
- mDeviceFd = deviceFd;
+ private static final String APPFUSE_MOUNT_NAME_TEMPLATE = "/mnt/appfuse/%d_%d";
+
+ private final IMountScope mMountScope;
+ private final ParcelFileDescriptor mProxyFd;
+ private final BlockingQueue<Boolean> mChannel;
+
+ /**
+ * @param mountScope Listener to unmount mount point.
+ * @param proxyFd FD of socket pair. Ownership of FD is taken by AppFuseBridge.
+ * @param channel Channel that the runnable send mount result to.
+ */
+ public AppFuseBridge(
+ IMountScope mountScope, ParcelFileDescriptor proxyFd, BlockingQueue<Boolean> channel) {
+ Preconditions.checkNotNull(mountScope);
+ Preconditions.checkNotNull(proxyFd);
+ Preconditions.checkNotNull(channel);
+ mMountScope = mountScope;
mProxyFd = proxyFd;
- }
-
- public static AppFuseMount startMessageLoop(
- int uid,
- String name,
- FileDescriptor deviceFd,
- Handler handler,
- ParcelFileDescriptor.OnCloseListener listener)
- throws IOException, ErrnoException, InterruptedException {
- final FileDescriptor localFd = new FileDescriptor();
- final FileDescriptor remoteFd = new FileDescriptor();
- // Needs to specify OsConstants.SOCK_SEQPACKET to keep message boundaries.
- Os.socketpair(OsConstants.AF_UNIX, OsConstants.SOCK_SEQPACKET, 0, remoteFd, localFd);
-
- // Caller must invoke #start() after instantiate AppFuseBridge.
- // Otherwise FDs will be leaked.
- final AppFuseBridge bridge = new AppFuseBridge(deviceFd, localFd);
- final Thread thread = new Thread(bridge, TAG);
- thread.start();
- try {
- bridge.mMountLatch.await();
- } catch (InterruptedException error) {
- throw error;
- }
- return new AppFuseMount(
- new File("/mnt/appfuse/" + uid + "_" + name),
- ParcelFileDescriptor.fromFd(remoteFd, handler, listener));
+ mChannel = channel;
}
@Override
public void run() {
- // deviceFd and proxyFd must be closed in native_start_loop.
- final int deviceFd = mDeviceFd.getInt$();
- final int proxyFd = mProxyFd.getInt$();
- mDeviceFd.setInt$(-1);
- mProxyFd.setInt$(-1);
- native_start_loop(deviceFd, proxyFd);
+ try {
+ // deviceFd and proxyFd must be closed in native_start_loop.
+ native_start_loop(
+ mMountScope.getDeviceFileDescriptor().detachFd(),
+ mProxyFd.detachFd());
+ } finally {
+ close();
+ }
+ }
+
+ public static ParcelFileDescriptor openFile(int uid, int mountId, int fileId, int mode)
+ throws FileNotFoundException {
+ final File mountPoint = getMountPoint(uid, mountId);
+ try {
+ if (Os.stat(mountPoint.getPath()).st_ino != 1) {
+ throw new FileNotFoundException("Could not find bridge mount point.");
+ }
+ } catch (ErrnoException e) {
+ throw new FileNotFoundException(
+ "Failed to stat mount point: " + mountPoint.getParent());
+ }
+ return ParcelFileDescriptor.open(new File(mountPoint, String.valueOf(fileId)), mode);
+ }
+
+ private static File getMountPoint(int uid, int mountId) {
+ return new File(String.format(APPFUSE_MOUNT_NAME_TEMPLATE, uid, mountId));
+ }
+
+ @Override
+ public void close() {
+ IoUtils.closeQuietly(mMountScope);
+ IoUtils.closeQuietly(mProxyFd);
+ // Invoke countDown here in case where close is invoked before mount.
+ mChannel.offer(false);
}
// Used by com_android_server_storage_AppFuse.cpp.
private void onMount() {
- mMountLatch.countDown();
+ mChannel.offer(true);
+ }
+
+ public static interface IMountScope extends AutoCloseable {
+ ParcelFileDescriptor getDeviceFileDescriptor();
}
private native boolean native_start_loop(int deviceFd, int proxyFd);