diff --git a/api/system-current.txt b/api/system-current.txt index 76d017c448228..37ce55f8d8f85 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -29,6 +29,7 @@ package android { 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.sms { } +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 0000000000000..cc8116d090130 --- /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. + * + *

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:

+ *
+ *     <service android:name=".ExternalStorageServiceImpl"
+ *             android:exported="true"
+ *             android:priority="100"
+ *             android:permission="android.permission.BIND_EXTERNAL_STORAGE_SERVICE">
+ *         <intent-filter>
+ *             <action android:name="android.service.storage.ExternalStorageService" />
+ *         </intent-filter>
+ *     </service>
+ * 
+ * @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}. + * + *

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}. + * + *

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. + * + *

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 0000000000000..ae46f1fc2fd26 --- /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 cff9275477ca6..c0a8650a179ab 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4524,6 +4524,13 @@ + + +