Merge "Add ExternalStorageService API"
diff --git a/api/system-current.txt b/api/system-current.txt
index 76d017c..37ce55f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -29,6 +29,7 @@
     field public static final String BIND_CONTENT_SUGGESTIONS_SERVICE = "android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE";
     field public static final String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH";
     field public static final String BIND_EUICC_SERVICE = "android.permission.BIND_EUICC_SERVICE";
+    field public static final String BIND_EXTERNAL_STORAGE_SERVICE = "android.permission.BIND_EXTERNAL_STORAGE_SERVICE";
     field public static final String BIND_IMS_SERVICE = "android.permission.BIND_IMS_SERVICE";
     field public static final String BIND_KEYGUARD_APPWIDGET = "android.permission.BIND_KEYGUARD_APPWIDGET";
     field public static final String BIND_NETWORK_RECOMMENDATION_SERVICE = "android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE";
@@ -6767,6 +6768,20 @@
 
 }
 
+package android.service.storage {
+
+  public abstract class ExternalStorageService extends android.app.Service {
+    ctor public ExternalStorageService();
+    method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
+    method public abstract void onEndSession(@NonNull String) throws java.io.IOException;
+    method public abstract void onStartSession(@NonNull String, int, @NonNull android.os.ParcelFileDescriptor, @NonNull String, @NonNull String) throws java.io.IOException;
+    field public static final int FLAG_SESSION_ATTRIBUTE_INDEXABLE = 2; // 0x2
+    field public static final int FLAG_SESSION_TYPE_FUSE = 1; // 0x1
+    field public static final String SERVICE_INTERFACE = "android.service.storage.ExternalStorageService";
+  }
+
+}
+
 package android.service.textclassifier {
 
   public abstract class TextClassifierService extends android.app.Service {
diff --git a/core/java/android/service/storage/ExternalStorageService.java b/core/java/android/service/storage/ExternalStorageService.java
new file mode 100644
index 0000000..cc8116d0
--- /dev/null
+++ b/core/java/android/service/storage/ExternalStorageService.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2019 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 android.service.storage;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelableException;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A service to handle filesystem I/O from other apps.
+ *
+ * <p>To extend this class, you must declare the service in your manifest file with the
+ * {@link android.Manifest.permission#BIND_EXTERNAL_STORAGE_SERVICE} permission,
+ * and include an intent filter with the {@link #SERVICE_INTERFACE} action.
+ * For example:</p>
+ * <pre>
+ *     &lt;service android:name=".ExternalStorageServiceImpl"
+ *             android:exported="true"
+ *             android:priority="100"
+ *             android:permission="android.permission.BIND_EXTERNAL_STORAGE_SERVICE"&gt;
+ *         &lt;intent-filter&gt;
+ *             &lt;action android:name="android.service.storage.ExternalStorageService" /&gt;
+ *         &lt;/intent-filter&gt;
+ *     &lt;/service&gt;
+ * </pre>
+ * @hide
+ */
+@SystemApi
+public abstract class ExternalStorageService extends Service {
+    /**
+     * The Intent action that a service must respond to. Add it as an intent filter in the
+     * manifest declaration of the implementing service.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE = "android.service.storage.ExternalStorageService";
+    /**
+     * Whether the session associated with the device file descriptor when calling
+     * {@link #onStartSession} is a FUSE session.
+     */
+    public static final int FLAG_SESSION_TYPE_FUSE = 1 << 0;
+
+    /**
+     * Whether the upper file system path specified when calling {@link #onStartSession}
+     * should be indexed.
+     */
+    public static final int FLAG_SESSION_ATTRIBUTE_INDEXABLE = 1 << 1;
+
+    /**
+     * {@link Bundle} key for a {@link String} value.
+     *
+     * {@hide}
+     */
+    public static final String EXTRA_SESSION_ID =
+            "android.service.storage.extra.session_id";
+    /**
+     * {@link Bundle} key for a {@link ParcelableException} value.
+     *
+     * {@hide}
+     */
+    public static final String EXTRA_ERROR =
+            "android.service.storage.extra.error";
+
+    /** @hide */
+    @IntDef(flag = true, prefix = {"FLAG_SESSION_"},
+        value = {FLAG_SESSION_TYPE_FUSE, FLAG_SESSION_ATTRIBUTE_INDEXABLE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SessionFlag {}
+
+    private final ExternalStorageServiceWrapper mWrapper = new ExternalStorageServiceWrapper();
+    private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
+
+    /**
+     * Called when the system starts a session associated with {@code deviceFd}
+     * identified by {@code sessionId} to handle filesystem I/O for other apps. The type of
+     * session and other attributes are passed in {@code flag}.
+     *
+     * <p> I/O is received as requests originating from {@code upperFileSystemPath} on
+     * {@code deviceFd}. Implementors should handle the I/O by responding to these requests
+     * using the data on the {@code lowerFileSystemPath}.
+     *
+     * <p> Additional calls to start a session for the same {@code sessionId} while the session
+     * is still starting or already started should have no effect.
+     */
+    public abstract void onStartSession(@NonNull String sessionId, @SessionFlag int flag,
+            @NonNull ParcelFileDescriptor deviceFd, @NonNull String upperFileSystemPath,
+            @NonNull String lowerFileSystemPath) throws IOException;
+
+    /**
+     * Called when the system ends the session identified by {@code sessionId}. Implementors should
+     * stop handling filesystem I/O and clean up resources from the ended session.
+     *
+     * <p> Additional calls to end a session for the same {@code sessionId} while the session
+     * is still ending or has not started should have no effect.
+     */
+    public abstract void onEndSession(@NonNull String sessionId) throws IOException;
+
+    @Override
+    @NonNull
+    public final IBinder onBind(@NonNull Intent intent) {
+        return mWrapper;
+    }
+
+    private class ExternalStorageServiceWrapper extends IExternalStorageService.Stub {
+        @Override
+        public void startSession(String sessionId, @SessionFlag int flag,
+                ParcelFileDescriptor deviceFd, String upperPath, String lowerPath,
+                RemoteCallback callback) throws RemoteException {
+            mHandler.post(() -> {
+                try {
+                    onStartSession(sessionId, flag, deviceFd, upperPath, lowerPath);
+                    sendResult(sessionId, null /* throwable */, callback);
+                } catch (Throwable t) {
+                    sendResult(sessionId, t, callback);
+                }
+            });
+        }
+
+        @Override
+        public void endSession(String sessionId, RemoteCallback callback) throws RemoteException {
+            mHandler.post(() -> {
+                try {
+                    onEndSession(sessionId);
+                    sendResult(sessionId, null /* throwable */, callback);
+                } catch (Throwable t) {
+                    sendResult(sessionId, t, callback);
+                }
+            });
+        }
+
+        private void sendResult(String sessionId, Throwable throwable, RemoteCallback callback) {
+            Bundle bundle = new Bundle();
+            bundle.putString(EXTRA_SESSION_ID, sessionId);
+            if (throwable != null) {
+                bundle.putParcelable(EXTRA_ERROR, new ParcelableException(throwable));
+            }
+            callback.sendResult(bundle);
+        }
+    }
+}
diff --git a/core/java/android/service/storage/IExternalStorageService.aidl b/core/java/android/service/storage/IExternalStorageService.aidl
new file mode 100644
index 0000000..ae46f1f
--- /dev/null
+++ b/core/java/android/service/storage/IExternalStorageService.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 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 android.service.storage;
+
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallback;
+
+/**
+ * @hide
+ */
+oneway interface IExternalStorageService
+{
+    void startSession(@utf8InCpp String sessionId, int type, in ParcelFileDescriptor deviceFd,
+         @utf8InCpp String upperPath, @utf8InCpp String lowerPath, in RemoteCallback callback);
+    void endSession(@utf8InCpp String sessionId, in RemoteCallback callback);
+}
\ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index cff9275..c0a8650 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4524,6 +4524,13 @@
     <permission android:name="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Must be required by an {@link android.service.storage.ExternalStorageService} to
+         ensure that only the system can bind to it.
+         @hide This is not a third-party API (intended for OEMs and system apps).
+    -->
+    <permission android:name="android.permission.BIND_EXTERNAL_STORAGE_SERVICE"
+        android:protectionLevel="signature" />
+
     <!-- @hide Permission that allows configuring appops.
      <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.MANAGE_APPOPS"