1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.service.storage; 18 19 import android.annotation.BytesLong; 20 import android.annotation.IntDef; 21 import android.annotation.NonNull; 22 import android.annotation.SdkConstant; 23 import android.annotation.SystemApi; 24 import android.app.Service; 25 import android.content.Intent; 26 import android.os.Bundle; 27 import android.os.Handler; 28 import android.os.IBinder; 29 import android.os.ParcelFileDescriptor; 30 import android.os.ParcelableException; 31 import android.os.RemoteCallback; 32 import android.os.RemoteException; 33 import android.os.storage.StorageManager; 34 import android.os.storage.StorageVolume; 35 36 import com.android.internal.os.BackgroundThread; 37 38 import java.io.File; 39 import java.io.IOException; 40 import java.lang.annotation.Retention; 41 import java.lang.annotation.RetentionPolicy; 42 import java.util.UUID; 43 44 /** 45 * A service to handle filesystem I/O from other apps. 46 * 47 * <p>To extend this class, you must declare the service in your manifest file with the 48 * {@link android.Manifest.permission#BIND_EXTERNAL_STORAGE_SERVICE} permission, 49 * and include an intent filter with the {@link #SERVICE_INTERFACE} action. 50 * For example:</p> 51 * <pre> 52 * <service android:name=".ExternalStorageServiceImpl" 53 * android:exported="true" 54 * android:priority="100" 55 * android:permission="android.permission.BIND_EXTERNAL_STORAGE_SERVICE"> 56 * <intent-filter> 57 * <action android:name="android.service.storage.ExternalStorageService" /> 58 * </intent-filter> 59 * </service> 60 * </pre> 61 * @hide 62 */ 63 @SystemApi 64 public abstract class ExternalStorageService extends Service { 65 /** 66 * The Intent action that a service must respond to. Add it as an intent filter in the 67 * manifest declaration of the implementing service. 68 */ 69 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) 70 public static final String SERVICE_INTERFACE = "android.service.storage.ExternalStorageService"; 71 /** 72 * Whether the session associated with the device file descriptor when calling 73 * {@link #onStartSession} is a FUSE session. 74 */ 75 public static final int FLAG_SESSION_TYPE_FUSE = 1 << 0; 76 77 /** 78 * Whether the upper file system path specified when calling {@link #onStartSession} 79 * should be indexed. 80 */ 81 public static final int FLAG_SESSION_ATTRIBUTE_INDEXABLE = 1 << 1; 82 83 /** 84 * {@link Bundle} key for a {@link String} value. 85 * 86 * {@hide} 87 */ 88 public static final String EXTRA_SESSION_ID = 89 "android.service.storage.extra.session_id"; 90 /** 91 * {@link Bundle} key for a {@link ParcelableException} value. 92 * 93 * {@hide} 94 */ 95 public static final String EXTRA_ERROR = 96 "android.service.storage.extra.error"; 97 98 /** 99 * {@link Bundle} key for a package name {@link String} value. 100 * 101 * {@hide} 102 */ 103 public static final String EXTRA_PACKAGE_NAME = "android.service.storage.extra.package_name"; 104 105 /** @hide */ 106 @IntDef(flag = true, prefix = {"FLAG_SESSION_"}, 107 value = {FLAG_SESSION_TYPE_FUSE, FLAG_SESSION_ATTRIBUTE_INDEXABLE}) 108 @Retention(RetentionPolicy.SOURCE) 109 public @interface SessionFlag {} 110 111 private final ExternalStorageServiceWrapper mWrapper = new ExternalStorageServiceWrapper(); 112 private final Handler mHandler = BackgroundThread.getHandler(); 113 114 /** 115 * Called when the system starts a session associated with {@code deviceFd} 116 * identified by {@code sessionId} to handle filesystem I/O for other apps. The type of 117 * session and other attributes are passed in {@code flag}. 118 * 119 * <p> I/O is received as requests originating from {@code upperFileSystemPath} on 120 * {@code deviceFd}. Implementors should handle the I/O by responding to these requests 121 * using the data on the {@code lowerFileSystemPath}. 122 * 123 * <p> Additional calls to start a session for the same {@code sessionId} while the session 124 * is still starting or already started should have no effect. 125 * 126 * @param sessionId uniquely identifies a running session and used in {@link #onEndSession} 127 * @param flag specifies the type or additional attributes of a session 128 * @param deviceFd for intercepting IO from other apps 129 * @param upperFileSystemPath is the root path on which we are intercepting IO from other apps 130 * @param lowerFileSystemPath is the root path matching {@code upperFileSystemPath} containing 131 * the actual data apps are trying to access 132 */ onStartSession(@onNull String sessionId, @SessionFlag int flag, @NonNull ParcelFileDescriptor deviceFd, @NonNull File upperFileSystemPath, @NonNull File lowerFileSystemPath)133 public abstract void onStartSession(@NonNull String sessionId, @SessionFlag int flag, 134 @NonNull ParcelFileDescriptor deviceFd, @NonNull File upperFileSystemPath, 135 @NonNull File lowerFileSystemPath) throws IOException; 136 137 /** 138 * Called when the system ends the session identified by {@code sessionId}. Implementors should 139 * stop handling filesystem I/O and clean up resources from the ended session. 140 * 141 * <p> Additional calls to end a session for the same {@code sessionId} while the session 142 * is still ending or has not started should have no effect. 143 */ onEndSession(@onNull String sessionId)144 public abstract void onEndSession(@NonNull String sessionId) throws IOException; 145 146 /** 147 * Called when any volume's state changes. 148 * 149 * <p> This is required to communicate volume state changes with the Storage Service before 150 * broadcasting to other apps. The Storage Service needs to process any change in the volume 151 * state (before other apps receive a broadcast for the same) to update the database so that 152 * other apps have the correct view of the volume. 153 * 154 * <p> Blocks until the Storage Service processes/scans the volume or fails in doing so. 155 * 156 * @param vol name of the volume that was changed 157 */ onVolumeStateChanged(@onNull StorageVolume vol)158 public abstract void onVolumeStateChanged(@NonNull StorageVolume vol) throws IOException; 159 160 /** 161 * Called when any cache held by the ExternalStorageService needs to be freed. 162 * 163 * <p> Blocks until the service frees the cache or fails in doing so. 164 * 165 * @param volumeUuid uuid of the {@link StorageVolume} from which cache needs to be freed 166 * @param bytes number of bytes which need to be freed 167 */ onFreeCache(@onNull UUID volumeUuid, @BytesLong long bytes)168 public void onFreeCache(@NonNull UUID volumeUuid, @BytesLong long bytes) throws IOException { 169 throw new UnsupportedOperationException("onFreeCacheRequested not implemented"); 170 } 171 172 /** 173 * Called when {@code packageName} is about to ANR. The {@link ExternalStorageService} can 174 * show a progress dialog for the {@code reason}. 175 * 176 * @param packageName the package name of the ANR'ing app 177 * @param uid the uid of the ANR'ing app 178 * @param tid the thread id of the ANR'ing app 179 * @param reason the reason the app is ANR'ing 180 */ onAnrDelayStarted(@onNull String packageName, int uid, int tid, @StorageManager.AppIoBlockedReason int reason)181 public void onAnrDelayStarted(@NonNull String packageName, int uid, int tid, 182 @StorageManager.AppIoBlockedReason int reason) { 183 throw new UnsupportedOperationException("onAnrDelayStarted not implemented"); 184 } 185 186 @Override 187 @NonNull onBind(@onNull Intent intent)188 public final IBinder onBind(@NonNull Intent intent) { 189 return mWrapper; 190 } 191 192 private class ExternalStorageServiceWrapper extends IExternalStorageService.Stub { 193 @Override startSession(String sessionId, @SessionFlag int flag, ParcelFileDescriptor deviceFd, String upperPath, String lowerPath, RemoteCallback callback)194 public void startSession(String sessionId, @SessionFlag int flag, 195 ParcelFileDescriptor deviceFd, String upperPath, String lowerPath, 196 RemoteCallback callback) throws RemoteException { 197 mHandler.post(() -> { 198 try { 199 onStartSession(sessionId, flag, deviceFd, new File(upperPath), 200 new File(lowerPath)); 201 sendResult(sessionId, null /* throwable */, callback); 202 } catch (Throwable t) { 203 sendResult(sessionId, t, callback); 204 } 205 }); 206 } 207 208 @Override notifyVolumeStateChanged(String sessionId, StorageVolume vol, RemoteCallback callback)209 public void notifyVolumeStateChanged(String sessionId, StorageVolume vol, 210 RemoteCallback callback) { 211 mHandler.post(() -> { 212 try { 213 onVolumeStateChanged(vol); 214 sendResult(sessionId, null /* throwable */, callback); 215 } catch (Throwable t) { 216 sendResult(sessionId, t, callback); 217 } 218 }); 219 } 220 221 @Override freeCache(String sessionId, String volumeUuid, long bytes, RemoteCallback callback)222 public void freeCache(String sessionId, String volumeUuid, long bytes, 223 RemoteCallback callback) { 224 mHandler.post(() -> { 225 try { 226 onFreeCache(StorageManager.convert(volumeUuid), bytes); 227 sendResult(sessionId, null /* throwable */, callback); 228 } catch (Throwable t) { 229 sendResult(sessionId, t, callback); 230 } 231 }); 232 } 233 234 @Override endSession(String sessionId, RemoteCallback callback)235 public void endSession(String sessionId, RemoteCallback callback) throws RemoteException { 236 mHandler.post(() -> { 237 try { 238 onEndSession(sessionId); 239 sendResult(sessionId, null /* throwable */, callback); 240 } catch (Throwable t) { 241 sendResult(sessionId, t, callback); 242 } 243 }); 244 } 245 246 @Override notifyAnrDelayStarted(String packageName, int uid, int tid, int reason)247 public void notifyAnrDelayStarted(String packageName, int uid, int tid, int reason) 248 throws RemoteException { 249 mHandler.post(() -> { 250 try { 251 onAnrDelayStarted(packageName, uid, tid, reason); 252 } catch (Throwable t) { 253 // Ignored 254 } 255 }); 256 } 257 sendResult(String sessionId, Throwable throwable, RemoteCallback callback)258 private void sendResult(String sessionId, Throwable throwable, RemoteCallback callback) { 259 Bundle bundle = new Bundle(); 260 bundle.putString(EXTRA_SESSION_ID, sessionId); 261 if (throwable != null) { 262 bundle.putParcelable(EXTRA_ERROR, new ParcelableException(throwable)); 263 } 264 callback.sendResult(bundle); 265 } 266 } 267 } 268