Final push to build against SDK.
The bulk of the work needed to get MediaProvider building against the "system_current" SDK surface has been slowly merged over the last few months, and this change makes the last few adjustments. This adds a new StorageVolumeCallback which is simpler version of StorageEventListener that simply delivers the changed StorageVolume. Move DownloadManager logic into a onMediaStoreDownloadsDeleted() method which hides the implementation details of how the OS connects with that implementation. Make local copies of some ExifInterface parsing logic; they could be added to the androidx version in an unbundled release. Make a local copy of RedactingFileDescriptor, since it's only needed for the next few weeks until FUSE is globally enabled. Bug: 137890034 Test: atest --test-mapping packages/providers/MediaProvider Change-Id: Ib416eb8724781bdd234c8b7d728dee8b695ad6ac
This commit is contained in:
@@ -9502,6 +9502,7 @@ package android.content {
|
||||
method public void dump(java.io.FileDescriptor, java.io.PrintWriter, String[]);
|
||||
method @Nullable public final String getCallingFeatureId();
|
||||
method @Nullable public final String getCallingPackage();
|
||||
method @Nullable public final String getCallingPackageUnchecked();
|
||||
method @Nullable public final android.content.Context getContext();
|
||||
method @Nullable public final android.content.pm.PathPermission[] getPathPermissions();
|
||||
method @Nullable public final String getReadPermission();
|
||||
@@ -9511,6 +9512,7 @@ package android.content {
|
||||
method @Nullable public abstract android.net.Uri insert(@NonNull android.net.Uri, @Nullable android.content.ContentValues);
|
||||
method @Nullable public android.net.Uri insert(@NonNull android.net.Uri, @Nullable android.content.ContentValues, @Nullable android.os.Bundle);
|
||||
method protected boolean isTemporary();
|
||||
method public void onCallingPackageChanged();
|
||||
method public void onConfigurationChanged(android.content.res.Configuration);
|
||||
method public abstract boolean onCreate();
|
||||
method public void onLowMemory();
|
||||
@@ -13321,6 +13323,7 @@ package android.database.sqlite {
|
||||
method @Deprecated public String buildUnionSubQuery(String, String[], java.util.Set<java.lang.String>, int, String, String, String[], String, String);
|
||||
method public int delete(@NonNull android.database.sqlite.SQLiteDatabase, @Nullable String, @Nullable String[]);
|
||||
method @Nullable public android.database.sqlite.SQLiteDatabase.CursorFactory getCursorFactory();
|
||||
method @Nullable public java.util.Collection<java.util.regex.Pattern> getProjectionGreylist();
|
||||
method @Nullable public java.util.Map<java.lang.String,java.lang.String> getProjectionMap();
|
||||
method @Nullable public String getTables();
|
||||
method public long insert(@NonNull android.database.sqlite.SQLiteDatabase, @NonNull android.content.ContentValues);
|
||||
@@ -13333,6 +13336,7 @@ package android.database.sqlite {
|
||||
method public android.database.Cursor query(android.database.sqlite.SQLiteDatabase, String[], String, String[], String, String, String, String, android.os.CancellationSignal);
|
||||
method public void setCursorFactory(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory);
|
||||
method public void setDistinct(boolean);
|
||||
method public void setProjectionGreylist(@Nullable java.util.Collection<java.util.regex.Pattern>);
|
||||
method public void setProjectionMap(@Nullable java.util.Map<java.lang.String,java.lang.String>);
|
||||
method public void setStrict(boolean);
|
||||
method public void setStrictColumns(boolean);
|
||||
@@ -36344,15 +36348,22 @@ package android.os.storage {
|
||||
method public boolean isObbMounted(String);
|
||||
method public boolean mountObb(String, String, android.os.storage.OnObbStateChangeListener);
|
||||
method @NonNull public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback, android.os.Handler) throws java.io.IOException;
|
||||
method public void registerStorageVolumeCallback(@NonNull java.util.concurrent.Executor, @NonNull android.os.storage.StorageManager.StorageVolumeCallback);
|
||||
method public void setCacheBehaviorGroup(java.io.File, boolean) throws java.io.IOException;
|
||||
method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
|
||||
method public boolean unmountObb(String, boolean, android.os.storage.OnObbStateChangeListener);
|
||||
method public void unregisterStorageVolumeCallback(@NonNull android.os.storage.StorageManager.StorageVolumeCallback);
|
||||
field public static final String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
|
||||
field public static final String EXTRA_REQUESTED_BYTES = "android.os.storage.extra.REQUESTED_BYTES";
|
||||
field public static final String EXTRA_UUID = "android.os.storage.extra.UUID";
|
||||
field public static final java.util.UUID UUID_DEFAULT;
|
||||
}
|
||||
|
||||
public static class StorageManager.StorageVolumeCallback {
|
||||
ctor public StorageManager.StorageVolumeCallback();
|
||||
method public void onStateChanged(@NonNull android.os.storage.StorageVolume);
|
||||
}
|
||||
|
||||
public final class StorageVolume implements android.os.Parcelable {
|
||||
method @Deprecated @Nullable public android.content.Intent createAccessIntent(String);
|
||||
method @NonNull public android.content.Intent createOpenDocumentTreeIntent();
|
||||
|
||||
@@ -553,6 +553,7 @@ package android.app {
|
||||
}
|
||||
|
||||
public class DownloadManager {
|
||||
method @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public void onMediaStoreDownloadsDeleted(@NonNull android.util.LongSparseArray<java.lang.String>);
|
||||
field public static final String ACTION_DOWNLOAD_COMPLETED = "android.intent.action.DOWNLOAD_COMPLETED";
|
||||
}
|
||||
|
||||
@@ -1629,11 +1630,17 @@ package android.content {
|
||||
method @NonNull public final android.os.UserHandle getSendingUser();
|
||||
}
|
||||
|
||||
public abstract class ContentProvider implements android.content.ComponentCallbacks2 {
|
||||
method public int checkUriPermission(@NonNull android.net.Uri, int, int);
|
||||
}
|
||||
|
||||
public class ContentProviderClient implements java.lang.AutoCloseable {
|
||||
method @RequiresPermission(android.Manifest.permission.REMOVE_TASKS) public void setDetectNotResponding(long);
|
||||
}
|
||||
|
||||
public abstract class ContentResolver {
|
||||
method @NonNull public static android.net.Uri decodeFromFile(@NonNull java.io.File);
|
||||
method @NonNull public static java.io.File encodeToFile(@NonNull android.net.Uri);
|
||||
method @Nullable @RequiresPermission("android.permission.CACHE_CONTENT") public android.os.Bundle getCache(@NonNull android.net.Uri);
|
||||
method @RequiresPermission("android.permission.CACHE_CONTENT") public void putCache(@NonNull android.net.Uri, @Nullable android.os.Bundle);
|
||||
}
|
||||
@@ -6873,6 +6880,10 @@ package android.os {
|
||||
method public boolean hasSingleFileDescriptor();
|
||||
}
|
||||
|
||||
public class ParcelFileDescriptor implements java.io.Closeable android.os.Parcelable {
|
||||
method @NonNull public static android.os.ParcelFileDescriptor wrap(@NonNull android.os.ParcelFileDescriptor, @NonNull android.os.Handler, @NonNull android.os.ParcelFileDescriptor.OnCloseListener) throws java.io.IOException;
|
||||
}
|
||||
|
||||
public final class PowerManager {
|
||||
method @RequiresPermission(allOf={android.Manifest.permission.READ_DREAM_STATE, android.Manifest.permission.WRITE_DREAM_STATE}) public void dream(long);
|
||||
method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public boolean forceSuspend();
|
||||
@@ -7249,6 +7260,10 @@ package android.os.storage {
|
||||
field @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public static final int FLAG_ALLOCATE_AGGRESSIVE = 1; // 0x1
|
||||
}
|
||||
|
||||
public final class StorageVolume implements android.os.Parcelable {
|
||||
method @NonNull public String getId();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package android.permission {
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
|
||||
package android.app;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SdkConstant;
|
||||
import android.annotation.SdkConstant.SdkConstantType;
|
||||
import android.annotation.SystemApi;
|
||||
@@ -40,11 +42,13 @@ import android.os.Environment;
|
||||
import android.os.FileUtils;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.BaseColumns;
|
||||
import android.provider.Downloads;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.Settings;
|
||||
import android.provider.Settings.SettingNotFoundException;
|
||||
import android.text.TextUtils;
|
||||
import android.util.LongSparseArray;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.io.File;
|
||||
@@ -1068,6 +1072,37 @@ public class DownloadManager {
|
||||
mAccessFilename = accessFilename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify {@link DownloadManager} that the given {@link MediaStore} items
|
||||
* were just deleted so that {@link DownloadManager} internal data
|
||||
* structures can be cleaned up.
|
||||
*
|
||||
* @param idToMime map from {@link BaseColumns#_ID} to
|
||||
* {@link ContentResolver#getType(Uri)}.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
|
||||
public void onMediaStoreDownloadsDeleted(@NonNull LongSparseArray<String> idToMime) {
|
||||
try (ContentProviderClient client = mResolver
|
||||
.acquireUnstableContentProviderClient(mBaseUri)) {
|
||||
final Bundle callExtras = new Bundle();
|
||||
final long[] ids = new long[idToMime.size()];
|
||||
final String[] mimeTypes = new String[idToMime.size()];
|
||||
for (int i = idToMime.size() - 1; i >= 0; --i) {
|
||||
ids[i] = idToMime.keyAt(i);
|
||||
mimeTypes[i] = idToMime.valueAt(i);
|
||||
}
|
||||
callExtras.putLongArray(android.provider.Downloads.EXTRA_IDS, ids);
|
||||
callExtras.putStringArray(android.provider.Downloads.EXTRA_MIME_TYPES,
|
||||
mimeTypes);
|
||||
client.call(android.provider.Downloads.CALL_MEDIASTORE_DOWNLOADS_DELETED,
|
||||
null, callExtras);
|
||||
} catch (RemoteException e) {
|
||||
// Should not happen
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a new download. The download will start automatically once the download manager is
|
||||
* ready to execute it and connectivity is available.
|
||||
|
||||
@@ -27,6 +27,7 @@ import static android.os.Trace.TRACE_TAG_DATABASE;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.app.AppOpsManager;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.content.pm.PackageManager;
|
||||
@@ -942,7 +943,18 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
|
||||
return null;
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
/**
|
||||
* Return the package name of the caller that initiated the request being
|
||||
* processed on the current thread. The returned package will have
|
||||
* <em>not</em> been verified to belong to the calling UID. Returns
|
||||
* {@code null} if not currently processing a request.
|
||||
* <p>
|
||||
* This will always return {@code null} when processing
|
||||
* {@link #getType(Uri)} or {@link #getStreamTypes(Uri, String)} requests.
|
||||
*
|
||||
* @see Binder#getCallingUid()
|
||||
* @see Context#grantUriPermission(String, Uri, int)
|
||||
*/
|
||||
public final @Nullable String getCallingPackageUnchecked() {
|
||||
final Pair<String, String> pkg = mCallingPackage.get();
|
||||
if (pkg != null) {
|
||||
@@ -952,7 +964,14 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
|
||||
return null;
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
/**
|
||||
* Called whenever the value of {@link #getCallingPackage()} changes, giving
|
||||
* the provider an opportunity to invalidate any security related caching it
|
||||
* may be performing.
|
||||
* <p>
|
||||
* This typically happens when a {@link ContentProvider} makes a nested call
|
||||
* back into itself when already processing a call from a remote process.
|
||||
*/
|
||||
public void onCallingPackageChanged() {
|
||||
}
|
||||
|
||||
@@ -1525,8 +1544,24 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
|
||||
return false;
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
/**
|
||||
* Perform a detailed internal check on a {@link Uri} to determine if a UID
|
||||
* is able to access it with specific mode flags.
|
||||
* <p>
|
||||
* This method is typically used when the provider implements more dynamic
|
||||
* access controls that cannot be expressed with {@code <path-permission>}
|
||||
* style static rules.
|
||||
*
|
||||
* @param uri the {@link Uri} to perform an access check on.
|
||||
* @param uid the UID to check the permission for.
|
||||
* @param modeFlags the access flags to use for the access check, such as
|
||||
* {@link Intent#FLAG_GRANT_READ_URI_PERMISSION}.
|
||||
* @return {@link PackageManager#PERMISSION_GRANTED} if access is allowed,
|
||||
* otherwise {@link PackageManager#PERMISSION_DENIED}.
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
@SystemApi
|
||||
public int checkUriPermission(@NonNull Uri uri, int uid, @Intent.AccessUriMode int modeFlags) {
|
||||
return PackageManager.PERMISSION_DENIED;
|
||||
}
|
||||
|
||||
@@ -3851,15 +3851,47 @@ public abstract class ContentResolver implements ContentInterface {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a path generated by {@link #encodeToFile(Uri)} back into
|
||||
* the original {@link Uri}.
|
||||
* <p>
|
||||
* This is used to offer a way to intercept filesystem calls in
|
||||
* {@link ContentProvider} unaware code and redirect them to a
|
||||
* {@link ContentProvider} when they attempt to use {@code _DATA} columns
|
||||
* that are otherwise deprecated.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public static @NonNull Uri decodeFromFile(@NonNull File file) {
|
||||
return translateDeprecatedDataPath(file.getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a {@link Uri} into an opaque filesystem path which can then be
|
||||
* resurrected by {@link #decodeFromFile(File)}.
|
||||
* <p>
|
||||
* This is used to offer a way to intercept filesystem calls in
|
||||
* {@link ContentProvider} unaware code and redirect them to a
|
||||
* {@link ContentProvider} when they attempt to use {@code _DATA} columns
|
||||
* that are otherwise deprecated.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public static @NonNull File encodeToFile(@NonNull Uri uri) {
|
||||
return new File(translateDeprecatedDataPath(uri));
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public static Uri translateDeprecatedDataPath(String path) {
|
||||
public static @NonNull Uri translateDeprecatedDataPath(@NonNull String path) {
|
||||
final String ssp = "//" + path.substring(DEPRECATE_DATA_PREFIX.length());
|
||||
return Uri.parse(new Uri.Builder().scheme(SCHEME_CONTENT)
|
||||
.encodedOpaquePart(ssp).build().toString());
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public static String translateDeprecatedDataPath(Uri uri) {
|
||||
public static @NonNull String translateDeprecatedDataPath(@NonNull Uri uri) {
|
||||
return DEPRECATE_DATA_PREFIX + uri.getEncodedSchemeSpecificPart().substring(2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ import com.android.internal.util.ArrayUtils;
|
||||
import libcore.util.EmptyArray;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
@@ -56,7 +56,7 @@ public class SQLiteQueryBuilder {
|
||||
"(?i)(AVG|COUNT|MAX|MIN|SUM|TOTAL|GROUP_CONCAT)\\((.+)\\)");
|
||||
|
||||
private Map<String, String> mProjectionMap = null;
|
||||
private List<Pattern> mProjectionGreylist = null;
|
||||
private Collection<Pattern> mProjectionGreylist = null;
|
||||
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
|
||||
private String mTables = "";
|
||||
@@ -196,20 +196,16 @@ public class SQLiteQueryBuilder {
|
||||
* Sets a projection greylist of columns that will be allowed through, even
|
||||
* when {@link #setStrict(boolean)} is enabled. This provides a way for
|
||||
* abusive custom columns like {@code COUNT(*)} to continue working.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void setProjectionGreylist(@Nullable List<Pattern> projectionGreylist) {
|
||||
public void setProjectionGreylist(@Nullable Collection<Pattern> projectionGreylist) {
|
||||
mProjectionGreylist = projectionGreylist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the projection greylist for the query, as last configured by
|
||||
* {@link #setProjectionGreylist(List)}.
|
||||
*
|
||||
* @hide
|
||||
* {@link #setProjectionGreylist}.
|
||||
*/
|
||||
public @Nullable List<Pattern> getProjectionGreylist() {
|
||||
public @Nullable Collection<Pattern> getProjectionGreylist() {
|
||||
return mProjectionGreylist;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@ import static android.system.OsConstants.S_ISLNK;
|
||||
import static android.system.OsConstants.S_ISREG;
|
||||
import static android.system.OsConstants.S_IWOTH;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.TestApi;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.content.BroadcastReceiver;
|
||||
@@ -253,6 +256,9 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
|
||||
* be opened with the requested mode.
|
||||
* @see #parseMode(String)
|
||||
*/
|
||||
// We can't accept a generic Executor here, since we need to use
|
||||
// MessageQueue.addOnFileDescriptorEventListener()
|
||||
@SuppressLint("ExecutorRegistration")
|
||||
public static ParcelFileDescriptor open(File file, int mode, Handler handler,
|
||||
final OnCloseListener listener) throws IOException {
|
||||
if (handler == null) {
|
||||
@@ -268,9 +274,22 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
|
||||
return fromFd(fd, handler, listener);
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public static ParcelFileDescriptor fromPfd(ParcelFileDescriptor pfd, Handler handler,
|
||||
final OnCloseListener listener) throws IOException {
|
||||
/**
|
||||
* Create a new ParcelFileDescriptor wrapping an already-opened file.
|
||||
*
|
||||
* @param pfd The already-opened file.
|
||||
* @param handler to call listener from.
|
||||
* @param listener to be invoked when the returned descriptor has been
|
||||
* closed.
|
||||
* @return a new ParcelFileDescriptor pointing to the given file.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
// We can't accept a generic Executor here, since we need to use
|
||||
// MessageQueue.addOnFileDescriptorEventListener()
|
||||
@SuppressLint("ExecutorRegistration")
|
||||
public static @NonNull ParcelFileDescriptor wrap(@NonNull ParcelFileDescriptor pfd,
|
||||
@NonNull Handler handler, @NonNull OnCloseListener listener) throws IOException {
|
||||
final FileDescriptor original = new FileDescriptor();
|
||||
original.setInt$(pfd.detachFd());
|
||||
return fromFd(original, handler, listener);
|
||||
|
||||
@@ -31,6 +31,7 @@ import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
|
||||
import android.annotation.BytesLong;
|
||||
import android.annotation.CallbackExecutor;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
@@ -72,6 +73,7 @@ import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.ServiceManager.ServiceNotFoundException;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.Settings;
|
||||
import android.sysprop.VoldProperties;
|
||||
@@ -93,7 +95,6 @@ import com.android.internal.os.AppFuseMount;
|
||||
import com.android.internal.os.FuseAppLoop;
|
||||
import com.android.internal.os.FuseUnavailableMountException;
|
||||
import com.android.internal.os.RoSystemProperties;
|
||||
import com.android.internal.os.SomeArgs;
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import dalvik.system.BlockGuard;
|
||||
@@ -114,6 +115,7 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@@ -305,109 +307,85 @@ public class StorageManager {
|
||||
private final Looper mLooper;
|
||||
private final AtomicInteger mNextNonce = new AtomicInteger(0);
|
||||
|
||||
@GuardedBy("mDelegates")
|
||||
private final ArrayList<StorageEventListenerDelegate> mDelegates = new ArrayList<>();
|
||||
|
||||
private static class StorageEventListenerDelegate extends IStorageEventListener.Stub implements
|
||||
Handler.Callback {
|
||||
private static final int MSG_STORAGE_STATE_CHANGED = 1;
|
||||
private static final int MSG_VOLUME_STATE_CHANGED = 2;
|
||||
private static final int MSG_VOLUME_RECORD_CHANGED = 3;
|
||||
private static final int MSG_VOLUME_FORGOTTEN = 4;
|
||||
private static final int MSG_DISK_SCANNED = 5;
|
||||
private static final int MSG_DISK_DESTROYED = 6;
|
||||
private class StorageEventListenerDelegate extends IStorageEventListener.Stub {
|
||||
final Executor mExecutor;
|
||||
final StorageEventListener mListener;
|
||||
final StorageVolumeCallback mCallback;
|
||||
|
||||
final StorageEventListener mCallback;
|
||||
final Handler mHandler;
|
||||
|
||||
public StorageEventListenerDelegate(StorageEventListener callback, Looper looper) {
|
||||
public StorageEventListenerDelegate(@NonNull Executor executor,
|
||||
@NonNull StorageEventListener listener, @NonNull StorageVolumeCallback callback) {
|
||||
mExecutor = executor;
|
||||
mListener = listener;
|
||||
mCallback = callback;
|
||||
mHandler = new Handler(looper, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
final SomeArgs args = (SomeArgs) msg.obj;
|
||||
switch (msg.what) {
|
||||
case MSG_STORAGE_STATE_CHANGED:
|
||||
mCallback.onStorageStateChanged((String) args.arg1, (String) args.arg2,
|
||||
(String) args.arg3);
|
||||
args.recycle();
|
||||
return true;
|
||||
case MSG_VOLUME_STATE_CHANGED:
|
||||
mCallback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3);
|
||||
args.recycle();
|
||||
return true;
|
||||
case MSG_VOLUME_RECORD_CHANGED:
|
||||
mCallback.onVolumeRecordChanged((VolumeRecord) args.arg1);
|
||||
args.recycle();
|
||||
return true;
|
||||
case MSG_VOLUME_FORGOTTEN:
|
||||
mCallback.onVolumeForgotten((String) args.arg1);
|
||||
args.recycle();
|
||||
return true;
|
||||
case MSG_DISK_SCANNED:
|
||||
mCallback.onDiskScanned((DiskInfo) args.arg1, args.argi2);
|
||||
args.recycle();
|
||||
return true;
|
||||
case MSG_DISK_DESTROYED:
|
||||
mCallback.onDiskDestroyed((DiskInfo) args.arg1);
|
||||
args.recycle();
|
||||
return true;
|
||||
}
|
||||
args.recycle();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUsbMassStorageConnectionChanged(boolean connected) throws RemoteException {
|
||||
// Ignored
|
||||
mExecutor.execute(() -> {
|
||||
mListener.onUsbMassStorageConnectionChanged(connected);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStorageStateChanged(String path, String oldState, String newState) {
|
||||
final SomeArgs args = SomeArgs.obtain();
|
||||
args.arg1 = path;
|
||||
args.arg2 = oldState;
|
||||
args.arg3 = newState;
|
||||
mHandler.obtainMessage(MSG_STORAGE_STATE_CHANGED, args).sendToTarget();
|
||||
mExecutor.execute(() -> {
|
||||
mListener.onStorageStateChanged(path, oldState, newState);
|
||||
|
||||
if (path != null) {
|
||||
for (StorageVolume sv : getStorageVolumes()) {
|
||||
if (Objects.equals(path, sv.getPath())) {
|
||||
mCallback.onStateChanged(sv);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
|
||||
final SomeArgs args = SomeArgs.obtain();
|
||||
args.arg1 = vol;
|
||||
args.argi2 = oldState;
|
||||
args.argi3 = newState;
|
||||
mHandler.obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget();
|
||||
mExecutor.execute(() -> {
|
||||
mListener.onVolumeStateChanged(vol, oldState, newState);
|
||||
|
||||
final File path = vol.getPathForUser(UserHandle.myUserId());
|
||||
if (path != null) {
|
||||
for (StorageVolume sv : getStorageVolumes()) {
|
||||
if (Objects.equals(path.getAbsolutePath(), sv.getPath())) {
|
||||
mCallback.onStateChanged(sv);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVolumeRecordChanged(VolumeRecord rec) {
|
||||
final SomeArgs args = SomeArgs.obtain();
|
||||
args.arg1 = rec;
|
||||
mHandler.obtainMessage(MSG_VOLUME_RECORD_CHANGED, args).sendToTarget();
|
||||
mExecutor.execute(() -> {
|
||||
mListener.onVolumeRecordChanged(rec);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVolumeForgotten(String fsUuid) {
|
||||
final SomeArgs args = SomeArgs.obtain();
|
||||
args.arg1 = fsUuid;
|
||||
mHandler.obtainMessage(MSG_VOLUME_FORGOTTEN, args).sendToTarget();
|
||||
mExecutor.execute(() -> {
|
||||
mListener.onVolumeForgotten(fsUuid);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDiskScanned(DiskInfo disk, int volumeCount) {
|
||||
final SomeArgs args = SomeArgs.obtain();
|
||||
args.arg1 = disk;
|
||||
args.argi2 = volumeCount;
|
||||
mHandler.obtainMessage(MSG_DISK_SCANNED, args).sendToTarget();
|
||||
mExecutor.execute(() -> {
|
||||
mListener.onDiskScanned(disk, volumeCount);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDiskDestroyed(DiskInfo disk) throws RemoteException {
|
||||
final SomeArgs args = SomeArgs.obtain();
|
||||
args.arg1 = disk;
|
||||
mHandler.obtainMessage(MSG_DISK_DESTROYED, args).sendToTarget();
|
||||
mExecutor.execute(() -> {
|
||||
mListener.onDiskDestroyed(disk);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,8 +503,8 @@ public class StorageManager {
|
||||
@UnsupportedAppUsage
|
||||
public void registerListener(StorageEventListener listener) {
|
||||
synchronized (mDelegates) {
|
||||
final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate(listener,
|
||||
mLooper);
|
||||
final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate(
|
||||
mContext.getMainExecutor(), listener, new StorageVolumeCallback());
|
||||
try {
|
||||
mStorageManager.registerListener(delegate);
|
||||
} catch (RemoteException e) {
|
||||
@@ -548,7 +526,76 @@ public class StorageManager {
|
||||
synchronized (mDelegates) {
|
||||
for (Iterator<StorageEventListenerDelegate> i = mDelegates.iterator(); i.hasNext();) {
|
||||
final StorageEventListenerDelegate delegate = i.next();
|
||||
if (delegate.mCallback == listener) {
|
||||
if (delegate.mListener == listener) {
|
||||
try {
|
||||
mStorageManager.unregisterListener(delegate);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback that delivers {@link StorageVolume} related events.
|
||||
* <p>
|
||||
* For example, this can be used to detect when a volume changes to the
|
||||
* {@link Environment#MEDIA_MOUNTED} or {@link Environment#MEDIA_UNMOUNTED}
|
||||
* states.
|
||||
*
|
||||
* @see StorageManager#registerStorageVolumeCallback
|
||||
* @see StorageManager#unregisterStorageVolumeCallback
|
||||
*/
|
||||
public static class StorageVolumeCallback {
|
||||
/**
|
||||
* Called when {@link StorageVolume#getState()} changes, such as
|
||||
* changing to the {@link Environment#MEDIA_MOUNTED} or
|
||||
* {@link Environment#MEDIA_UNMOUNTED} states.
|
||||
* <p>
|
||||
* The given argument is a snapshot in time and can be used to process
|
||||
* events in the order they occurred, or you can call
|
||||
* {@link StorageManager#getStorageVolumes()} to observe the latest
|
||||
* value.
|
||||
*/
|
||||
public void onStateChanged(@NonNull StorageVolume volume) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given callback to listen for {@link StorageVolume} changes.
|
||||
* <p>
|
||||
* For example, this can be used to detect when a volume changes to the
|
||||
* {@link Environment#MEDIA_MOUNTED} or {@link Environment#MEDIA_UNMOUNTED}
|
||||
* states.
|
||||
*
|
||||
* @see StorageManager#unregisterStorageVolumeCallback
|
||||
*/
|
||||
public void registerStorageVolumeCallback(@CallbackExecutor @NonNull Executor executor,
|
||||
@NonNull StorageVolumeCallback callback) {
|
||||
synchronized (mDelegates) {
|
||||
final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate(
|
||||
executor, new StorageEventListener(), callback);
|
||||
try {
|
||||
mStorageManager.registerListener(delegate);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
mDelegates.add(delegate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the given callback from listening for {@link StorageVolume}
|
||||
* changes.
|
||||
*
|
||||
* @see StorageManager#registerStorageVolumeCallback
|
||||
*/
|
||||
public void unregisterStorageVolumeCallback(@NonNull StorageVolumeCallback callback) {
|
||||
synchronized (mDelegates) {
|
||||
for (Iterator<StorageEventListenerDelegate> i = mDelegates.iterator(); i.hasNext();) {
|
||||
final StorageEventListenerDelegate delegate = i.next();
|
||||
if (delegate.mCallback == callback) {
|
||||
try {
|
||||
mStorageManager.unregisterListener(delegate);
|
||||
} catch (RemoteException e) {
|
||||
|
||||
@@ -18,6 +18,7 @@ package android.os.storage;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.TestApi;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.content.Context;
|
||||
@@ -162,9 +163,13 @@ public final class StorageVolume implements Parcelable {
|
||||
mState = in.readString();
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
@UnsupportedAppUsage
|
||||
public String getId() {
|
||||
/**
|
||||
* Return an opaque ID that can be used to identify this volume.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public @NonNull String getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user