Loading core/java/android/provider/MediaStore.java +13 −0 Original line number Diff line number Diff line Loading @@ -3591,11 +3591,24 @@ public final class MediaStore { scan(context, SCAN_VOLUME_CALL, file, false); } /** @hide */ public static Uri scanFile(ContentProviderClient client, File file) { return scan(client, SCAN_FILE_CALL, file, false); } /** @hide */ private static Uri scan(Context context, String method, File file, boolean originatedFromShell) { final ContentResolver resolver = context.getContentResolver(); try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) { return scan(client, method, file, originatedFromShell); } } /** @hide */ private static Uri scan(ContentProviderClient client, String method, File file, boolean originatedFromShell) { try { final Bundle in = new Bundle(); in.putParcelable(Intent.EXTRA_STREAM, Uri.fromFile(file)); in.putBoolean(EXTRA_ORIGINATED_FROM_SHELL, originatedFromShell); Loading media/java/android/media/MediaScannerConnection.java +91 −107 Original line number Diff line number Diff line Loading @@ -16,17 +16,20 @@ package android.media; import android.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.ContentProviderClient; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.media.IMediaScannerListener; import android.media.IMediaScannerService; import android.net.Uri; import android.os.Build; import android.os.IBinder; import android.os.RemoteException; import android.provider.MediaStore; import android.util.Log; import com.android.internal.os.BackgroundThread; import java.io.File; /** * MediaScannerConnection provides a way for applications to pass a Loading @@ -38,20 +41,24 @@ import android.util.Log; * to the client of the MediaScannerConnection class. */ public class MediaScannerConnection implements ServiceConnection { private static final String TAG = "MediaScannerConnection"; private Context mContext; private MediaScannerConnectionClient mClient; private IMediaScannerService mService; private boolean mConnected; // true if connect() has been called since last disconnect() private final Context mContext; private final MediaScannerConnectionClient mClient; private ContentProviderClient mProvider; @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) private IMediaScannerService mService; @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) private boolean mConnected; @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) private final IMediaScannerListener.Stub mListener = new IMediaScannerListener.Stub() { @Override public void scanCompleted(String path, Uri uri) { MediaScannerConnectionClient client = mClient; if (client != null) { client.onScanCompleted(path, uri); } } }; Loading Loading @@ -81,15 +88,6 @@ public class MediaScannerConnection implements ServiceConnection { * MediaScanner service has been established. */ public void onMediaScannerConnected(); /** * Called to notify the client when the media scanner has finished * scanning a file. * @param path the path to the file that has been scanned. * @param uri the Uri for the file if the scanning operation succeeded * and the file was added to the media database, or null if scanning failed. */ public void onScanCompleted(String path, Uri uri); } /** Loading @@ -111,13 +109,12 @@ public class MediaScannerConnection implements ServiceConnection { */ public void connect() { synchronized (this) { if (!mConnected) { Intent intent = new Intent(IMediaScannerService.class.getName()); intent.setComponent( new ComponentName("com.android.providers.media", "com.android.providers.media.MediaScannerService")); mContext.bindService(intent, this, Context.BIND_AUTO_CREATE); mConnected = true; if (mProvider == null) { mProvider = mContext.getContentResolver() .acquireContentProviderClient(MediaStore.AUTHORITY); if (mClient != null) { mClient.onMediaScannerConnected(); } } } } Loading @@ -127,22 +124,9 @@ public class MediaScannerConnection implements ServiceConnection { */ public void disconnect() { synchronized (this) { if (mConnected) { if (false) { Log.v(TAG, "Disconnecting from Media Scanner"); } try { mContext.unbindService(this); if (mClient instanceof ClientProxy) { mClient = null; } mService = null; } catch (IllegalArgumentException ex) { if (false) { Log.v(TAG, "disconnect failed: " + ex); } } mConnected = false; if (mProvider != null) { mProvider.close(); mProvider = null; } } } Loading @@ -152,7 +136,7 @@ public class MediaScannerConnection implements ServiceConnection { * @return true if we are connected, false otherwise */ public synchronized boolean isConnected() { return (mService != null && mConnected); return (mProvider != null); } /** Loading @@ -166,107 +150,107 @@ public class MediaScannerConnection implements ServiceConnection { */ public void scanFile(String path, String mimeType) { synchronized (this) { if (mService == null || !mConnected) { if (mProvider == null) { throw new IllegalStateException("not connected to MediaScannerService"); } try { if (false) { Log.v(TAG, "Scanning file " + path); BackgroundThread.getExecutor().execute(() -> { final Uri uri = scanFileQuietly(mProvider, new File(path)); if (mClient != null) { mClient.onScanCompleted(path, uri); } mService.requestScanFile(path, mimeType, mListener); } catch (RemoteException e) { if (false) { Log.d(TAG, "Failed to scan file " + path); }); } } /** * Convenience for constructing a {@link MediaScannerConnection}, calling * {@link #connect} on it, and calling {@link #scanFile} with the given * <var>path</var> and <var>mimeType</var> when the connection is * established. * @param context The caller's Context, required for establishing a connection to * the media scanner service. * Success or failure of the scanning operation cannot be determined until * {@link MediaScannerConnectionClient#onScanCompleted(String, Uri)} is called. * @param paths Array of paths to be scanned. * @param mimeTypes Optional array of MIME types for each path. * If mimeType is null, then the mimeType will be inferred from the file extension. * @param callback Optional callback through which you can receive the * scanned URI and MIME type; If null, the file will be scanned but * you will not get a result back. * @see #scanFile(String, String) */ public static void scanFile(Context context, String[] paths, String[] mimeTypes, OnScanCompletedListener callback) { BackgroundThread.getExecutor().execute(() -> { try (ContentProviderClient client = context.getContentResolver() .acquireContentProviderClient(MediaStore.AUTHORITY)) { for (String path : paths) { final Uri uri = scanFileQuietly(client, new File(path)); if (callback != null) { callback.onScanCompleted(path, uri); } } } }); } private static Uri scanFileQuietly(ContentProviderClient client, File file) { Uri uri = null; try { uri = MediaStore.scanFile(client, file); Log.d(TAG, "Scanned " + file + " to " + uri); } catch (Exception e) { Log.w(TAG, "Failed to scan " + file + ": " + e); } return uri; } @Deprecated static class ClientProxy implements MediaScannerConnectionClient { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) final String[] mPaths; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) final String[] mMimeTypes; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) final OnScanCompletedListener mClient; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) MediaScannerConnection mConnection; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) int mNextPath; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) ClientProxy(String[] paths, String[] mimeTypes, OnScanCompletedListener client) { mPaths = paths; mMimeTypes = mimeTypes; mClient = client; } @Override public void onMediaScannerConnected() { scanNextPath(); } @Override public void onScanCompleted(String path, Uri uri) { if (mClient != null) { mClient.onScanCompleted(path, uri); } scanNextPath(); } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) void scanNextPath() { if (mNextPath >= mPaths.length) { mConnection.disconnect(); mConnection = null; return; } String mimeType = mMimeTypes != null ? mMimeTypes[mNextPath] : null; mConnection.scanFile(mPaths[mNextPath], mimeType); mNextPath++; } } /** * Convenience for constructing a {@link MediaScannerConnection}, calling * {@link #connect} on it, and calling {@link #scanFile} with the given * <var>path</var> and <var>mimeType</var> when the connection is * established. * @param context The caller's Context, required for establishing a connection to * the media scanner service. * Success or failure of the scanning operation cannot be determined until * {@link MediaScannerConnectionClient#onScanCompleted(String, Uri)} is called. * @param paths Array of paths to be scanned. * @param mimeTypes Optional array of MIME types for each path. * If mimeType is null, then the mimeType will be inferred from the file extension. * @param callback Optional callback through which you can receive the * scanned URI and MIME type; If null, the file will be scanned but * you will not get a result back. * @see #scanFile(String, String) */ public static void scanFile(Context context, String[] paths, String[] mimeTypes, OnScanCompletedListener callback) { ClientProxy client = new ClientProxy(paths, mimeTypes, callback); MediaScannerConnection connection = new MediaScannerConnection(context, client); client.mConnection = connection; connection.connect(); } /** * Part of the ServiceConnection interface. Do not call. */ @Override public void onServiceConnected(ComponentName className, IBinder service) { if (false) { Log.v(TAG, "Connected to Media Scanner"); } synchronized (this) { mService = IMediaScannerService.Stub.asInterface(service); if (mService != null && mClient != null) { mClient.onMediaScannerConnected(); } } // No longer needed } /** * Part of the ServiceConnection interface. Do not call. */ @Override public void onServiceDisconnected(ComponentName className) { if (false) { Log.v(TAG, "Disconnected from Media Scanner"); } synchronized (this) { mService = null; } // No longer needed } } Loading
core/java/android/provider/MediaStore.java +13 −0 Original line number Diff line number Diff line Loading @@ -3591,11 +3591,24 @@ public final class MediaStore { scan(context, SCAN_VOLUME_CALL, file, false); } /** @hide */ public static Uri scanFile(ContentProviderClient client, File file) { return scan(client, SCAN_FILE_CALL, file, false); } /** @hide */ private static Uri scan(Context context, String method, File file, boolean originatedFromShell) { final ContentResolver resolver = context.getContentResolver(); try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) { return scan(client, method, file, originatedFromShell); } } /** @hide */ private static Uri scan(ContentProviderClient client, String method, File file, boolean originatedFromShell) { try { final Bundle in = new Bundle(); in.putParcelable(Intent.EXTRA_STREAM, Uri.fromFile(file)); in.putBoolean(EXTRA_ORIGINATED_FROM_SHELL, originatedFromShell); Loading
media/java/android/media/MediaScannerConnection.java +91 −107 Original line number Diff line number Diff line Loading @@ -16,17 +16,20 @@ package android.media; import android.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.ContentProviderClient; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.media.IMediaScannerListener; import android.media.IMediaScannerService; import android.net.Uri; import android.os.Build; import android.os.IBinder; import android.os.RemoteException; import android.provider.MediaStore; import android.util.Log; import com.android.internal.os.BackgroundThread; import java.io.File; /** * MediaScannerConnection provides a way for applications to pass a Loading @@ -38,20 +41,24 @@ import android.util.Log; * to the client of the MediaScannerConnection class. */ public class MediaScannerConnection implements ServiceConnection { private static final String TAG = "MediaScannerConnection"; private Context mContext; private MediaScannerConnectionClient mClient; private IMediaScannerService mService; private boolean mConnected; // true if connect() has been called since last disconnect() private final Context mContext; private final MediaScannerConnectionClient mClient; private ContentProviderClient mProvider; @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) private IMediaScannerService mService; @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) private boolean mConnected; @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) private final IMediaScannerListener.Stub mListener = new IMediaScannerListener.Stub() { @Override public void scanCompleted(String path, Uri uri) { MediaScannerConnectionClient client = mClient; if (client != null) { client.onScanCompleted(path, uri); } } }; Loading Loading @@ -81,15 +88,6 @@ public class MediaScannerConnection implements ServiceConnection { * MediaScanner service has been established. */ public void onMediaScannerConnected(); /** * Called to notify the client when the media scanner has finished * scanning a file. * @param path the path to the file that has been scanned. * @param uri the Uri for the file if the scanning operation succeeded * and the file was added to the media database, or null if scanning failed. */ public void onScanCompleted(String path, Uri uri); } /** Loading @@ -111,13 +109,12 @@ public class MediaScannerConnection implements ServiceConnection { */ public void connect() { synchronized (this) { if (!mConnected) { Intent intent = new Intent(IMediaScannerService.class.getName()); intent.setComponent( new ComponentName("com.android.providers.media", "com.android.providers.media.MediaScannerService")); mContext.bindService(intent, this, Context.BIND_AUTO_CREATE); mConnected = true; if (mProvider == null) { mProvider = mContext.getContentResolver() .acquireContentProviderClient(MediaStore.AUTHORITY); if (mClient != null) { mClient.onMediaScannerConnected(); } } } } Loading @@ -127,22 +124,9 @@ public class MediaScannerConnection implements ServiceConnection { */ public void disconnect() { synchronized (this) { if (mConnected) { if (false) { Log.v(TAG, "Disconnecting from Media Scanner"); } try { mContext.unbindService(this); if (mClient instanceof ClientProxy) { mClient = null; } mService = null; } catch (IllegalArgumentException ex) { if (false) { Log.v(TAG, "disconnect failed: " + ex); } } mConnected = false; if (mProvider != null) { mProvider.close(); mProvider = null; } } } Loading @@ -152,7 +136,7 @@ public class MediaScannerConnection implements ServiceConnection { * @return true if we are connected, false otherwise */ public synchronized boolean isConnected() { return (mService != null && mConnected); return (mProvider != null); } /** Loading @@ -166,107 +150,107 @@ public class MediaScannerConnection implements ServiceConnection { */ public void scanFile(String path, String mimeType) { synchronized (this) { if (mService == null || !mConnected) { if (mProvider == null) { throw new IllegalStateException("not connected to MediaScannerService"); } try { if (false) { Log.v(TAG, "Scanning file " + path); BackgroundThread.getExecutor().execute(() -> { final Uri uri = scanFileQuietly(mProvider, new File(path)); if (mClient != null) { mClient.onScanCompleted(path, uri); } mService.requestScanFile(path, mimeType, mListener); } catch (RemoteException e) { if (false) { Log.d(TAG, "Failed to scan file " + path); }); } } /** * Convenience for constructing a {@link MediaScannerConnection}, calling * {@link #connect} on it, and calling {@link #scanFile} with the given * <var>path</var> and <var>mimeType</var> when the connection is * established. * @param context The caller's Context, required for establishing a connection to * the media scanner service. * Success or failure of the scanning operation cannot be determined until * {@link MediaScannerConnectionClient#onScanCompleted(String, Uri)} is called. * @param paths Array of paths to be scanned. * @param mimeTypes Optional array of MIME types for each path. * If mimeType is null, then the mimeType will be inferred from the file extension. * @param callback Optional callback through which you can receive the * scanned URI and MIME type; If null, the file will be scanned but * you will not get a result back. * @see #scanFile(String, String) */ public static void scanFile(Context context, String[] paths, String[] mimeTypes, OnScanCompletedListener callback) { BackgroundThread.getExecutor().execute(() -> { try (ContentProviderClient client = context.getContentResolver() .acquireContentProviderClient(MediaStore.AUTHORITY)) { for (String path : paths) { final Uri uri = scanFileQuietly(client, new File(path)); if (callback != null) { callback.onScanCompleted(path, uri); } } } }); } private static Uri scanFileQuietly(ContentProviderClient client, File file) { Uri uri = null; try { uri = MediaStore.scanFile(client, file); Log.d(TAG, "Scanned " + file + " to " + uri); } catch (Exception e) { Log.w(TAG, "Failed to scan " + file + ": " + e); } return uri; } @Deprecated static class ClientProxy implements MediaScannerConnectionClient { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) final String[] mPaths; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) final String[] mMimeTypes; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) final OnScanCompletedListener mClient; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) MediaScannerConnection mConnection; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) int mNextPath; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) ClientProxy(String[] paths, String[] mimeTypes, OnScanCompletedListener client) { mPaths = paths; mMimeTypes = mimeTypes; mClient = client; } @Override public void onMediaScannerConnected() { scanNextPath(); } @Override public void onScanCompleted(String path, Uri uri) { if (mClient != null) { mClient.onScanCompleted(path, uri); } scanNextPath(); } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) void scanNextPath() { if (mNextPath >= mPaths.length) { mConnection.disconnect(); mConnection = null; return; } String mimeType = mMimeTypes != null ? mMimeTypes[mNextPath] : null; mConnection.scanFile(mPaths[mNextPath], mimeType); mNextPath++; } } /** * Convenience for constructing a {@link MediaScannerConnection}, calling * {@link #connect} on it, and calling {@link #scanFile} with the given * <var>path</var> and <var>mimeType</var> when the connection is * established. * @param context The caller's Context, required for establishing a connection to * the media scanner service. * Success or failure of the scanning operation cannot be determined until * {@link MediaScannerConnectionClient#onScanCompleted(String, Uri)} is called. * @param paths Array of paths to be scanned. * @param mimeTypes Optional array of MIME types for each path. * If mimeType is null, then the mimeType will be inferred from the file extension. * @param callback Optional callback through which you can receive the * scanned URI and MIME type; If null, the file will be scanned but * you will not get a result back. * @see #scanFile(String, String) */ public static void scanFile(Context context, String[] paths, String[] mimeTypes, OnScanCompletedListener callback) { ClientProxy client = new ClientProxy(paths, mimeTypes, callback); MediaScannerConnection connection = new MediaScannerConnection(context, client); client.mConnection = connection; connection.connect(); } /** * Part of the ServiceConnection interface. Do not call. */ @Override public void onServiceConnected(ComponentName className, IBinder service) { if (false) { Log.v(TAG, "Connected to Media Scanner"); } synchronized (this) { mService = IMediaScannerService.Stub.asInterface(service); if (mService != null && mClient != null) { mClient.onMediaScannerConnected(); } } // No longer needed } /** * Part of the ServiceConnection interface. Do not call. */ @Override public void onServiceDisconnected(ComponentName className) { if (false) { Log.v(TAG, "Disconnected from Media Scanner"); } synchronized (this) { mService = null; } // No longer needed } }