Merge "Migrate MediaScannerConnection to using call()."
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 2299aad..a959913f 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -3592,10 +3592,23 @@
     }
 
     /** @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);
diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java
index 471fa2c..7eec8d9 100644
--- a/media/java/android/media/MediaScannerConnection.java
+++ b/media/java/android/media/MediaScannerConnection.java
@@ -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
@@ -38,20 +41,24 @@
  * 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);
-            }
         }
     };
 
@@ -81,15 +88,6 @@
          * 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);
     }
 
     /**
@@ -111,13 +109,12 @@
      */
     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();
+                }
             }
         }
     }
@@ -127,22 +124,9 @@
      */
     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;
             }
         }
     }
@@ -152,7 +136,7 @@
      * @return true if we are connected, false otherwise
      */
     public synchronized boolean isConnected() {
-        return (mService != null && mConnected);
+        return (mProvider != null);
     }
 
     /**
@@ -166,55 +150,15 @@
      */
      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);
-                }
-            }
-        }
-    }
-
-    static class ClientProxy implements MediaScannerConnectionClient {
-        final String[] mPaths;
-        final String[] mMimeTypes;
-        final OnScanCompletedListener mClient;
-        MediaScannerConnection mConnection;
-        int mNextPath;
-
-        ClientProxy(String[] paths, String[] mimeTypes, OnScanCompletedListener client) {
-            mPaths = paths;
-            mMimeTypes = mimeTypes;
-            mClient = client;
-        }
-
-        public void onMediaScannerConnected() {
-            scanNextPath();
-        }
-
-        public void onScanCompleted(String path, Uri uri) {
-            if (mClient != null) {
-                mClient.onScanCompleted(path, uri);
-            }
-            scanNextPath();
-        }
-
-        void scanNextPath() {
-            if (mNextPath >= mPaths.length) {
-                mConnection.disconnect();
-                mConnection = null;
-                return;
-            }
-            String mimeType = mMimeTypes != null ? mMimeTypes[mNextPath] : null;
-            mConnection.scanFile(mPaths[mNextPath], mimeType);
-            mNextPath++;
+            });
         }
     }
 
@@ -237,36 +181,76 @@
      */
     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.
-     */
-    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();
+        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() {
+        }
+
+        @Override
+        public void onScanCompleted(String path, Uri uri) {
+        }
+
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)
+        void scanNextPath() {
         }
     }
 
     /**
      * Part of the ServiceConnection interface.  Do not call.
      */
+    @Override
+    public void onServiceConnected(ComponentName className, IBinder service) {
+        // 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
     }
 }