1 /*
2  * Copyright (C) 2008 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.os.storage;
18 
19 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
20 import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
21 import static android.app.AppOpsManager.OP_LEGACY_STORAGE;
22 import static android.app.AppOpsManager.OP_READ_EXTERNAL_STORAGE;
23 import static android.app.AppOpsManager.OP_READ_MEDIA_AUDIO;
24 import static android.app.AppOpsManager.OP_READ_MEDIA_IMAGES;
25 import static android.app.AppOpsManager.OP_READ_MEDIA_VIDEO;
26 import static android.app.AppOpsManager.OP_WRITE_EXTERNAL_STORAGE;
27 import static android.app.AppOpsManager.OP_WRITE_MEDIA_AUDIO;
28 import static android.app.AppOpsManager.OP_WRITE_MEDIA_IMAGES;
29 import static android.app.AppOpsManager.OP_WRITE_MEDIA_VIDEO;
30 import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX;
31 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
32 
33 import android.annotation.BytesLong;
34 import android.annotation.IntDef;
35 import android.annotation.NonNull;
36 import android.annotation.Nullable;
37 import android.annotation.RequiresPermission;
38 import android.annotation.SdkConstant;
39 import android.annotation.SuppressLint;
40 import android.annotation.SystemApi;
41 import android.annotation.SystemService;
42 import android.annotation.TestApi;
43 import android.annotation.WorkerThread;
44 import android.app.Activity;
45 import android.app.ActivityThread;
46 import android.app.AppGlobals;
47 import android.app.AppOpsManager;
48 import android.compat.annotation.UnsupportedAppUsage;
49 import android.content.ContentResolver;
50 import android.content.Context;
51 import android.content.Intent;
52 import android.content.pm.ApplicationInfo;
53 import android.content.pm.IPackageMoveObserver;
54 import android.content.pm.PackageManager;
55 import android.content.res.ObbInfo;
56 import android.content.res.ObbScanner;
57 import android.net.Uri;
58 import android.os.Binder;
59 import android.os.Environment;
60 import android.os.FileUtils;
61 import android.os.Handler;
62 import android.os.IInstalld;
63 import android.os.IVold;
64 import android.os.IVoldTaskListener;
65 import android.os.Looper;
66 import android.os.Message;
67 import android.os.ParcelFileDescriptor;
68 import android.os.ParcelableException;
69 import android.os.PersistableBundle;
70 import android.os.ProxyFileDescriptorCallback;
71 import android.os.RemoteException;
72 import android.os.ServiceManager;
73 import android.os.ServiceManager.ServiceNotFoundException;
74 import android.os.SystemProperties;
75 import android.provider.MediaStore;
76 import android.provider.Settings;
77 import android.sysprop.VoldProperties;
78 import android.system.ErrnoException;
79 import android.system.Os;
80 import android.system.OsConstants;
81 import android.text.TextUtils;
82 import android.util.DataUnit;
83 import android.util.Log;
84 import android.util.Pair;
85 import android.util.Slog;
86 import android.util.SparseArray;
87 
88 import com.android.internal.annotations.GuardedBy;
89 import com.android.internal.annotations.VisibleForTesting;
90 import com.android.internal.logging.MetricsLogger;
91 import com.android.internal.os.AppFuseMount;
92 import com.android.internal.os.FuseAppLoop;
93 import com.android.internal.os.FuseUnavailableMountException;
94 import com.android.internal.os.RoSystemProperties;
95 import com.android.internal.os.SomeArgs;
96 import com.android.internal.util.Preconditions;
97 
98 import dalvik.system.BlockGuard;
99 
100 import java.io.File;
101 import java.io.FileDescriptor;
102 import java.io.FileNotFoundException;
103 import java.io.IOException;
104 import java.lang.annotation.Retention;
105 import java.lang.annotation.RetentionPolicy;
106 import java.lang.ref.WeakReference;
107 import java.nio.charset.StandardCharsets;
108 import java.util.ArrayList;
109 import java.util.Arrays;
110 import java.util.Collections;
111 import java.util.Iterator;
112 import java.util.List;
113 import java.util.Objects;
114 import java.util.UUID;
115 import java.util.concurrent.CompletableFuture;
116 import java.util.concurrent.ThreadFactory;
117 import java.util.concurrent.TimeUnit;
118 import java.util.concurrent.atomic.AtomicInteger;
119 
120 /**
121  * StorageManager is the interface to the systems storage service. The storage
122  * manager handles storage-related items such as Opaque Binary Blobs (OBBs).
123  * <p>
124  * OBBs contain a filesystem that maybe be encrypted on disk and mounted
125  * on-demand from an application. OBBs are a good way of providing large amounts
126  * of binary assets without packaging them into APKs as they may be multiple
127  * gigabytes in size. However, due to their size, they're most likely stored in
128  * a shared storage pool accessible from all programs. The system does not
129  * guarantee the security of the OBB file itself: if any program modifies the
130  * OBB, there is no guarantee that a read from that OBB will produce the
131  * expected output.
132  */
133 @SystemService(Context.STORAGE_SERVICE)
134 public class StorageManager {
135     private static final String TAG = "StorageManager";
136     private static final boolean LOCAL_LOGV = Log.isLoggable(TAG, Log.VERBOSE);
137 
138     /** {@hide} */
139     public static final String PROP_PRIMARY_PHYSICAL = "ro.vold.primary_physical";
140     /** {@hide} */
141     public static final String PROP_HAS_ADOPTABLE = "vold.has_adoptable";
142     /** {@hide} */
143     public static final String PROP_HAS_RESERVED = "vold.has_reserved";
144     /** {@hide} */
145     public static final String PROP_ADOPTABLE = "persist.sys.adoptable";
146     /** {@hide} */
147     public static final String PROP_EMULATE_FBE = "persist.sys.emulate_fbe";
148     /** {@hide} */
149     public static final String PROP_SDCARDFS = "persist.sys.sdcardfs";
150     /** {@hide} */
151     public static final String PROP_VIRTUAL_DISK = "persist.sys.virtual_disk";
152     /** {@hide} */
153     public static final String PROP_ISOLATED_STORAGE = "persist.sys.isolated_storage";
154     /** {@hide} */
155     public static final String PROP_ISOLATED_STORAGE_SNAPSHOT = "sys.isolated_storage_snapshot";
156 
157     /** {@hide} */
158     public static final String UUID_PRIVATE_INTERNAL = null;
159     /** {@hide} */
160     public static final String UUID_PRIMARY_PHYSICAL = "primary_physical";
161     /** {@hide} */
162     public static final String UUID_SYSTEM = "system";
163 
164     // NOTE: UUID constants below are namespaced
165     // uuid -v5 ad99aa3d-308e-4191-a200-ebcab371c0ad default
166     // uuid -v5 ad99aa3d-308e-4191-a200-ebcab371c0ad primary_physical
167     // uuid -v5 ad99aa3d-308e-4191-a200-ebcab371c0ad system
168 
169     /**
170      * UUID representing the default internal storage of this device which
171      * provides {@link Environment#getDataDirectory()}.
172      * <p>
173      * This value is constant across all devices and it will never change, and
174      * thus it cannot be used to uniquely identify a particular physical device.
175      *
176      * @see #getUuidForPath(File)
177      * @see ApplicationInfo#storageUuid
178      */
179     public static final UUID UUID_DEFAULT = UUID
180             .fromString("41217664-9172-527a-b3d5-edabb50a7d69");
181 
182     /** {@hide} */
183     public static final UUID UUID_PRIMARY_PHYSICAL_ = UUID
184             .fromString("0f95a519-dae7-5abf-9519-fbd6209e05fd");
185 
186     /** {@hide} */
187     public static final UUID UUID_SYSTEM_ = UUID
188             .fromString("5d258386-e60d-59e3-826d-0089cdd42cc0");
189 
190     /**
191      * Activity Action: Allows the user to manage their storage. This activity
192      * provides the ability to free up space on the device by deleting data such
193      * as apps.
194      * <p>
195      * If the sending application has a specific storage device or allocation
196      * size in mind, they can optionally define {@link #EXTRA_UUID} or
197      * {@link #EXTRA_REQUESTED_BYTES}, respectively.
198      * <p>
199      * This intent should be launched using
200      * {@link Activity#startActivityForResult(Intent, int)} so that the user
201      * knows which app is requesting the storage space. The returned result will
202      * be {@link Activity#RESULT_OK} if the requested space was made available,
203      * or {@link Activity#RESULT_CANCELED} otherwise.
204      */
205     @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
206     public static final String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
207 
208     /**
209      * Extra {@link UUID} used to indicate the storage volume where an
210      * application is interested in allocating or managing disk space.
211      *
212      * @see #ACTION_MANAGE_STORAGE
213      * @see #UUID_DEFAULT
214      * @see #getUuidForPath(File)
215      * @see Intent#putExtra(String, java.io.Serializable)
216      */
217     public static final String EXTRA_UUID = "android.os.storage.extra.UUID";
218 
219     /**
220      * Extra used to indicate the total size (in bytes) that an application is
221      * interested in allocating.
222      * <p>
223      * When defined, the management UI will help guide the user to free up
224      * enough disk space to reach this requested value.
225      *
226      * @see #ACTION_MANAGE_STORAGE
227      */
228     public static final String EXTRA_REQUESTED_BYTES = "android.os.storage.extra.REQUESTED_BYTES";
229 
230     /** {@hide} */
231     public static final int DEBUG_ADOPTABLE_FORCE_ON = 1 << 0;
232     /** {@hide} */
233     public static final int DEBUG_ADOPTABLE_FORCE_OFF = 1 << 1;
234     /** {@hide} */
235     public static final int DEBUG_EMULATE_FBE = 1 << 2;
236     /** {@hide} */
237     public static final int DEBUG_SDCARDFS_FORCE_ON = 1 << 3;
238     /** {@hide} */
239     public static final int DEBUG_SDCARDFS_FORCE_OFF = 1 << 4;
240     /** {@hide} */
241     public static final int DEBUG_VIRTUAL_DISK = 1 << 5;
242     /** {@hide} */
243     public static final int DEBUG_ISOLATED_STORAGE_FORCE_ON = 1 << 6;
244     /** {@hide} */
245     public static final int DEBUG_ISOLATED_STORAGE_FORCE_OFF = 1 << 7;
246 
247     /** {@hide} */
248     public static final int FLAG_STORAGE_DE = IInstalld.FLAG_STORAGE_DE;
249     /** {@hide} */
250     public static final int FLAG_STORAGE_CE = IInstalld.FLAG_STORAGE_CE;
251     /** {@hide} */
252     public static final int FLAG_STORAGE_EXTERNAL = IInstalld.FLAG_STORAGE_EXTERNAL;
253 
254     /** {@hide} */
255     public static final int FLAG_FOR_WRITE = 1 << 8;
256     /** {@hide} */
257     public static final int FLAG_REAL_STATE = 1 << 9;
258     /** {@hide} */
259     public static final int FLAG_INCLUDE_INVISIBLE = 1 << 10;
260 
261     /** {@hide} */
262     public static final int FSTRIM_FLAG_DEEP = IVold.FSTRIM_FLAG_DEEP_TRIM;
263 
264     /** @hide The volume is not encrypted. */
265     @UnsupportedAppUsage
266     public static final int ENCRYPTION_STATE_NONE =
267             IVold.ENCRYPTION_STATE_NONE;
268 
269     /** @hide The volume has been encrypted succesfully. */
270     public static final int ENCRYPTION_STATE_OK =
271             IVold.ENCRYPTION_STATE_OK;
272 
273     /** @hide The volume is in a bad state. */
274     public static final int ENCRYPTION_STATE_ERROR_UNKNOWN =
275             IVold.ENCRYPTION_STATE_ERROR_UNKNOWN;
276 
277     /** @hide Encryption is incomplete */
278     public static final int ENCRYPTION_STATE_ERROR_INCOMPLETE =
279             IVold.ENCRYPTION_STATE_ERROR_INCOMPLETE;
280 
281     /** @hide Encryption is incomplete and irrecoverable */
282     public static final int ENCRYPTION_STATE_ERROR_INCONSISTENT =
283             IVold.ENCRYPTION_STATE_ERROR_INCONSISTENT;
284 
285     /** @hide Underlying data is corrupt */
286     public static final int ENCRYPTION_STATE_ERROR_CORRUPT =
287             IVold.ENCRYPTION_STATE_ERROR_CORRUPT;
288 
289     private static volatile IStorageManager sStorageManager = null;
290 
291     private final Context mContext;
292     private final ContentResolver mResolver;
293 
294     private final IStorageManager mStorageManager;
295     private final AppOpsManager mAppOps;
296     private final Looper mLooper;
297     private final AtomicInteger mNextNonce = new AtomicInteger(0);
298 
299     private final ArrayList<StorageEventListenerDelegate> mDelegates = new ArrayList<>();
300 
301     private static class StorageEventListenerDelegate extends IStorageEventListener.Stub implements
302             Handler.Callback {
303         private static final int MSG_STORAGE_STATE_CHANGED = 1;
304         private static final int MSG_VOLUME_STATE_CHANGED = 2;
305         private static final int MSG_VOLUME_RECORD_CHANGED = 3;
306         private static final int MSG_VOLUME_FORGOTTEN = 4;
307         private static final int MSG_DISK_SCANNED = 5;
308         private static final int MSG_DISK_DESTROYED = 6;
309 
310         final StorageEventListener mCallback;
311         final Handler mHandler;
312 
StorageEventListenerDelegate(StorageEventListener callback, Looper looper)313         public StorageEventListenerDelegate(StorageEventListener callback, Looper looper) {
314             mCallback = callback;
315             mHandler = new Handler(looper, this);
316         }
317 
318         @Override
handleMessage(Message msg)319         public boolean handleMessage(Message msg) {
320             final SomeArgs args = (SomeArgs) msg.obj;
321             switch (msg.what) {
322                 case MSG_STORAGE_STATE_CHANGED:
323                     mCallback.onStorageStateChanged((String) args.arg1, (String) args.arg2,
324                             (String) args.arg3);
325                     args.recycle();
326                     return true;
327                 case MSG_VOLUME_STATE_CHANGED:
328                     mCallback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3);
329                     args.recycle();
330                     return true;
331                 case MSG_VOLUME_RECORD_CHANGED:
332                     mCallback.onVolumeRecordChanged((VolumeRecord) args.arg1);
333                     args.recycle();
334                     return true;
335                 case MSG_VOLUME_FORGOTTEN:
336                     mCallback.onVolumeForgotten((String) args.arg1);
337                     args.recycle();
338                     return true;
339                 case MSG_DISK_SCANNED:
340                     mCallback.onDiskScanned((DiskInfo) args.arg1, args.argi2);
341                     args.recycle();
342                     return true;
343                 case MSG_DISK_DESTROYED:
344                     mCallback.onDiskDestroyed((DiskInfo) args.arg1);
345                     args.recycle();
346                     return true;
347             }
348             args.recycle();
349             return false;
350         }
351 
352         @Override
onUsbMassStorageConnectionChanged(boolean connected)353         public void onUsbMassStorageConnectionChanged(boolean connected) throws RemoteException {
354             // Ignored
355         }
356 
357         @Override
onStorageStateChanged(String path, String oldState, String newState)358         public void onStorageStateChanged(String path, String oldState, String newState) {
359             final SomeArgs args = SomeArgs.obtain();
360             args.arg1 = path;
361             args.arg2 = oldState;
362             args.arg3 = newState;
363             mHandler.obtainMessage(MSG_STORAGE_STATE_CHANGED, args).sendToTarget();
364         }
365 
366         @Override
onVolumeStateChanged(VolumeInfo vol, int oldState, int newState)367         public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
368             final SomeArgs args = SomeArgs.obtain();
369             args.arg1 = vol;
370             args.argi2 = oldState;
371             args.argi3 = newState;
372             mHandler.obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget();
373         }
374 
375         @Override
onVolumeRecordChanged(VolumeRecord rec)376         public void onVolumeRecordChanged(VolumeRecord rec) {
377             final SomeArgs args = SomeArgs.obtain();
378             args.arg1 = rec;
379             mHandler.obtainMessage(MSG_VOLUME_RECORD_CHANGED, args).sendToTarget();
380         }
381 
382         @Override
onVolumeForgotten(String fsUuid)383         public void onVolumeForgotten(String fsUuid) {
384             final SomeArgs args = SomeArgs.obtain();
385             args.arg1 = fsUuid;
386             mHandler.obtainMessage(MSG_VOLUME_FORGOTTEN, args).sendToTarget();
387         }
388 
389         @Override
onDiskScanned(DiskInfo disk, int volumeCount)390         public void onDiskScanned(DiskInfo disk, int volumeCount) {
391             final SomeArgs args = SomeArgs.obtain();
392             args.arg1 = disk;
393             args.argi2 = volumeCount;
394             mHandler.obtainMessage(MSG_DISK_SCANNED, args).sendToTarget();
395         }
396 
397         @Override
onDiskDestroyed(DiskInfo disk)398         public void onDiskDestroyed(DiskInfo disk) throws RemoteException {
399             final SomeArgs args = SomeArgs.obtain();
400             args.arg1 = disk;
401             mHandler.obtainMessage(MSG_DISK_DESTROYED, args).sendToTarget();
402         }
403     }
404 
405     /**
406      * Binder listener for OBB action results.
407      */
408     private final ObbActionListener mObbActionListener = new ObbActionListener();
409 
410     private class ObbActionListener extends IObbActionListener.Stub {
411         @SuppressWarnings("hiding")
412         private SparseArray<ObbListenerDelegate> mListeners = new SparseArray<ObbListenerDelegate>();
413 
414         @Override
onObbResult(String filename, int nonce, int status)415         public void onObbResult(String filename, int nonce, int status) {
416             final ObbListenerDelegate delegate;
417             synchronized (mListeners) {
418                 delegate = mListeners.get(nonce);
419                 if (delegate != null) {
420                     mListeners.remove(nonce);
421                 }
422             }
423 
424             if (delegate != null) {
425                 delegate.sendObbStateChanged(filename, status);
426             }
427         }
428 
addListener(OnObbStateChangeListener listener)429         public int addListener(OnObbStateChangeListener listener) {
430             final ObbListenerDelegate delegate = new ObbListenerDelegate(listener);
431 
432             synchronized (mListeners) {
433                 mListeners.put(delegate.nonce, delegate);
434             }
435 
436             return delegate.nonce;
437         }
438     }
439 
getNextNonce()440     private int getNextNonce() {
441         return mNextNonce.getAndIncrement();
442     }
443 
444     /**
445      * Private class containing sender and receiver code for StorageEvents.
446      */
447     private class ObbListenerDelegate {
448         private final WeakReference<OnObbStateChangeListener> mObbEventListenerRef;
449         private final Handler mHandler;
450 
451         private final int nonce;
452 
ObbListenerDelegate(OnObbStateChangeListener listener)453         ObbListenerDelegate(OnObbStateChangeListener listener) {
454             nonce = getNextNonce();
455             mObbEventListenerRef = new WeakReference<OnObbStateChangeListener>(listener);
456             mHandler = new Handler(mLooper) {
457                 @Override
458                 public void handleMessage(Message msg) {
459                     final OnObbStateChangeListener changeListener = getListener();
460                     if (changeListener == null) {
461                         return;
462                     }
463 
464                     changeListener.onObbStateChange((String) msg.obj, msg.arg1);
465                 }
466             };
467         }
468 
getListener()469         OnObbStateChangeListener getListener() {
470             if (mObbEventListenerRef == null) {
471                 return null;
472             }
473             return mObbEventListenerRef.get();
474         }
475 
sendObbStateChanged(String path, int state)476         void sendObbStateChanged(String path, int state) {
477             mHandler.obtainMessage(0, state, 0, path).sendToTarget();
478         }
479     }
480 
481     /** {@hide} */
482     @Deprecated
483     @UnsupportedAppUsage
from(Context context)484     public static StorageManager from(Context context) {
485         return context.getSystemService(StorageManager.class);
486     }
487 
488     /**
489      * Constructs a StorageManager object through which an application can
490      * can communicate with the systems mount service.
491      *
492      * @param looper The {@link android.os.Looper} which events will be received on.
493      *
494      * <p>Applications can get instance of this class by calling
495      * {@link android.content.Context#getSystemService(java.lang.String)} with an argument
496      * of {@link android.content.Context#STORAGE_SERVICE}.
497      *
498      * @hide
499      */
500     @UnsupportedAppUsage
StorageManager(Context context, Looper looper)501     public StorageManager(Context context, Looper looper) throws ServiceNotFoundException {
502         mContext = context;
503         mResolver = context.getContentResolver();
504         mLooper = looper;
505         mStorageManager = IStorageManager.Stub.asInterface(ServiceManager.getServiceOrThrow("mount"));
506         mAppOps = mContext.getSystemService(AppOpsManager.class);
507     }
508 
509     /**
510      * Registers a {@link android.os.storage.StorageEventListener StorageEventListener}.
511      *
512      * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
513      *
514      * @hide
515      */
516     @UnsupportedAppUsage
registerListener(StorageEventListener listener)517     public void registerListener(StorageEventListener listener) {
518         synchronized (mDelegates) {
519             final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate(listener,
520                     mLooper);
521             try {
522                 mStorageManager.registerListener(delegate);
523             } catch (RemoteException e) {
524                 throw e.rethrowFromSystemServer();
525             }
526             mDelegates.add(delegate);
527         }
528     }
529 
530     /**
531      * Unregisters a {@link android.os.storage.StorageEventListener StorageEventListener}.
532      *
533      * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
534      *
535      * @hide
536      */
537     @UnsupportedAppUsage
unregisterListener(StorageEventListener listener)538     public void unregisterListener(StorageEventListener listener) {
539         synchronized (mDelegates) {
540             for (Iterator<StorageEventListenerDelegate> i = mDelegates.iterator(); i.hasNext();) {
541                 final StorageEventListenerDelegate delegate = i.next();
542                 if (delegate.mCallback == listener) {
543                     try {
544                         mStorageManager.unregisterListener(delegate);
545                     } catch (RemoteException e) {
546                         throw e.rethrowFromSystemServer();
547                     }
548                     i.remove();
549                 }
550             }
551         }
552     }
553 
554     /**
555      * Enables USB Mass Storage (UMS) on the device.
556      *
557      * @hide
558      */
559     @Deprecated
560     @UnsupportedAppUsage
enableUsbMassStorage()561     public void enableUsbMassStorage() {
562     }
563 
564     /**
565      * Disables USB Mass Storage (UMS) on the device.
566      *
567      * @hide
568      */
569     @Deprecated
570     @UnsupportedAppUsage
disableUsbMassStorage()571     public void disableUsbMassStorage() {
572     }
573 
574     /**
575      * Query if a USB Mass Storage (UMS) host is connected.
576      * @return true if UMS host is connected.
577      *
578      * @hide
579      */
580     @Deprecated
581     @UnsupportedAppUsage
isUsbMassStorageConnected()582     public boolean isUsbMassStorageConnected() {
583         return false;
584     }
585 
586     /**
587      * Query if a USB Mass Storage (UMS) is enabled on the device.
588      * @return true if UMS host is enabled.
589      *
590      * @hide
591      */
592     @Deprecated
593     @UnsupportedAppUsage
isUsbMassStorageEnabled()594     public boolean isUsbMassStorageEnabled() {
595         return false;
596     }
597 
598     /**
599      * Mount an Opaque Binary Blob (OBB) file. If a <code>key</code> is
600      * specified, it is supplied to the mounting process to be used in any
601      * encryption used in the OBB.
602      * <p>
603      * The OBB will remain mounted for as long as the StorageManager reference
604      * is held by the application. As soon as this reference is lost, the OBBs
605      * in use will be unmounted. The {@link OnObbStateChangeListener} registered
606      * with this call will receive the success or failure of this operation.
607      * <p>
608      * <em>Note:</em> you can only mount OBB files for which the OBB tag on the
609      * file matches a package ID that is owned by the calling program's UID.
610      * That is, shared UID applications can attempt to mount any other
611      * application's OBB that shares its UID.
612      *
613      * @param rawPath the path to the OBB file
614      * @param key secret used to encrypt the OBB; may be <code>null</code> if no
615      *            encryption was used on the OBB.
616      * @param listener will receive the success or failure of the operation
617      * @return whether the mount call was successfully queued or not
618      */
mountObb(String rawPath, String key, OnObbStateChangeListener listener)619     public boolean mountObb(String rawPath, String key, OnObbStateChangeListener listener) {
620         Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
621         Preconditions.checkNotNull(listener, "listener cannot be null");
622 
623         try {
624             final String canonicalPath = new File(rawPath).getCanonicalPath();
625             final int nonce = mObbActionListener.addListener(listener);
626             mStorageManager.mountObb(rawPath, canonicalPath, key, mObbActionListener, nonce,
627                     getObbInfo(canonicalPath));
628             return true;
629         } catch (IOException e) {
630             throw new IllegalArgumentException("Failed to resolve path: " + rawPath, e);
631         } catch (RemoteException e) {
632             throw e.rethrowFromSystemServer();
633         }
634     }
635 
getObbInfo(String canonicalPath)636     private ObbInfo getObbInfo(String canonicalPath) {
637         try {
638             final ObbInfo obbInfo = ObbScanner.getObbInfo(canonicalPath);
639             return obbInfo;
640         } catch (IOException e) {
641             throw new IllegalArgumentException("Couldn't get OBB info for " + canonicalPath, e);
642         }
643     }
644 
645     /**
646      * Unmount an Opaque Binary Blob (OBB) file asynchronously. If the
647      * <code>force</code> flag is true, it will kill any application needed to
648      * unmount the given OBB (even the calling application).
649      * <p>
650      * The {@link OnObbStateChangeListener} registered with this call will
651      * receive the success or failure of this operation.
652      * <p>
653      * <em>Note:</em> you can only mount OBB files for which the OBB tag on the
654      * file matches a package ID that is owned by the calling program's UID.
655      * That is, shared UID applications can obtain access to any other
656      * application's OBB that shares its UID.
657      * <p>
658      *
659      * @param rawPath path to the OBB file
660      * @param force whether to kill any programs using this in order to unmount
661      *            it
662      * @param listener will receive the success or failure of the operation
663      * @return whether the unmount call was successfully queued or not
664      */
unmountObb(String rawPath, boolean force, OnObbStateChangeListener listener)665     public boolean unmountObb(String rawPath, boolean force, OnObbStateChangeListener listener) {
666         Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
667         Preconditions.checkNotNull(listener, "listener cannot be null");
668 
669         try {
670             final int nonce = mObbActionListener.addListener(listener);
671             mStorageManager.unmountObb(rawPath, force, mObbActionListener, nonce);
672             return true;
673         } catch (RemoteException e) {
674             throw e.rethrowFromSystemServer();
675         }
676     }
677 
678     /**
679      * Check whether an Opaque Binary Blob (OBB) is mounted or not.
680      *
681      * @param rawPath path to OBB image
682      * @return true if OBB is mounted; false if not mounted or on error
683      */
isObbMounted(String rawPath)684     public boolean isObbMounted(String rawPath) {
685         Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
686 
687         try {
688             return mStorageManager.isObbMounted(rawPath);
689         } catch (RemoteException e) {
690             throw e.rethrowFromSystemServer();
691         }
692     }
693 
694     /**
695      * Check the mounted path of an Opaque Binary Blob (OBB) file. This will
696      * give you the path to where you can obtain access to the internals of the
697      * OBB.
698      *
699      * @param rawPath path to OBB image
700      * @return absolute path to mounted OBB image data or <code>null</code> if
701      *         not mounted or exception encountered trying to read status
702      */
getMountedObbPath(String rawPath)703     public String getMountedObbPath(String rawPath) {
704         Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
705 
706         try {
707             return mStorageManager.getMountedObbPath(rawPath);
708         } catch (RemoteException e) {
709             throw e.rethrowFromSystemServer();
710         }
711     }
712 
713     /** {@hide} */
714     @UnsupportedAppUsage
getDisks()715     public @NonNull List<DiskInfo> getDisks() {
716         try {
717             return Arrays.asList(mStorageManager.getDisks());
718         } catch (RemoteException e) {
719             throw e.rethrowFromSystemServer();
720         }
721     }
722 
723     /** {@hide} */
724     @UnsupportedAppUsage
findDiskById(String id)725     public @Nullable DiskInfo findDiskById(String id) {
726         Preconditions.checkNotNull(id);
727         // TODO; go directly to service to make this faster
728         for (DiskInfo disk : getDisks()) {
729             if (Objects.equals(disk.id, id)) {
730                 return disk;
731             }
732         }
733         return null;
734     }
735 
736     /** {@hide} */
737     @UnsupportedAppUsage
findVolumeById(String id)738     public @Nullable VolumeInfo findVolumeById(String id) {
739         Preconditions.checkNotNull(id);
740         // TODO; go directly to service to make this faster
741         for (VolumeInfo vol : getVolumes()) {
742             if (Objects.equals(vol.id, id)) {
743                 return vol;
744             }
745         }
746         return null;
747     }
748 
749     /** {@hide} */
750     @UnsupportedAppUsage
findVolumeByUuid(String fsUuid)751     public @Nullable VolumeInfo findVolumeByUuid(String fsUuid) {
752         Preconditions.checkNotNull(fsUuid);
753         // TODO; go directly to service to make this faster
754         for (VolumeInfo vol : getVolumes()) {
755             if (Objects.equals(vol.fsUuid, fsUuid)) {
756                 return vol;
757             }
758         }
759         return null;
760     }
761 
762     /** {@hide} */
findRecordByUuid(String fsUuid)763     public @Nullable VolumeRecord findRecordByUuid(String fsUuid) {
764         Preconditions.checkNotNull(fsUuid);
765         // TODO; go directly to service to make this faster
766         for (VolumeRecord rec : getVolumeRecords()) {
767             if (Objects.equals(rec.fsUuid, fsUuid)) {
768                 return rec;
769             }
770         }
771         return null;
772     }
773 
774     /** {@hide} */
findPrivateForEmulated(VolumeInfo emulatedVol)775     public @Nullable VolumeInfo findPrivateForEmulated(VolumeInfo emulatedVol) {
776         if (emulatedVol != null) {
777             return findVolumeById(emulatedVol.getId().replace("emulated", "private"));
778         } else {
779             return null;
780         }
781     }
782 
783     /** {@hide} */
784     @UnsupportedAppUsage
findEmulatedForPrivate(VolumeInfo privateVol)785     public @Nullable VolumeInfo findEmulatedForPrivate(VolumeInfo privateVol) {
786         if (privateVol != null) {
787             return findVolumeById(privateVol.getId().replace("private", "emulated"));
788         } else {
789             return null;
790         }
791     }
792 
793     /** {@hide} */
findVolumeByQualifiedUuid(String volumeUuid)794     public @Nullable VolumeInfo findVolumeByQualifiedUuid(String volumeUuid) {
795         if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, volumeUuid)) {
796             return findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL);
797         } else if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, volumeUuid)) {
798             return getPrimaryPhysicalVolume();
799         } else {
800             return findVolumeByUuid(volumeUuid);
801         }
802     }
803 
804     /**
805      * Return a UUID identifying the storage volume that hosts the given
806      * filesystem path.
807      * <p>
808      * If this path is hosted by the default internal storage of the device at
809      * {@link Environment#getDataDirectory()}, the returned value will be
810      * {@link #UUID_DEFAULT}.
811      *
812      * @throws IOException when the storage device hosting the given path isn't
813      *             present, or when it doesn't have a valid UUID.
814      */
getUuidForPath(@onNull File path)815     public @NonNull UUID getUuidForPath(@NonNull File path) throws IOException {
816         Preconditions.checkNotNull(path);
817         final String pathString = path.getCanonicalPath();
818         if (FileUtils.contains(Environment.getDataDirectory().getAbsolutePath(), pathString)) {
819             return UUID_DEFAULT;
820         }
821         try {
822             for (VolumeInfo vol : mStorageManager.getVolumes(0)) {
823                 if (vol.path != null && FileUtils.contains(vol.path, pathString)
824                         && vol.type != VolumeInfo.TYPE_PUBLIC && vol.type != VolumeInfo.TYPE_STUB) {
825                     // TODO: verify that emulated adopted devices have UUID of
826                     // underlying volume
827                     try {
828                         return convert(vol.fsUuid);
829                     } catch (IllegalArgumentException e) {
830                         continue;
831                     }
832                 }
833             }
834         } catch (RemoteException e) {
835             throw e.rethrowFromSystemServer();
836         }
837         throw new FileNotFoundException("Failed to find a storage device for " + path);
838     }
839 
840     /** {@hide} */
findPathForUuid(String volumeUuid)841     public @NonNull File findPathForUuid(String volumeUuid) throws FileNotFoundException {
842         final VolumeInfo vol = findVolumeByQualifiedUuid(volumeUuid);
843         if (vol != null) {
844             return vol.getPath();
845         }
846         throw new FileNotFoundException("Failed to find a storage device for " + volumeUuid);
847     }
848 
849     /**
850      * Test if the given file descriptor supports allocation of disk space using
851      * {@link #allocateBytes(FileDescriptor, long)}.
852      */
isAllocationSupported(@onNull FileDescriptor fd)853     public boolean isAllocationSupported(@NonNull FileDescriptor fd) {
854         try {
855             getUuidForPath(ParcelFileDescriptor.getFile(fd));
856             return true;
857         } catch (IOException e) {
858             return false;
859         }
860     }
861 
862     /** {@hide} */
863     @UnsupportedAppUsage
getVolumes()864     public @NonNull List<VolumeInfo> getVolumes() {
865         try {
866             return Arrays.asList(mStorageManager.getVolumes(0));
867         } catch (RemoteException e) {
868             throw e.rethrowFromSystemServer();
869         }
870     }
871 
872     /** {@hide} */
getWritablePrivateVolumes()873     public @NonNull List<VolumeInfo> getWritablePrivateVolumes() {
874         try {
875             final ArrayList<VolumeInfo> res = new ArrayList<>();
876             for (VolumeInfo vol : mStorageManager.getVolumes(0)) {
877                 if (vol.getType() == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) {
878                     res.add(vol);
879                 }
880             }
881             return res;
882         } catch (RemoteException e) {
883             throw e.rethrowFromSystemServer();
884         }
885     }
886 
887     /** {@hide} */
getVolumeRecords()888     public @NonNull List<VolumeRecord> getVolumeRecords() {
889         try {
890             return Arrays.asList(mStorageManager.getVolumeRecords(0));
891         } catch (RemoteException e) {
892             throw e.rethrowFromSystemServer();
893         }
894     }
895 
896     /** {@hide} */
897     @UnsupportedAppUsage
getBestVolumeDescription(VolumeInfo vol)898     public @Nullable String getBestVolumeDescription(VolumeInfo vol) {
899         if (vol == null) return null;
900 
901         // Nickname always takes precedence when defined
902         if (!TextUtils.isEmpty(vol.fsUuid)) {
903             final VolumeRecord rec = findRecordByUuid(vol.fsUuid);
904             if (rec != null && !TextUtils.isEmpty(rec.nickname)) {
905                 return rec.nickname;
906             }
907         }
908 
909         if (!TextUtils.isEmpty(vol.getDescription())) {
910             return vol.getDescription();
911         }
912 
913         if (vol.disk != null) {
914             return vol.disk.getDescription();
915         }
916 
917         return null;
918     }
919 
920     /** {@hide} */
921     @UnsupportedAppUsage
getPrimaryPhysicalVolume()922     public @Nullable VolumeInfo getPrimaryPhysicalVolume() {
923         final List<VolumeInfo> vols = getVolumes();
924         for (VolumeInfo vol : vols) {
925             if (vol.isPrimaryPhysical()) {
926                 return vol;
927             }
928         }
929         return null;
930     }
931 
932     /** {@hide} */
mount(String volId)933     public void mount(String volId) {
934         try {
935             mStorageManager.mount(volId);
936         } catch (RemoteException e) {
937             throw e.rethrowFromSystemServer();
938         }
939     }
940 
941     /** {@hide} */
942     @UnsupportedAppUsage
unmount(String volId)943     public void unmount(String volId) {
944         try {
945             mStorageManager.unmount(volId);
946         } catch (RemoteException e) {
947             throw e.rethrowFromSystemServer();
948         }
949     }
950 
951     /** {@hide} */
952     @UnsupportedAppUsage
format(String volId)953     public void format(String volId) {
954         try {
955             mStorageManager.format(volId);
956         } catch (RemoteException e) {
957             throw e.rethrowFromSystemServer();
958         }
959     }
960 
961     /** {@hide} */
962     @Deprecated
benchmark(String volId)963     public long benchmark(String volId) {
964         final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
965         benchmark(volId, new IVoldTaskListener.Stub() {
966             @Override
967             public void onStatus(int status, PersistableBundle extras) {
968                 // Ignored
969             }
970 
971             @Override
972             public void onFinished(int status, PersistableBundle extras) {
973                 result.complete(extras);
974             }
975         });
976         try {
977             // Convert ms to ns
978             return result.get(3, TimeUnit.MINUTES).getLong("run", Long.MAX_VALUE) * 1000000;
979         } catch (Exception e) {
980             return Long.MAX_VALUE;
981         }
982     }
983 
984     /** {@hide} */
benchmark(String volId, IVoldTaskListener listener)985     public void benchmark(String volId, IVoldTaskListener listener) {
986         try {
987             mStorageManager.benchmark(volId, listener);
988         } catch (RemoteException e) {
989             throw e.rethrowFromSystemServer();
990         }
991     }
992 
993     /** {@hide} */
994     @UnsupportedAppUsage
partitionPublic(String diskId)995     public void partitionPublic(String diskId) {
996         try {
997             mStorageManager.partitionPublic(diskId);
998         } catch (RemoteException e) {
999             throw e.rethrowFromSystemServer();
1000         }
1001     }
1002 
1003     /** {@hide} */
partitionPrivate(String diskId)1004     public void partitionPrivate(String diskId) {
1005         try {
1006             mStorageManager.partitionPrivate(diskId);
1007         } catch (RemoteException e) {
1008             throw e.rethrowFromSystemServer();
1009         }
1010     }
1011 
1012     /** {@hide} */
partitionMixed(String diskId, int ratio)1013     public void partitionMixed(String diskId, int ratio) {
1014         try {
1015             mStorageManager.partitionMixed(diskId, ratio);
1016         } catch (RemoteException e) {
1017             throw e.rethrowFromSystemServer();
1018         }
1019     }
1020 
1021     /** {@hide} */
wipeAdoptableDisks()1022     public void wipeAdoptableDisks() {
1023         // We only wipe devices in "adoptable" locations, which are in a
1024         // long-term stable slot/location on the device, where apps have a
1025         // reasonable chance of storing sensitive data. (Apps need to go through
1026         // SAF to write to transient volumes.)
1027         final List<DiskInfo> disks = getDisks();
1028         for (DiskInfo disk : disks) {
1029             final String diskId = disk.getId();
1030             if (disk.isAdoptable()) {
1031                 Slog.d(TAG, "Found adoptable " + diskId + "; wiping");
1032                 try {
1033                     // TODO: switch to explicit wipe command when we have it,
1034                     // for now rely on the fact that vfat format does a wipe
1035                     mStorageManager.partitionPublic(diskId);
1036                 } catch (Exception e) {
1037                     Slog.w(TAG, "Failed to wipe " + diskId + ", but soldiering onward", e);
1038                 }
1039             } else {
1040                 Slog.d(TAG, "Ignorning non-adoptable disk " + disk.getId());
1041             }
1042         }
1043     }
1044 
1045     /** {@hide} */
setVolumeNickname(String fsUuid, String nickname)1046     public void setVolumeNickname(String fsUuid, String nickname) {
1047         try {
1048             mStorageManager.setVolumeNickname(fsUuid, nickname);
1049         } catch (RemoteException e) {
1050             throw e.rethrowFromSystemServer();
1051         }
1052     }
1053 
1054     /** {@hide} */
setVolumeInited(String fsUuid, boolean inited)1055     public void setVolumeInited(String fsUuid, boolean inited) {
1056         try {
1057             mStorageManager.setVolumeUserFlags(fsUuid, inited ? VolumeRecord.USER_FLAG_INITED : 0,
1058                     VolumeRecord.USER_FLAG_INITED);
1059         } catch (RemoteException e) {
1060             throw e.rethrowFromSystemServer();
1061         }
1062     }
1063 
1064     /** {@hide} */
setVolumeSnoozed(String fsUuid, boolean snoozed)1065     public void setVolumeSnoozed(String fsUuid, boolean snoozed) {
1066         try {
1067             mStorageManager.setVolumeUserFlags(fsUuid, snoozed ? VolumeRecord.USER_FLAG_SNOOZED : 0,
1068                     VolumeRecord.USER_FLAG_SNOOZED);
1069         } catch (RemoteException e) {
1070             throw e.rethrowFromSystemServer();
1071         }
1072     }
1073 
1074     /** {@hide} */
forgetVolume(String fsUuid)1075     public void forgetVolume(String fsUuid) {
1076         try {
1077             mStorageManager.forgetVolume(fsUuid);
1078         } catch (RemoteException e) {
1079             throw e.rethrowFromSystemServer();
1080         }
1081     }
1082 
1083     /**
1084      * This is not the API you're looking for.
1085      *
1086      * @see PackageManager#getPrimaryStorageCurrentVolume()
1087      * @hide
1088      */
getPrimaryStorageUuid()1089     public String getPrimaryStorageUuid() {
1090         try {
1091             return mStorageManager.getPrimaryStorageUuid();
1092         } catch (RemoteException e) {
1093             throw e.rethrowFromSystemServer();
1094         }
1095     }
1096 
1097     /**
1098      * This is not the API you're looking for.
1099      *
1100      * @see PackageManager#movePrimaryStorage(VolumeInfo)
1101      * @hide
1102      */
setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback)1103     public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback) {
1104         try {
1105             mStorageManager.setPrimaryStorageUuid(volumeUuid, callback);
1106         } catch (RemoteException e) {
1107             throw e.rethrowFromSystemServer();
1108         }
1109     }
1110 
1111     /**
1112      * Return the {@link StorageVolume} that contains the given file, or
1113      * {@code null} if none.
1114      */
getStorageVolume(File file)1115     public @Nullable StorageVolume getStorageVolume(File file) {
1116         return getStorageVolume(getVolumeList(), file);
1117     }
1118 
1119     /**
1120      * Return the {@link StorageVolume} that contains the given
1121      * {@link MediaStore} item.
1122      */
getStorageVolume(@onNull Uri uri)1123     public @NonNull StorageVolume getStorageVolume(@NonNull Uri uri) {
1124         final String volumeName = MediaStore.getVolumeName(uri);
1125         switch (volumeName) {
1126             case MediaStore.VOLUME_EXTERNAL_PRIMARY:
1127                 return getPrimaryStorageVolume();
1128             default:
1129                 for (StorageVolume vol : getStorageVolumes()) {
1130                     if (Objects.equals(vol.getNormalizedUuid(), volumeName)) {
1131                         return vol;
1132                     }
1133                 }
1134         }
1135         throw new IllegalStateException("Unknown volume for " + uri);
1136     }
1137 
1138     /** {@hide} */
getStorageVolume(File file, int userId)1139     public static @Nullable StorageVolume getStorageVolume(File file, int userId) {
1140         return getStorageVolume(getVolumeList(userId, 0), file);
1141     }
1142 
1143     /** {@hide} */
1144     @UnsupportedAppUsage
getStorageVolume(StorageVolume[] volumes, File file)1145     private static @Nullable StorageVolume getStorageVolume(StorageVolume[] volumes, File file) {
1146         if (file == null) {
1147             return null;
1148         }
1149         final String path = file.getAbsolutePath();
1150         if (path.startsWith(DEPRECATE_DATA_PREFIX)) {
1151             final Uri uri = ContentResolver.translateDeprecatedDataPath(path);
1152             return AppGlobals.getInitialApplication().getSystemService(StorageManager.class)
1153                     .getStorageVolume(uri);
1154         }
1155         try {
1156             file = file.getCanonicalFile();
1157         } catch (IOException ignored) {
1158             Slog.d(TAG, "Could not get canonical path for " + file);
1159             return null;
1160         }
1161         for (StorageVolume volume : volumes) {
1162             File volumeFile = volume.getPathFile();
1163             try {
1164                 volumeFile = volumeFile.getCanonicalFile();
1165             } catch (IOException ignored) {
1166                 continue;
1167             }
1168             if (FileUtils.contains(volumeFile, file)) {
1169                 return volume;
1170             }
1171         }
1172         return null;
1173     }
1174 
1175     /**
1176      * Gets the state of a volume via its mountpoint.
1177      * @hide
1178      */
1179     @Deprecated
1180     @UnsupportedAppUsage
getVolumeState(String mountPoint)1181     public @NonNull String getVolumeState(String mountPoint) {
1182         final StorageVolume vol = getStorageVolume(new File(mountPoint));
1183         if (vol != null) {
1184             return vol.getState();
1185         } else {
1186             return Environment.MEDIA_UNKNOWN;
1187         }
1188     }
1189 
1190     /**
1191      * Return the list of shared/external storage volumes available to the
1192      * current user. This includes both the primary shared storage device and
1193      * any attached external volumes including SD cards and USB drives.
1194      *
1195      * @see Environment#getExternalStorageDirectory()
1196      * @see StorageVolume#createAccessIntent(String)
1197      */
getStorageVolumes()1198     public @NonNull List<StorageVolume> getStorageVolumes() {
1199         final ArrayList<StorageVolume> res = new ArrayList<>();
1200         Collections.addAll(res,
1201                 getVolumeList(mContext.getUserId(), FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE));
1202         return res;
1203     }
1204 
1205     /**
1206      * Return the primary shared/external storage volume available to the
1207      * current user. This volume is the same storage device returned by
1208      * {@link Environment#getExternalStorageDirectory()} and
1209      * {@link Context#getExternalFilesDir(String)}.
1210      */
getPrimaryStorageVolume()1211     public @NonNull StorageVolume getPrimaryStorageVolume() {
1212         return getVolumeList(mContext.getUserId(), FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE)[0];
1213     }
1214 
1215     /** {@hide} */
getPrimaryStoragePathAndSize()1216     public static Pair<String, Long> getPrimaryStoragePathAndSize() {
1217         return Pair.create(null,
1218                 FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()
1219                     + Environment.getRootDirectory().getTotalSpace()));
1220     }
1221 
1222     /** {@hide} */
getPrimaryStorageSize()1223     public long getPrimaryStorageSize() {
1224         return FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()
1225                 + Environment.getRootDirectory().getTotalSpace());
1226     }
1227 
1228     /** {@hide} */
mkdirs(File file)1229     public void mkdirs(File file) {
1230         BlockGuard.getVmPolicy().onPathAccess(file.getAbsolutePath());
1231         try {
1232             mStorageManager.mkdirs(mContext.getOpPackageName(), file.getAbsolutePath());
1233         } catch (RemoteException e) {
1234             throw e.rethrowFromSystemServer();
1235         }
1236     }
1237 
1238     /** @removed */
getVolumeList()1239     public @NonNull StorageVolume[] getVolumeList() {
1240         return getVolumeList(mContext.getUserId(), 0);
1241     }
1242 
1243     /** {@hide} */
1244     @UnsupportedAppUsage
getVolumeList(int userId, int flags)1245     public static @NonNull StorageVolume[] getVolumeList(int userId, int flags) {
1246         final IStorageManager storageManager = IStorageManager.Stub.asInterface(
1247                 ServiceManager.getService("mount"));
1248         try {
1249             String packageName = ActivityThread.currentOpPackageName();
1250             if (packageName == null) {
1251                 // Package name can be null if the activity thread is running but the app
1252                 // hasn't bound yet. In this case we fall back to the first package in the
1253                 // current UID. This works for runtime permissions as permission state is
1254                 // per UID and permission realted app ops are updated for all UID packages.
1255                 String[] packageNames = ActivityThread.getPackageManager().getPackagesForUid(
1256                         android.os.Process.myUid());
1257                 if (packageNames == null || packageNames.length <= 0) {
1258                     return new StorageVolume[0];
1259                 }
1260                 packageName = packageNames[0];
1261             }
1262             final int uid = ActivityThread.getPackageManager().getPackageUid(packageName,
1263                     PackageManager.MATCH_DEBUG_TRIAGED_MISSING, userId);
1264             if (uid <= 0) {
1265                 return new StorageVolume[0];
1266             }
1267             return storageManager.getVolumeList(uid, packageName, flags);
1268         } catch (RemoteException e) {
1269             throw e.rethrowFromSystemServer();
1270         }
1271     }
1272 
1273     /**
1274      * Returns list of paths for all mountable volumes.
1275      * @hide
1276      */
1277     @Deprecated
1278     @UnsupportedAppUsage
getVolumePaths()1279     public @NonNull String[] getVolumePaths() {
1280         StorageVolume[] volumes = getVolumeList();
1281         int count = volumes.length;
1282         String[] paths = new String[count];
1283         for (int i = 0; i < count; i++) {
1284             paths[i] = volumes[i].getPath();
1285         }
1286         return paths;
1287     }
1288 
1289     /** @removed */
getPrimaryVolume()1290     public @NonNull StorageVolume getPrimaryVolume() {
1291         return getPrimaryVolume(getVolumeList());
1292     }
1293 
1294     /** {@hide} */
getPrimaryVolume(StorageVolume[] volumes)1295     public static @NonNull StorageVolume getPrimaryVolume(StorageVolume[] volumes) {
1296         for (StorageVolume volume : volumes) {
1297             if (volume.isPrimary()) {
1298                 return volume;
1299             }
1300         }
1301         throw new IllegalStateException("Missing primary storage");
1302     }
1303 
1304     private static final int DEFAULT_THRESHOLD_PERCENTAGE = 5;
1305     private static final long DEFAULT_THRESHOLD_MAX_BYTES = DataUnit.MEBIBYTES.toBytes(500);
1306 
1307     private static final int DEFAULT_CACHE_PERCENTAGE = 10;
1308     private static final long DEFAULT_CACHE_MAX_BYTES = DataUnit.GIBIBYTES.toBytes(5);
1309 
1310     private static final long DEFAULT_FULL_THRESHOLD_BYTES = DataUnit.MEBIBYTES.toBytes(1);
1311 
1312     /**
1313      * Return the number of available bytes until the given path is considered
1314      * running low on storage.
1315      *
1316      * @hide
1317      */
1318     @UnsupportedAppUsage
getStorageBytesUntilLow(File path)1319     public long getStorageBytesUntilLow(File path) {
1320         return path.getUsableSpace() - getStorageFullBytes(path);
1321     }
1322 
1323     /**
1324      * Return the number of available bytes at which the given path is
1325      * considered running low on storage.
1326      *
1327      * @hide
1328      */
1329     @UnsupportedAppUsage
getStorageLowBytes(File path)1330     public long getStorageLowBytes(File path) {
1331         final long lowPercent = Settings.Global.getInt(mResolver,
1332                 Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE);
1333         final long lowBytes = (path.getTotalSpace() * lowPercent) / 100;
1334 
1335         final long maxLowBytes = Settings.Global.getLong(mResolver,
1336                 Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES, DEFAULT_THRESHOLD_MAX_BYTES);
1337 
1338         return Math.min(lowBytes, maxLowBytes);
1339     }
1340 
1341     /**
1342      * Return the minimum number of bytes of storage on the device that should
1343      * be reserved for cached data.
1344      *
1345      * @hide
1346      */
getStorageCacheBytes(File path, @AllocateFlags int flags)1347     public long getStorageCacheBytes(File path, @AllocateFlags int flags) {
1348         final long cachePercent = Settings.Global.getInt(mResolver,
1349                 Settings.Global.SYS_STORAGE_CACHE_PERCENTAGE, DEFAULT_CACHE_PERCENTAGE);
1350         final long cacheBytes = (path.getTotalSpace() * cachePercent) / 100;
1351 
1352         final long maxCacheBytes = Settings.Global.getLong(mResolver,
1353                 Settings.Global.SYS_STORAGE_CACHE_MAX_BYTES, DEFAULT_CACHE_MAX_BYTES);
1354 
1355         final long result = Math.min(cacheBytes, maxCacheBytes);
1356         if ((flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0) {
1357             return 0;
1358         } else if ((flags & StorageManager.FLAG_ALLOCATE_DEFY_ALL_RESERVED) != 0) {
1359             return 0;
1360         } else if ((flags & StorageManager.FLAG_ALLOCATE_DEFY_HALF_RESERVED) != 0) {
1361             return result / 2;
1362         } else {
1363             return result;
1364         }
1365     }
1366 
1367     /**
1368      * Return the number of available bytes at which the given path is
1369      * considered full.
1370      *
1371      * @hide
1372      */
1373     @UnsupportedAppUsage
getStorageFullBytes(File path)1374     public long getStorageFullBytes(File path) {
1375         return Settings.Global.getLong(mResolver, Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
1376                 DEFAULT_FULL_THRESHOLD_BYTES);
1377     }
1378 
1379     /** {@hide} */
createUserKey(int userId, int serialNumber, boolean ephemeral)1380     public void createUserKey(int userId, int serialNumber, boolean ephemeral) {
1381         try {
1382             mStorageManager.createUserKey(userId, serialNumber, ephemeral);
1383         } catch (RemoteException e) {
1384             throw e.rethrowFromSystemServer();
1385         }
1386     }
1387 
1388     /** {@hide} */
destroyUserKey(int userId)1389     public void destroyUserKey(int userId) {
1390         try {
1391             mStorageManager.destroyUserKey(userId);
1392         } catch (RemoteException e) {
1393             throw e.rethrowFromSystemServer();
1394         }
1395     }
1396 
1397     /** {@hide} */
unlockUserKey(int userId, int serialNumber, byte[] token, byte[] secret)1398     public void unlockUserKey(int userId, int serialNumber, byte[] token, byte[] secret) {
1399         try {
1400             mStorageManager.unlockUserKey(userId, serialNumber, token, secret);
1401         } catch (RemoteException e) {
1402             throw e.rethrowFromSystemServer();
1403         }
1404     }
1405 
1406     /** {@hide} */
lockUserKey(int userId)1407     public void lockUserKey(int userId) {
1408         try {
1409             mStorageManager.lockUserKey(userId);
1410         } catch (RemoteException e) {
1411             throw e.rethrowFromSystemServer();
1412         }
1413     }
1414 
1415     /** {@hide} */
prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags)1416     public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) {
1417         try {
1418             mStorageManager.prepareUserStorage(volumeUuid, userId, serialNumber, flags);
1419         } catch (RemoteException e) {
1420             throw e.rethrowFromSystemServer();
1421         }
1422     }
1423 
1424     /** {@hide} */
destroyUserStorage(String volumeUuid, int userId, int flags)1425     public void destroyUserStorage(String volumeUuid, int userId, int flags) {
1426         try {
1427             mStorageManager.destroyUserStorage(volumeUuid, userId, flags);
1428         } catch (RemoteException e) {
1429             throw e.rethrowFromSystemServer();
1430         }
1431     }
1432 
1433     /** {@hide} */
isUserKeyUnlocked(int userId)1434     public static boolean isUserKeyUnlocked(int userId) {
1435         if (sStorageManager == null) {
1436             sStorageManager = IStorageManager.Stub
1437                     .asInterface(ServiceManager.getService("mount"));
1438         }
1439         if (sStorageManager == null) {
1440             Slog.w(TAG, "Early during boot, assuming locked");
1441             return false;
1442         }
1443         final long token = Binder.clearCallingIdentity();
1444         try {
1445             return sStorageManager.isUserKeyUnlocked(userId);
1446         } catch (RemoteException e) {
1447             throw e.rethrowAsRuntimeException();
1448         } finally {
1449             Binder.restoreCallingIdentity(token);
1450         }
1451     }
1452 
1453     /**
1454      * Return if data stored at or under the given path will be encrypted while
1455      * at rest. This can help apps avoid the overhead of double-encrypting data.
1456      */
isEncrypted(File file)1457     public boolean isEncrypted(File file) {
1458         if (FileUtils.contains(Environment.getDataDirectory(), file)) {
1459             return isEncrypted();
1460         } else if (FileUtils.contains(Environment.getExpandDirectory(), file)) {
1461             return true;
1462         }
1463         // TODO: extend to support shared storage
1464         return false;
1465     }
1466 
1467     /** {@hide}
1468      * Is this device encryptable or already encrypted?
1469      * @return true for encryptable or encrypted
1470      *         false not encrypted and not encryptable
1471      */
isEncryptable()1472     public static boolean isEncryptable() {
1473         return RoSystemProperties.CRYPTO_ENCRYPTABLE;
1474     }
1475 
1476     /** {@hide}
1477      * Is this device already encrypted?
1478      * @return true for encrypted. (Implies isEncryptable() == true)
1479      *         false not encrypted
1480      */
isEncrypted()1481     public static boolean isEncrypted() {
1482         return RoSystemProperties.CRYPTO_ENCRYPTED;
1483     }
1484 
1485     /** {@hide}
1486      * Is this device file encrypted?
1487      * @return true for file encrypted. (Implies isEncrypted() == true)
1488      *         false not encrypted or block encrypted
1489      */
1490     @UnsupportedAppUsage
isFileEncryptedNativeOnly()1491     public static boolean isFileEncryptedNativeOnly() {
1492         if (!isEncrypted()) {
1493             return false;
1494         }
1495         return RoSystemProperties.CRYPTO_FILE_ENCRYPTED;
1496     }
1497 
1498     /** {@hide}
1499      * Is this device block encrypted?
1500      * @return true for block encrypted. (Implies isEncrypted() == true)
1501      *         false not encrypted or file encrypted
1502      */
isBlockEncrypted()1503     public static boolean isBlockEncrypted() {
1504         if (!isEncrypted()) {
1505             return false;
1506         }
1507         return RoSystemProperties.CRYPTO_BLOCK_ENCRYPTED;
1508     }
1509 
1510     /** {@hide}
1511      * Is this device block encrypted with credentials?
1512      * @return true for crediential block encrypted.
1513      *         (Implies isBlockEncrypted() == true)
1514      *         false not encrypted, file encrypted or default block encrypted
1515      */
isNonDefaultBlockEncrypted()1516     public static boolean isNonDefaultBlockEncrypted() {
1517         if (!isBlockEncrypted()) {
1518             return false;
1519         }
1520 
1521         try {
1522             IStorageManager storageManager = IStorageManager.Stub.asInterface(
1523                     ServiceManager.getService("mount"));
1524             return storageManager.getPasswordType() != CRYPT_TYPE_DEFAULT;
1525         } catch (RemoteException e) {
1526             Log.e(TAG, "Error getting encryption type");
1527             return false;
1528         }
1529     }
1530 
1531     /** {@hide}
1532      * Is this device in the process of being block encrypted?
1533      * @return true for encrypting.
1534      *         false otherwise
1535      * Whether device isEncrypted at this point is undefined
1536      * Note that only system services and CryptKeeper will ever see this return
1537      * true - no app will ever be launched in this state.
1538      * Also note that this state will not change without a teardown of the
1539      * framework, so no service needs to check for changes during their lifespan
1540      */
isBlockEncrypting()1541     public static boolean isBlockEncrypting() {
1542         final String state = VoldProperties.encrypt_progress().orElse("");
1543         return !"".equalsIgnoreCase(state);
1544     }
1545 
1546     /** {@hide}
1547      * Is this device non default block encrypted and in the process of
1548      * prompting for credentials?
1549      * @return true for prompting for credentials.
1550      *         (Implies isNonDefaultBlockEncrypted() == true)
1551      *         false otherwise
1552      * Note that only system services and CryptKeeper will ever see this return
1553      * true - no app will ever be launched in this state.
1554      * Also note that this state will not change without a teardown of the
1555      * framework, so no service needs to check for changes during their lifespan
1556      */
inCryptKeeperBounce()1557     public static boolean inCryptKeeperBounce() {
1558         final String status = VoldProperties.decrypt().orElse("");
1559         return "trigger_restart_min_framework".equals(status);
1560     }
1561 
1562     /** {@hide} */
isFileEncryptedEmulatedOnly()1563     public static boolean isFileEncryptedEmulatedOnly() {
1564         return SystemProperties.getBoolean(StorageManager.PROP_EMULATE_FBE, false);
1565     }
1566 
1567     /** {@hide}
1568      * Is this device running in a file encrypted mode, either native or emulated?
1569      * @return true for file encrypted, false otherwise
1570      */
isFileEncryptedNativeOrEmulated()1571     public static boolean isFileEncryptedNativeOrEmulated() {
1572         return isFileEncryptedNativeOnly()
1573                || isFileEncryptedEmulatedOnly();
1574     }
1575 
1576     /** {@hide} */
hasAdoptable()1577     public static boolean hasAdoptable() {
1578         return SystemProperties.getBoolean(PROP_HAS_ADOPTABLE, false);
1579     }
1580 
1581     /**
1582      * Return if the currently booted device has the "isolated storage" feature
1583      * flag enabled. This will eventually be fully enabled in the final
1584      * {@link android.os.Build.VERSION_CODES#Q} release.
1585      *
1586      * @hide
1587      */
1588     @SystemApi
1589     @TestApi
hasIsolatedStorage()1590     public static boolean hasIsolatedStorage() {
1591         // Prefer to use snapshot for current boot when available
1592         return SystemProperties.getBoolean(PROP_ISOLATED_STORAGE_SNAPSHOT,
1593                 SystemProperties.getBoolean(PROP_ISOLATED_STORAGE, true));
1594     }
1595 
1596     /**
1597      * @deprecated disabled now that FUSE has been replaced by sdcardfs
1598      * @hide
1599      */
1600     @Deprecated
maybeTranslateEmulatedPathToInternal(File path)1601     public static File maybeTranslateEmulatedPathToInternal(File path) {
1602         // Disabled now that FUSE has been replaced by sdcardfs
1603         return path;
1604     }
1605 
1606     /**
1607      * Translate given shared storage path from a path in an app sandbox
1608      * namespace to a path in the system namespace.
1609      *
1610      * @hide
1611      */
translateAppToSystem(File file, int pid, int uid)1612     public File translateAppToSystem(File file, int pid, int uid) {
1613         return file;
1614     }
1615 
1616     /**
1617      * Translate given shared storage path from a path in the system namespace
1618      * to a path in an app sandbox namespace.
1619      *
1620      * @hide
1621      */
translateSystemToApp(File file, int pid, int uid)1622     public File translateSystemToApp(File file, int pid, int uid) {
1623         return file;
1624     }
1625 
1626     /**
1627      * Check that given app holds both permission and appop.
1628      * @hide
1629      */
checkPermissionAndAppOp(Context context, boolean enforce, int pid, int uid, String packageName, String permission, int op)1630     public static boolean checkPermissionAndAppOp(Context context, boolean enforce,
1631             int pid, int uid, String packageName, String permission, int op) {
1632         return checkPermissionAndAppOp(context, enforce, pid, uid, packageName, permission, op,
1633                 true);
1634     }
1635 
1636     /**
1637      * Check that given app holds both permission and appop but do not noteOp.
1638      * @hide
1639      */
checkPermissionAndCheckOp(Context context, boolean enforce, int pid, int uid, String packageName, String permission, int op)1640     public static boolean checkPermissionAndCheckOp(Context context, boolean enforce,
1641             int pid, int uid, String packageName, String permission, int op) {
1642         return checkPermissionAndAppOp(context, enforce, pid, uid, packageName, permission, op,
1643                 false);
1644     }
1645 
1646     /**
1647      * Check that given app holds both permission and appop.
1648      * @hide
1649      */
checkPermissionAndAppOp(Context context, boolean enforce, int pid, int uid, String packageName, String permission, int op, boolean note)1650     private static boolean checkPermissionAndAppOp(Context context, boolean enforce,
1651             int pid, int uid, String packageName, String permission, int op, boolean note) {
1652         if (context.checkPermission(permission, pid, uid) != PERMISSION_GRANTED) {
1653             if (enforce) {
1654                 throw new SecurityException(
1655                         "Permission " + permission + " denied for package " + packageName);
1656             } else {
1657                 return false;
1658             }
1659         }
1660 
1661         AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
1662         final int mode;
1663         if (note) {
1664             mode = appOps.noteOpNoThrow(op, uid, packageName);
1665         } else {
1666             try {
1667                 appOps.checkPackage(uid, packageName);
1668             } catch (SecurityException e) {
1669                 if (enforce) {
1670                     throw e;
1671                 } else {
1672                     return false;
1673                 }
1674             }
1675             mode = appOps.checkOpNoThrow(op, uid, packageName);
1676         }
1677         switch (mode) {
1678             case AppOpsManager.MODE_ALLOWED:
1679                 return true;
1680             case AppOpsManager.MODE_DEFAULT:
1681             case AppOpsManager.MODE_IGNORED:
1682             case AppOpsManager.MODE_ERRORED:
1683                 if (enforce) {
1684                     throw new SecurityException("Op " + AppOpsManager.opToName(op) + " "
1685                             + AppOpsManager.modeToName(mode) + " for package " + packageName);
1686                 } else {
1687                     return false;
1688                 }
1689             default:
1690                 throw new IllegalStateException(
1691                         AppOpsManager.opToName(op) + " has unknown mode "
1692                                 + AppOpsManager.modeToName(mode));
1693         }
1694     }
1695 
checkPermissionAndAppOp(boolean enforce, int pid, int uid, String packageName, String permission, int op)1696     private boolean checkPermissionAndAppOp(boolean enforce,
1697             int pid, int uid, String packageName, String permission, int op) {
1698         return checkPermissionAndAppOp(mContext, enforce, pid, uid, packageName, permission, op);
1699     }
1700 
noteAppOpAllowingLegacy(boolean enforce, int pid, int uid, String packageName, int op)1701     private boolean noteAppOpAllowingLegacy(boolean enforce,
1702             int pid, int uid, String packageName, int op) {
1703         final int mode = mAppOps.noteOpNoThrow(op, uid, packageName);
1704         switch (mode) {
1705             case AppOpsManager.MODE_ALLOWED:
1706                 return true;
1707             case AppOpsManager.MODE_DEFAULT:
1708             case AppOpsManager.MODE_IGNORED:
1709             case AppOpsManager.MODE_ERRORED:
1710                 // Legacy apps technically have the access granted by this op,
1711                 // even when the op is denied
1712                 if ((mAppOps.checkOpNoThrow(OP_LEGACY_STORAGE, uid,
1713                         packageName) == AppOpsManager.MODE_ALLOWED)) return true;
1714 
1715                 if (enforce) {
1716                     throw new SecurityException("Op " + AppOpsManager.opToName(op) + " "
1717                             + AppOpsManager.modeToName(mode) + " for package " + packageName);
1718                 } else {
1719                     return false;
1720                 }
1721             default:
1722                 throw new IllegalStateException(
1723                         AppOpsManager.opToName(op) + " has unknown mode "
1724                                 + AppOpsManager.modeToName(mode));
1725         }
1726     }
1727 
1728     // Callers must hold both the old and new permissions, so that we can
1729     // handle obscure cases like when an app targets Q but was installed on
1730     // a device that was originally running on P before being upgraded to Q.
1731 
1732     /** {@hide} */
checkPermissionReadAudio(boolean enforce, int pid, int uid, String packageName)1733     public boolean checkPermissionReadAudio(boolean enforce,
1734             int pid, int uid, String packageName) {
1735         if (!checkPermissionAndAppOp(enforce, pid, uid, packageName,
1736                 READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE)) return false;
1737         return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, OP_READ_MEDIA_AUDIO);
1738     }
1739 
1740     /** {@hide} */
checkPermissionWriteAudio(boolean enforce, int pid, int uid, String packageName)1741     public boolean checkPermissionWriteAudio(boolean enforce,
1742             int pid, int uid, String packageName) {
1743         if (!checkPermissionAndAppOp(enforce, pid, uid, packageName,
1744                 WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE)) return false;
1745         return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, OP_WRITE_MEDIA_AUDIO);
1746     }
1747 
1748     /** {@hide} */
checkPermissionReadVideo(boolean enforce, int pid, int uid, String packageName)1749     public boolean checkPermissionReadVideo(boolean enforce,
1750             int pid, int uid, String packageName) {
1751         if (!checkPermissionAndAppOp(enforce, pid, uid, packageName,
1752                 READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE)) return false;
1753         return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, OP_READ_MEDIA_VIDEO);
1754     }
1755 
1756     /** {@hide} */
checkPermissionWriteVideo(boolean enforce, int pid, int uid, String packageName)1757     public boolean checkPermissionWriteVideo(boolean enforce,
1758             int pid, int uid, String packageName) {
1759         if (!checkPermissionAndAppOp(enforce, pid, uid, packageName,
1760                 WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE)) return false;
1761         return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, OP_WRITE_MEDIA_VIDEO);
1762     }
1763 
1764     /** {@hide} */
checkPermissionReadImages(boolean enforce, int pid, int uid, String packageName)1765     public boolean checkPermissionReadImages(boolean enforce,
1766             int pid, int uid, String packageName) {
1767         if (!checkPermissionAndAppOp(enforce, pid, uid, packageName,
1768                 READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE)) return false;
1769         return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, OP_READ_MEDIA_IMAGES);
1770     }
1771 
1772     /** {@hide} */
checkPermissionWriteImages(boolean enforce, int pid, int uid, String packageName)1773     public boolean checkPermissionWriteImages(boolean enforce,
1774             int pid, int uid, String packageName) {
1775         if (!checkPermissionAndAppOp(enforce, pid, uid, packageName,
1776                 WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE)) return false;
1777         return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, OP_WRITE_MEDIA_IMAGES);
1778     }
1779 
1780     /** {@hide} */
1781     @VisibleForTesting
openProxyFileDescriptor( int mode, ProxyFileDescriptorCallback callback, Handler handler, ThreadFactory factory)1782     public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
1783             int mode, ProxyFileDescriptorCallback callback, Handler handler, ThreadFactory factory)
1784                     throws IOException {
1785         Preconditions.checkNotNull(callback);
1786         MetricsLogger.count(mContext, "storage_open_proxy_file_descriptor", 1);
1787         // Retry is needed because the mount point mFuseAppLoop is using may be unmounted before
1788         // invoking StorageManagerService#openProxyFileDescriptor. In this case, we need to re-mount
1789         // the bridge by calling mountProxyFileDescriptorBridge.
1790         while (true) {
1791             try {
1792                 synchronized (mFuseAppLoopLock) {
1793                     boolean newlyCreated = false;
1794                     if (mFuseAppLoop == null) {
1795                         final AppFuseMount mount = mStorageManager.mountProxyFileDescriptorBridge();
1796                         if (mount == null) {
1797                             throw new IOException("Failed to mount proxy bridge");
1798                         }
1799                         mFuseAppLoop = new FuseAppLoop(mount.mountPointId, mount.fd, factory);
1800                         newlyCreated = true;
1801                     }
1802                     if (handler == null) {
1803                         handler = new Handler(Looper.getMainLooper());
1804                     }
1805                     try {
1806                         final int fileId = mFuseAppLoop.registerCallback(callback, handler);
1807                         final ParcelFileDescriptor pfd = mStorageManager.openProxyFileDescriptor(
1808                                 mFuseAppLoop.getMountPointId(), fileId, mode);
1809                         if (pfd == null) {
1810                             mFuseAppLoop.unregisterCallback(fileId);
1811                             throw new FuseUnavailableMountException(
1812                                     mFuseAppLoop.getMountPointId());
1813                         }
1814                         return pfd;
1815                     } catch (FuseUnavailableMountException exception) {
1816                         // The bridge is being unmounted. Tried to recreate it unless the bridge was
1817                         // just created.
1818                         if (newlyCreated) {
1819                             throw new IOException(exception);
1820                         }
1821                         mFuseAppLoop = null;
1822                         continue;
1823                     }
1824                 }
1825             } catch (RemoteException e) {
1826                 // Cannot recover from remote exception.
1827                 throw new IOException(e);
1828             }
1829         }
1830     }
1831 
1832     /** {@hide} */
openProxyFileDescriptor( int mode, ProxyFileDescriptorCallback callback)1833     public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
1834             int mode, ProxyFileDescriptorCallback callback)
1835                     throws IOException {
1836         return openProxyFileDescriptor(mode, callback, null, null);
1837     }
1838 
1839     /**
1840      * Opens a seekable {@link ParcelFileDescriptor} that proxies all low-level
1841      * I/O requests back to the given {@link ProxyFileDescriptorCallback}.
1842      * <p>
1843      * This can be useful when you want to provide quick access to a large file
1844      * that isn't backed by a real file on disk, such as a file on a network
1845      * share, cloud storage service, etc. As an example, you could respond to a
1846      * {@link ContentResolver#openFileDescriptor(android.net.Uri, String)}
1847      * request by returning a {@link ParcelFileDescriptor} created with this
1848      * method, and then stream the content on-demand as requested.
1849      * <p>
1850      * Another useful example might be where you have an encrypted file that
1851      * you're willing to decrypt on-demand, but where you want to avoid
1852      * persisting the cleartext version.
1853      *
1854      * @param mode The desired access mode, must be one of
1855      *            {@link ParcelFileDescriptor#MODE_READ_ONLY},
1856      *            {@link ParcelFileDescriptor#MODE_WRITE_ONLY}, or
1857      *            {@link ParcelFileDescriptor#MODE_READ_WRITE}
1858      * @param callback Callback to process file operation requests issued on
1859      *            returned file descriptor.
1860      * @param handler Handler that invokes callback methods.
1861      * @return Seekable ParcelFileDescriptor.
1862      * @throws IOException
1863      */
openProxyFileDescriptor( int mode, ProxyFileDescriptorCallback callback, Handler handler)1864     public @NonNull ParcelFileDescriptor openProxyFileDescriptor(
1865             int mode, ProxyFileDescriptorCallback callback, Handler handler)
1866                     throws IOException {
1867         Preconditions.checkNotNull(handler);
1868         return openProxyFileDescriptor(mode, callback, handler, null);
1869     }
1870 
1871     /** {@hide} */
1872     @VisibleForTesting
getProxyFileDescriptorMountPointId()1873     public int getProxyFileDescriptorMountPointId() {
1874         synchronized (mFuseAppLoopLock) {
1875             return mFuseAppLoop != null ? mFuseAppLoop.getMountPointId() : -1;
1876         }
1877     }
1878 
1879     /**
1880      * Return quota size in bytes for all cached data belonging to the calling
1881      * app on the given storage volume.
1882      * <p>
1883      * If your app goes above this quota, your cached files will be some of the
1884      * first to be deleted when additional disk space is needed. Conversely, if
1885      * your app stays under this quota, your cached files will be some of the
1886      * last to be deleted when additional disk space is needed.
1887      * <p>
1888      * This quota will change over time depending on how frequently the user
1889      * interacts with your app, and depending on how much system-wide disk space
1890      * is used.
1891      * <p class="note">
1892      * Note: if your app uses the {@code android:sharedUserId} manifest feature,
1893      * then cached data for all packages in your shared UID is tracked together
1894      * as a single unit.
1895      * </p>
1896      *
1897      * @param storageUuid the UUID of the storage volume that you're interested
1898      *            in. The UUID for a specific path can be obtained using
1899      *            {@link #getUuidForPath(File)}.
1900      * @throws IOException when the storage device isn't present, or when it
1901      *             doesn't support cache quotas.
1902      * @see #getCacheSizeBytes(UUID)
1903      */
1904     @WorkerThread
getCacheQuotaBytes(@onNull UUID storageUuid)1905     public @BytesLong long getCacheQuotaBytes(@NonNull UUID storageUuid) throws IOException {
1906         try {
1907             final ApplicationInfo app = mContext.getApplicationInfo();
1908             return mStorageManager.getCacheQuotaBytes(convert(storageUuid), app.uid);
1909         } catch (ParcelableException e) {
1910             e.maybeRethrow(IOException.class);
1911             throw new RuntimeException(e);
1912         } catch (RemoteException e) {
1913             throw e.rethrowFromSystemServer();
1914         }
1915     }
1916 
1917     /**
1918      * Return total size in bytes of all cached data belonging to the calling
1919      * app on the given storage volume.
1920      * <p>
1921      * Cached data tracked by this method always includes
1922      * {@link Context#getCacheDir()} and {@link Context#getCodeCacheDir()}, and
1923      * it also includes {@link Context#getExternalCacheDir()} if the primary
1924      * shared/external storage is hosted on the same storage device as your
1925      * private data.
1926      * <p class="note">
1927      * Note: if your app uses the {@code android:sharedUserId} manifest feature,
1928      * then cached data for all packages in your shared UID is tracked together
1929      * as a single unit.
1930      * </p>
1931      *
1932      * @param storageUuid the UUID of the storage volume that you're interested
1933      *            in. The UUID for a specific path can be obtained using
1934      *            {@link #getUuidForPath(File)}.
1935      * @throws IOException when the storage device isn't present, or when it
1936      *             doesn't support cache quotas.
1937      * @see #getCacheQuotaBytes(UUID)
1938      */
1939     @WorkerThread
getCacheSizeBytes(@onNull UUID storageUuid)1940     public @BytesLong long getCacheSizeBytes(@NonNull UUID storageUuid) throws IOException {
1941         try {
1942             final ApplicationInfo app = mContext.getApplicationInfo();
1943             return mStorageManager.getCacheSizeBytes(convert(storageUuid), app.uid);
1944         } catch (ParcelableException e) {
1945             e.maybeRethrow(IOException.class);
1946             throw new RuntimeException(e);
1947         } catch (RemoteException e) {
1948             throw e.rethrowFromSystemServer();
1949         }
1950     }
1951 
1952     /**
1953      * Flag indicating that a disk space allocation request should operate in an
1954      * aggressive mode. This flag should only be rarely used in situations that
1955      * are critical to system health or security.
1956      * <p>
1957      * When set, the system is more aggressive about the data that it considers
1958      * for possible deletion when allocating disk space.
1959      * <p class="note">
1960      * Note: your app must hold the
1961      * {@link android.Manifest.permission#ALLOCATE_AGGRESSIVE} permission for
1962      * this flag to take effect.
1963      * </p>
1964      *
1965      * @see #getAllocatableBytes(UUID, int)
1966      * @see #allocateBytes(UUID, long, int)
1967      * @see #allocateBytes(FileDescriptor, long, int)
1968      * @hide
1969      */
1970     @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE)
1971     @SystemApi
1972     public static final int FLAG_ALLOCATE_AGGRESSIVE = 1 << 0;
1973 
1974     /**
1975      * Flag indicating that a disk space allocation request should be allowed to
1976      * clear up to all reserved disk space.
1977      *
1978      * @hide
1979      */
1980     public static final int FLAG_ALLOCATE_DEFY_ALL_RESERVED = 1 << 1;
1981 
1982     /**
1983      * Flag indicating that a disk space allocation request should be allowed to
1984      * clear up to half of all reserved disk space.
1985      *
1986      * @hide
1987      */
1988     public static final int FLAG_ALLOCATE_DEFY_HALF_RESERVED = 1 << 2;
1989 
1990     /** @hide */
1991     @IntDef(flag = true, prefix = { "FLAG_ALLOCATE_" }, value = {
1992             FLAG_ALLOCATE_AGGRESSIVE,
1993             FLAG_ALLOCATE_DEFY_ALL_RESERVED,
1994             FLAG_ALLOCATE_DEFY_HALF_RESERVED,
1995     })
1996     @Retention(RetentionPolicy.SOURCE)
1997     public @interface AllocateFlags {}
1998 
1999     /**
2000      * Return the maximum number of new bytes that your app can allocate for
2001      * itself on the given storage volume. This value is typically larger than
2002      * {@link File#getUsableSpace()}, since the system may be willing to delete
2003      * cached files to satisfy an allocation request. You can then allocate
2004      * space for yourself using {@link #allocateBytes(UUID, long)} or
2005      * {@link #allocateBytes(FileDescriptor, long)}.
2006      * <p>
2007      * This method is best used as a pre-flight check, such as deciding if there
2008      * is enough space to store an entire music album before you allocate space
2009      * for each audio file in the album. Attempts to allocate disk space beyond
2010      * the returned value will fail.
2011      * <p>
2012      * If the returned value is not large enough for the data you'd like to
2013      * persist, you can launch {@link #ACTION_MANAGE_STORAGE} with the
2014      * {@link #EXTRA_UUID} and {@link #EXTRA_REQUESTED_BYTES} options to help
2015      * involve the user in freeing up disk space.
2016      * <p>
2017      * If you're progressively allocating an unbounded amount of storage space
2018      * (such as when recording a video) you should avoid calling this method
2019      * more than once every 30 seconds.
2020      * <p class="note">
2021      * Note: if your app uses the {@code android:sharedUserId} manifest feature,
2022      * then allocatable space for all packages in your shared UID is tracked
2023      * together as a single unit.
2024      * </p>
2025      *
2026      * @param storageUuid the UUID of the storage volume where you're
2027      *            considering allocating disk space, since allocatable space can
2028      *            vary widely depending on the underlying storage device. The
2029      *            UUID for a specific path can be obtained using
2030      *            {@link #getUuidForPath(File)}.
2031      * @return the maximum number of new bytes that the calling app can allocate
2032      *         using {@link #allocateBytes(UUID, long)} or
2033      *         {@link #allocateBytes(FileDescriptor, long)}.
2034      * @throws IOException when the storage device isn't present, or when it
2035      *             doesn't support allocating space.
2036      */
2037     @WorkerThread
getAllocatableBytes(@onNull UUID storageUuid)2038     public @BytesLong long getAllocatableBytes(@NonNull UUID storageUuid)
2039             throws IOException {
2040         return getAllocatableBytes(storageUuid, 0);
2041     }
2042 
2043     /** @hide */
2044     @SystemApi
2045     @WorkerThread
2046     @SuppressLint("Doclava125")
getAllocatableBytes(@onNull UUID storageUuid, @RequiresPermission @AllocateFlags int flags)2047     public long getAllocatableBytes(@NonNull UUID storageUuid,
2048             @RequiresPermission @AllocateFlags int flags) throws IOException {
2049         try {
2050             return mStorageManager.getAllocatableBytes(convert(storageUuid), flags,
2051                     mContext.getOpPackageName());
2052         } catch (ParcelableException e) {
2053             e.maybeRethrow(IOException.class);
2054             throw new RuntimeException(e);
2055         } catch (RemoteException e) {
2056             throw e.rethrowFromSystemServer();
2057         }
2058     }
2059 
2060     /**
2061      * Allocate the requested number of bytes for your application to use on the
2062      * given storage volume. This will cause the system to delete any cached
2063      * files necessary to satisfy your request.
2064      * <p>
2065      * Attempts to allocate disk space beyond the value returned by
2066      * {@link #getAllocatableBytes(UUID)} will fail.
2067      * <p>
2068      * Since multiple apps can be running simultaneously, this method may be
2069      * subject to race conditions. If possible, consider using
2070      * {@link #allocateBytes(FileDescriptor, long)} which will guarantee
2071      * that bytes are allocated to an opened file.
2072      * <p>
2073      * If you're progressively allocating an unbounded amount of storage space
2074      * (such as when recording a video) you should avoid calling this method
2075      * more than once every 60 seconds.
2076      *
2077      * @param storageUuid the UUID of the storage volume where you'd like to
2078      *            allocate disk space. The UUID for a specific path can be
2079      *            obtained using {@link #getUuidForPath(File)}.
2080      * @param bytes the number of bytes to allocate.
2081      * @throws IOException when the storage device isn't present, or when it
2082      *             doesn't support allocating space, or if the device had
2083      *             trouble allocating the requested space.
2084      * @see #getAllocatableBytes(UUID)
2085      */
2086     @WorkerThread
allocateBytes(@onNull UUID storageUuid, @BytesLong long bytes)2087     public void allocateBytes(@NonNull UUID storageUuid, @BytesLong long bytes)
2088             throws IOException {
2089         allocateBytes(storageUuid, bytes, 0);
2090     }
2091 
2092     /** @hide */
2093     @SystemApi
2094     @WorkerThread
2095     @SuppressLint("Doclava125")
allocateBytes(@onNull UUID storageUuid, @BytesLong long bytes, @RequiresPermission @AllocateFlags int flags)2096     public void allocateBytes(@NonNull UUID storageUuid, @BytesLong long bytes,
2097             @RequiresPermission @AllocateFlags int flags) throws IOException {
2098         try {
2099             mStorageManager.allocateBytes(convert(storageUuid), bytes, flags,
2100                     mContext.getOpPackageName());
2101         } catch (ParcelableException e) {
2102             e.maybeRethrow(IOException.class);
2103         } catch (RemoteException e) {
2104             throw e.rethrowFromSystemServer();
2105         }
2106     }
2107 
2108     /**
2109      * Allocate the requested number of bytes for your application to use in the
2110      * given open file. This will cause the system to delete any cached files
2111      * necessary to satisfy your request.
2112      * <p>
2113      * Attempts to allocate disk space beyond the value returned by
2114      * {@link #getAllocatableBytes(UUID)} will fail.
2115      * <p>
2116      * This method guarantees that bytes have been allocated to the opened file,
2117      * otherwise it will throw if fast allocation is not possible. Fast
2118      * allocation is typically only supported in private app data directories,
2119      * and on shared/external storage devices which are emulated.
2120      * <p>
2121      * If you're progressively allocating an unbounded amount of storage space
2122      * (such as when recording a video) you should avoid calling this method
2123      * more than once every 60 seconds.
2124      *
2125      * @param fd the open file that you'd like to allocate disk space for.
2126      * @param bytes the number of bytes to allocate. This is the desired final
2127      *            size of the open file. If the open file is smaller than this
2128      *            requested size, it will be extended without modifying any
2129      *            existing contents. If the open file is larger than this
2130      *            requested size, it will be truncated.
2131      * @throws IOException when the storage device isn't present, or when it
2132      *             doesn't support allocating space, or if the device had
2133      *             trouble allocating the requested space.
2134      * @see #isAllocationSupported(FileDescriptor)
2135      * @see Environment#isExternalStorageEmulated(File)
2136      */
2137     @WorkerThread
allocateBytes(FileDescriptor fd, @BytesLong long bytes)2138     public void allocateBytes(FileDescriptor fd, @BytesLong long bytes) throws IOException {
2139         allocateBytes(fd, bytes, 0);
2140     }
2141 
2142     /** @hide */
2143     @SystemApi
2144     @WorkerThread
2145     @SuppressLint("Doclava125")
allocateBytes(FileDescriptor fd, @BytesLong long bytes, @RequiresPermission @AllocateFlags int flags)2146     public void allocateBytes(FileDescriptor fd, @BytesLong long bytes,
2147             @RequiresPermission @AllocateFlags int flags) throws IOException {
2148         final File file = ParcelFileDescriptor.getFile(fd);
2149         final UUID uuid = getUuidForPath(file);
2150         for (int i = 0; i < 3; i++) {
2151             try {
2152                 final long haveBytes = Os.fstat(fd).st_blocks * 512;
2153                 final long needBytes = bytes - haveBytes;
2154 
2155                 if (needBytes > 0) {
2156                     allocateBytes(uuid, needBytes, flags);
2157                 }
2158 
2159                 try {
2160                     Os.posix_fallocate(fd, 0, bytes);
2161                     return;
2162                 } catch (ErrnoException e) {
2163                     if (e.errno == OsConstants.ENOSYS || e.errno == OsConstants.ENOTSUP) {
2164                         Log.w(TAG, "fallocate() not supported; falling back to ftruncate()");
2165                         Os.ftruncate(fd, bytes);
2166                         return;
2167                     } else {
2168                         throw e;
2169                     }
2170                 }
2171             } catch (ErrnoException e) {
2172                 if (e.errno == OsConstants.ENOSPC) {
2173                     Log.w(TAG, "Odd, not enough space; let's try again?");
2174                     continue;
2175                 }
2176                 throw e.rethrowAsIOException();
2177             }
2178         }
2179         throw new IOException(
2180                 "Well this is embarassing; we can't allocate " + bytes + " for " + file);
2181     }
2182 
2183     private static final String XATTR_CACHE_GROUP = "user.cache_group";
2184     private static final String XATTR_CACHE_TOMBSTONE = "user.cache_tombstone";
2185 
2186     /** {@hide} */
setCacheBehavior(File path, String name, boolean enabled)2187     private static void setCacheBehavior(File path, String name, boolean enabled)
2188             throws IOException {
2189         if (!path.isDirectory()) {
2190             throw new IOException("Cache behavior can only be set on directories");
2191         }
2192         if (enabled) {
2193             try {
2194                 Os.setxattr(path.getAbsolutePath(), name,
2195                         "1".getBytes(StandardCharsets.UTF_8), 0);
2196             } catch (ErrnoException e) {
2197                 throw e.rethrowAsIOException();
2198             }
2199         } else {
2200             try {
2201                 Os.removexattr(path.getAbsolutePath(), name);
2202             } catch (ErrnoException e) {
2203                 if (e.errno != OsConstants.ENODATA) {
2204                     throw e.rethrowAsIOException();
2205                 }
2206             }
2207         }
2208     }
2209 
2210     /** {@hide} */
isCacheBehavior(File path, String name)2211     private static boolean isCacheBehavior(File path, String name) throws IOException {
2212         try {
2213             Os.getxattr(path.getAbsolutePath(), name);
2214             return true;
2215         } catch (ErrnoException e) {
2216             if (e.errno != OsConstants.ENODATA) {
2217                 throw e.rethrowAsIOException();
2218             } else {
2219                 return false;
2220             }
2221         }
2222     }
2223 
2224     /**
2225      * Enable or disable special cache behavior that treats this directory and
2226      * its contents as an entire group.
2227      * <p>
2228      * When enabled and this directory is considered for automatic deletion by
2229      * the OS, all contained files will either be deleted together, or not at
2230      * all. This is useful when you have a directory that contains several
2231      * related metadata files that depend on each other, such as movie file and
2232      * a subtitle file.
2233      * <p>
2234      * When enabled, the <em>newest</em> {@link File#lastModified()} value of
2235      * any contained files is considered the modified time of the entire
2236      * directory.
2237      * <p>
2238      * This behavior can only be set on a directory, and it applies recursively
2239      * to all contained files and directories.
2240      */
setCacheBehaviorGroup(File path, boolean group)2241     public void setCacheBehaviorGroup(File path, boolean group) throws IOException {
2242         setCacheBehavior(path, XATTR_CACHE_GROUP, group);
2243     }
2244 
2245     /**
2246      * Read the current value set by
2247      * {@link #setCacheBehaviorGroup(File, boolean)}.
2248      */
isCacheBehaviorGroup(File path)2249     public boolean isCacheBehaviorGroup(File path) throws IOException {
2250         return isCacheBehavior(path, XATTR_CACHE_GROUP);
2251     }
2252 
2253     /**
2254      * Enable or disable special cache behavior that leaves deleted cache files
2255      * intact as tombstones.
2256      * <p>
2257      * When enabled and a file contained in this directory is automatically
2258      * deleted by the OS, the file will be truncated to have a length of 0 bytes
2259      * instead of being fully deleted. This is useful if you need to distinguish
2260      * between a file that was deleted versus one that never existed.
2261      * <p>
2262      * This behavior can only be set on a directory, and it applies recursively
2263      * to all contained files and directories.
2264      * <p class="note">
2265      * Note: this behavior is ignored completely if the user explicitly requests
2266      * that all cached data be cleared.
2267      * </p>
2268      */
setCacheBehaviorTombstone(File path, boolean tombstone)2269     public void setCacheBehaviorTombstone(File path, boolean tombstone) throws IOException {
2270         setCacheBehavior(path, XATTR_CACHE_TOMBSTONE, tombstone);
2271     }
2272 
2273     /**
2274      * Read the current value set by
2275      * {@link #setCacheBehaviorTombstone(File, boolean)}.
2276      */
isCacheBehaviorTombstone(File path)2277     public boolean isCacheBehaviorTombstone(File path) throws IOException {
2278         return isCacheBehavior(path, XATTR_CACHE_TOMBSTONE);
2279     }
2280 
2281     /** {@hide} */
convert(String uuid)2282     public static UUID convert(String uuid) {
2283         if (Objects.equals(uuid, UUID_PRIVATE_INTERNAL)) {
2284             return UUID_DEFAULT;
2285         } else if (Objects.equals(uuid, UUID_PRIMARY_PHYSICAL)) {
2286             return UUID_PRIMARY_PHYSICAL_;
2287         } else if (Objects.equals(uuid, UUID_SYSTEM)) {
2288             return UUID_SYSTEM_;
2289         } else {
2290             return UUID.fromString(uuid);
2291         }
2292     }
2293 
2294     /** {@hide} */
convert(UUID storageUuid)2295     public static String convert(UUID storageUuid) {
2296         if (UUID_DEFAULT.equals(storageUuid)) {
2297             return UUID_PRIVATE_INTERNAL;
2298         } else if (UUID_PRIMARY_PHYSICAL_.equals(storageUuid)) {
2299             return UUID_PRIMARY_PHYSICAL;
2300         } else if (UUID_SYSTEM_.equals(storageUuid)) {
2301             return UUID_SYSTEM;
2302         } else {
2303             return storageUuid.toString();
2304         }
2305     }
2306 
2307     /**
2308      * Check whether the device supports filesystem checkpoint.
2309      *
2310      * @return true if the device supports filesystem checkpoint, false otherwise.
2311      */
isCheckpointSupported()2312     public boolean isCheckpointSupported() {
2313         try {
2314             return mStorageManager.supportsCheckpoint();
2315         } catch (RemoteException e) {
2316             throw e.rethrowFromSystemServer();
2317         }
2318     }
2319 
2320     private final Object mFuseAppLoopLock = new Object();
2321 
2322     @GuardedBy("mFuseAppLoopLock")
2323     private @Nullable FuseAppLoop mFuseAppLoop = null;
2324 
2325     /// Consts to match the password types in cryptfs.h
2326     /** @hide */
2327     @UnsupportedAppUsage
2328     public static final int CRYPT_TYPE_PASSWORD = IVold.PASSWORD_TYPE_PASSWORD;
2329     /** @hide */
2330     @UnsupportedAppUsage
2331     public static final int CRYPT_TYPE_DEFAULT = IVold.PASSWORD_TYPE_DEFAULT;
2332     /** @hide */
2333     public static final int CRYPT_TYPE_PATTERN = IVold.PASSWORD_TYPE_PATTERN;
2334     /** @hide */
2335     public static final int CRYPT_TYPE_PIN = IVold.PASSWORD_TYPE_PIN;
2336 
2337     // Constants for the data available via StorageManagerService.getField.
2338     /** @hide */
2339     public static final String SYSTEM_LOCALE_KEY = "SystemLocale";
2340     /** @hide */
2341     public static final String OWNER_INFO_KEY = "OwnerInfo";
2342     /** @hide */
2343     public static final String PATTERN_VISIBLE_KEY = "PatternVisible";
2344     /** @hide */
2345     public static final String PASSWORD_VISIBLE_KEY = "PasswordVisible";
2346 }
2347