OSDN Git Service

Discourage use of "_data" column.
authorJeff Sharkey <jsharkey@android.com>
Wed, 6 Jan 2016 00:30:57 +0000 (17:30 -0700)
committerJeff Sharkey <jsharkey@android.com>
Wed, 6 Jan 2016 17:19:35 +0000 (10:19 -0700)
Moving forward, all client file access really needs to be going
through explicit APIs like openFileDescriptor(), since that allows
the provider to better protect its underlying files.

This change also changes several classes to use the AutoClosable
pattern, which enables try-with-resources usage.  Older release()
methods are deprecated in favor of close().

Uniformly apply CloseGuard across several classes, using
AtomicBoolean to avoid double-freeing, and fix several resource
leaks and bugs related to MediaScanner allocation.  Switch
MediaScanner and friends to use public API instead of raw AIDL calls.

Bug: 22958127
Change-Id: Id722379f72c9e4b80d8b72550d7ce90e5e2bc786

14 files changed:
api/current.txt
api/system-current.txt
api/test-current.txt
core/java/android/app/DownloadManager.java
core/java/android/app/SystemServiceRegistry.java
core/java/android/content/ContentProviderClient.java
core/java/android/content/ContentResolver.java
core/java/android/provider/MediaStore.java
drm/java/android/drm/DrmManagerClient.java
media/java/android/media/MediaInserter.java
media/java/android/media/MediaScanner.java
media/java/android/mtp/MtpDatabase.java
media/java/android/mtp/MtpPropertyGroup.java
media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java

index 94bb21a..53db7b2 100644 (file)
@@ -4228,7 +4228,7 @@ package android.app {
     field public static final java.lang.String COLUMN_DESCRIPTION = "description";
     field public static final java.lang.String COLUMN_ID = "_id";
     field public static final java.lang.String COLUMN_LAST_MODIFIED_TIMESTAMP = "last_modified_timestamp";
-    field public static final java.lang.String COLUMN_LOCAL_FILENAME = "local_filename";
+    field public static final deprecated java.lang.String COLUMN_LOCAL_FILENAME = "local_filename";
     field public static final java.lang.String COLUMN_LOCAL_URI = "local_uri";
     field public static final java.lang.String COLUMN_MEDIAPROVIDER_URI = "mediaprovider_uri";
     field public static final java.lang.String COLUMN_MEDIA_TYPE = "media_type";
@@ -7568,11 +7568,12 @@ package android.content {
     method public abstract void writeDataToPipe(android.os.ParcelFileDescriptor, android.net.Uri, java.lang.String, android.os.Bundle, T);
   }
 
-  public class ContentProviderClient {
+  public class ContentProviderClient implements java.lang.AutoCloseable {
     method public android.content.ContentProviderResult[] applyBatch(java.util.ArrayList<android.content.ContentProviderOperation>) throws android.content.OperationApplicationException, android.os.RemoteException;
     method public int bulkInsert(android.net.Uri, android.content.ContentValues[]) throws android.os.RemoteException;
     method public android.os.Bundle call(java.lang.String, java.lang.String, android.os.Bundle) throws android.os.RemoteException;
     method public final android.net.Uri canonicalize(android.net.Uri) throws android.os.RemoteException;
+    method public void close();
     method public int delete(android.net.Uri, java.lang.String, java.lang.String[]) throws android.os.RemoteException;
     method public android.content.ContentProvider getLocalContentProvider();
     method public java.lang.String[] getStreamTypes(android.net.Uri, java.lang.String) throws android.os.RemoteException;
@@ -7586,7 +7587,7 @@ package android.content {
     method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException;
     method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) throws android.os.RemoteException;
     method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal) throws android.os.RemoteException;
-    method public boolean release();
+    method public deprecated boolean release();
     method public final android.net.Uri uncanonicalize(android.net.Uri) throws android.os.RemoteException;
     method public int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]) throws android.os.RemoteException;
   }
@@ -10844,7 +10845,7 @@ package android.drm {
     field public final int statusCode;
   }
 
-  public class DrmManagerClient {
+  public class DrmManagerClient implements java.lang.AutoCloseable {
     ctor public DrmManagerClient(android.content.Context);
     method public android.drm.DrmInfo acquireDrmInfo(android.drm.DrmInfoRequest);
     method public int acquireRights(android.drm.DrmInfoRequest);
@@ -10854,6 +10855,7 @@ package android.drm {
     method public int checkRightsStatus(android.net.Uri);
     method public int checkRightsStatus(java.lang.String, int);
     method public int checkRightsStatus(android.net.Uri, int);
+    method public void close();
     method public android.drm.DrmConvertedStatus closeConvertSession(int);
     method public android.drm.DrmConvertedStatus convertData(int, byte[]);
     method public java.lang.String[] getAvailableDrmEngines();
@@ -10867,7 +10869,7 @@ package android.drm {
     method public java.lang.String getOriginalMimeType(android.net.Uri);
     method public int openConvertSession(java.lang.String);
     method public int processDrmInfo(android.drm.DrmInfo);
-    method public void release();
+    method public deprecated void release();
     method public int removeAllRights();
     method public int removeRights(java.lang.String);
     method public int removeRights(android.net.Uri);
index 79e44ea..967e6c8 100644 (file)
@@ -4348,7 +4348,7 @@ package android.app {
     field public static final java.lang.String COLUMN_DESCRIPTION = "description";
     field public static final java.lang.String COLUMN_ID = "_id";
     field public static final java.lang.String COLUMN_LAST_MODIFIED_TIMESTAMP = "last_modified_timestamp";
-    field public static final java.lang.String COLUMN_LOCAL_FILENAME = "local_filename";
+    field public static final deprecated java.lang.String COLUMN_LOCAL_FILENAME = "local_filename";
     field public static final java.lang.String COLUMN_LOCAL_URI = "local_uri";
     field public static final java.lang.String COLUMN_MEDIAPROVIDER_URI = "mediaprovider_uri";
     field public static final java.lang.String COLUMN_MEDIA_TYPE = "media_type";
@@ -7811,11 +7811,12 @@ package android.content {
     method public abstract void writeDataToPipe(android.os.ParcelFileDescriptor, android.net.Uri, java.lang.String, android.os.Bundle, T);
   }
 
-  public class ContentProviderClient {
+  public class ContentProviderClient implements java.lang.AutoCloseable {
     method public android.content.ContentProviderResult[] applyBatch(java.util.ArrayList<android.content.ContentProviderOperation>) throws android.content.OperationApplicationException, android.os.RemoteException;
     method public int bulkInsert(android.net.Uri, android.content.ContentValues[]) throws android.os.RemoteException;
     method public android.os.Bundle call(java.lang.String, java.lang.String, android.os.Bundle) throws android.os.RemoteException;
     method public final android.net.Uri canonicalize(android.net.Uri) throws android.os.RemoteException;
+    method public void close();
     method public int delete(android.net.Uri, java.lang.String, java.lang.String[]) throws android.os.RemoteException;
     method public android.content.ContentProvider getLocalContentProvider();
     method public java.lang.String[] getStreamTypes(android.net.Uri, java.lang.String) throws android.os.RemoteException;
@@ -7829,7 +7830,7 @@ package android.content {
     method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException;
     method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) throws android.os.RemoteException;
     method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal) throws android.os.RemoteException;
-    method public boolean release();
+    method public deprecated boolean release();
     method public final android.net.Uri uncanonicalize(android.net.Uri) throws android.os.RemoteException;
     method public int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]) throws android.os.RemoteException;
   }
@@ -11198,7 +11199,7 @@ package android.drm {
     field public final int statusCode;
   }
 
-  public class DrmManagerClient {
+  public class DrmManagerClient implements java.lang.AutoCloseable {
     ctor public DrmManagerClient(android.content.Context);
     method public android.drm.DrmInfo acquireDrmInfo(android.drm.DrmInfoRequest);
     method public int acquireRights(android.drm.DrmInfoRequest);
@@ -11208,6 +11209,7 @@ package android.drm {
     method public int checkRightsStatus(android.net.Uri);
     method public int checkRightsStatus(java.lang.String, int);
     method public int checkRightsStatus(android.net.Uri, int);
+    method public void close();
     method public android.drm.DrmConvertedStatus closeConvertSession(int);
     method public android.drm.DrmConvertedStatus convertData(int, byte[]);
     method public java.lang.String[] getAvailableDrmEngines();
@@ -11221,7 +11223,7 @@ package android.drm {
     method public java.lang.String getOriginalMimeType(android.net.Uri);
     method public int openConvertSession(java.lang.String);
     method public int processDrmInfo(android.drm.DrmInfo);
-    method public void release();
+    method public deprecated void release();
     method public int removeAllRights();
     method public int removeRights(java.lang.String);
     method public int removeRights(android.net.Uri);
index 090644b..6606508 100644 (file)
@@ -4228,7 +4228,7 @@ package android.app {
     field public static final java.lang.String COLUMN_DESCRIPTION = "description";
     field public static final java.lang.String COLUMN_ID = "_id";
     field public static final java.lang.String COLUMN_LAST_MODIFIED_TIMESTAMP = "last_modified_timestamp";
-    field public static final java.lang.String COLUMN_LOCAL_FILENAME = "local_filename";
+    field public static final deprecated java.lang.String COLUMN_LOCAL_FILENAME = "local_filename";
     field public static final java.lang.String COLUMN_LOCAL_URI = "local_uri";
     field public static final java.lang.String COLUMN_MEDIAPROVIDER_URI = "mediaprovider_uri";
     field public static final java.lang.String COLUMN_MEDIA_TYPE = "media_type";
@@ -7568,11 +7568,12 @@ package android.content {
     method public abstract void writeDataToPipe(android.os.ParcelFileDescriptor, android.net.Uri, java.lang.String, android.os.Bundle, T);
   }
 
-  public class ContentProviderClient {
+  public class ContentProviderClient implements java.lang.AutoCloseable {
     method public android.content.ContentProviderResult[] applyBatch(java.util.ArrayList<android.content.ContentProviderOperation>) throws android.content.OperationApplicationException, android.os.RemoteException;
     method public int bulkInsert(android.net.Uri, android.content.ContentValues[]) throws android.os.RemoteException;
     method public android.os.Bundle call(java.lang.String, java.lang.String, android.os.Bundle) throws android.os.RemoteException;
     method public final android.net.Uri canonicalize(android.net.Uri) throws android.os.RemoteException;
+    method public void close();
     method public int delete(android.net.Uri, java.lang.String, java.lang.String[]) throws android.os.RemoteException;
     method public android.content.ContentProvider getLocalContentProvider();
     method public java.lang.String[] getStreamTypes(android.net.Uri, java.lang.String) throws android.os.RemoteException;
@@ -7586,7 +7587,7 @@ package android.content {
     method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException;
     method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) throws android.os.RemoteException;
     method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal) throws android.os.RemoteException;
-    method public boolean release();
+    method public deprecated boolean release();
     method public final android.net.Uri uncanonicalize(android.net.Uri) throws android.os.RemoteException;
     method public int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]) throws android.os.RemoteException;
   }
@@ -10844,7 +10845,7 @@ package android.drm {
     field public final int statusCode;
   }
 
-  public class DrmManagerClient {
+  public class DrmManagerClient implements java.lang.AutoCloseable {
     ctor public DrmManagerClient(android.content.Context);
     method public android.drm.DrmInfo acquireDrmInfo(android.drm.DrmInfoRequest);
     method public int acquireRights(android.drm.DrmInfoRequest);
@@ -10854,6 +10855,7 @@ package android.drm {
     method public int checkRightsStatus(android.net.Uri);
     method public int checkRightsStatus(java.lang.String, int);
     method public int checkRightsStatus(android.net.Uri, int);
+    method public void close();
     method public android.drm.DrmConvertedStatus closeConvertSession(int);
     method public android.drm.DrmConvertedStatus convertData(int, byte[]);
     method public java.lang.String[] getAvailableDrmEngines();
@@ -10867,7 +10869,7 @@ package android.drm {
     method public java.lang.String getOriginalMimeType(android.net.Uri);
     method public int openConvertSession(java.lang.String);
     method public int processDrmInfo(android.drm.DrmInfo);
-    method public void release();
+    method public deprecated void release();
     method public int removeAllRights();
     method public int removeRights(java.lang.String);
     method public int removeRights(android.net.Uri);
index fb0e79b..a9516d0 100644 (file)
@@ -27,6 +27,7 @@ import android.database.CursorWrapper;
 import android.net.ConnectivityManager;
 import android.net.NetworkPolicyManager;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
 import android.provider.Downloads;
@@ -105,8 +106,17 @@ public class DownloadManager {
     public final static String COLUMN_LOCAL_URI = "local_uri";
 
     /**
-     * The pathname of the file where the download is stored.
+     * Path to the downloaded file on disk.
+     * <p>
+     * Note that apps may not have filesystem permissions to directly access
+     * this path. Instead of trying to open this path directly, apps should use
+     * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain access.
+     *
+     * @deprecated apps should transition to using
+     *             {@link ContentResolver#openFileDescriptor(Uri, String)}
+     *             instead.
      */
+    @Deprecated
     public final static String COLUMN_LOCAL_FILENAME = "local_filename";
 
     /**
@@ -908,16 +918,19 @@ public class DownloadManager {
         }
     }
 
-    private ContentResolver mResolver;
-    private String mPackageName;
+    private final ContentResolver mResolver;
+    private final String mPackageName;
+    private final int mTargetSdkVersion;
+
     private Uri mBaseUri = Downloads.Impl.CONTENT_URI;
 
     /**
      * @hide
      */
-    public DownloadManager(ContentResolver resolver, String packageName) {
-        mResolver = resolver;
-        mPackageName = packageName;
+    public DownloadManager(Context context) {
+        mResolver = context.getContentResolver();
+        mPackageName = context.getPackageName();
+        mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
     }
 
     /**
@@ -997,7 +1010,7 @@ public class DownloadManager {
         if (underlyingCursor == null) {
             return null;
         }
-        return new CursorTranslator(underlyingCursor, mBaseUri);
+        return new CursorTranslator(underlyingCursor, mBaseUri, mTargetSdkVersion);
     }
 
     /**
@@ -1265,11 +1278,13 @@ public class DownloadManager {
      * underlying data.
      */
     private static class CursorTranslator extends CursorWrapper {
-        private Uri mBaseUri;
+        private final Uri mBaseUri;
+        private final int mTargetSdkVersion;
 
-        public CursorTranslator(Cursor cursor, Uri baseUri) {
+        public CursorTranslator(Cursor cursor, Uri baseUri, int targetSdkVersion) {
             super(cursor);
             mBaseUri = baseUri;
+            mTargetSdkVersion = targetSdkVersion;
         }
 
         @Override
@@ -1290,8 +1305,19 @@ public class DownloadManager {
 
         @Override
         public String getString(int columnIndex) {
-            return (getColumnName(columnIndex).equals(COLUMN_LOCAL_URI)) ? getLocalUri() :
-                    super.getString(columnIndex);
+            final String columnName = getColumnName(columnIndex);
+            switch (columnName) {
+                case COLUMN_LOCAL_URI:
+                    return getLocalUri();
+                case COLUMN_LOCAL_FILENAME:
+                    if (mTargetSdkVersion >= Build.VERSION_CODES.N) {
+                        throw new IllegalArgumentException(
+                                "COLUMN_LOCAL_FILENAME is deprecated;"
+                                        + " use ContentResolver.openFileDescriptor() instead");
+                    }
+                default:
+                    return super.getString(columnIndex);
+            }
         }
 
         private String getLocalUri() {
index 288a2cb..89d52f2 100644 (file)
@@ -247,7 +247,7 @@ final class SystemServiceRegistry {
                 new CachedServiceFetcher<DownloadManager>() {
             @Override
             public DownloadManager createService(ContextImpl ctx) {
-                return new DownloadManager(ctx.getContentResolver(), ctx.getPackageName());
+                return new DownloadManager(ctx);
             }});
 
         registerService(Context.BATTERY_SERVICE, BatteryManager.class,
index d12595f..dec0d91 100644 (file)
@@ -19,6 +19,7 @@ package android.content;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.res.AssetFileDescriptor;
+import android.database.CrossProcessCursorWrapper;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
@@ -32,27 +33,34 @@ import android.os.RemoteException;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
 import dalvik.system.CloseGuard;
 
 import java.io.FileNotFoundException;
 import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
- * The public interface object used to interact with a {@link ContentProvider}. This is obtained by
- * calling {@link ContentResolver#acquireContentProviderClient}. This object must be released
- * using {@link #release} in order to indicate to the system that the {@link ContentProvider} is
- * no longer needed and can be killed to free up resources.
- *
- * <p>Note that you should generally create a new ContentProviderClient instance
- * for each thread that will be performing operations.  Unlike
+ * The public interface object used to interact with a specific
+ * {@link ContentProvider}.
+ * <p>
+ * Instances can be obtained by calling
+ * {@link ContentResolver#acquireContentProviderClient} or
+ * {@link ContentResolver#acquireUnstableContentProviderClient}. Instances must
+ * be released using {@link #close()} in order to indicate to the system that
+ * the underlying {@link ContentProvider} is no longer needed and can be killed
+ * to free up resources.
+ * <p>
+ * Note that you should generally create a new ContentProviderClient instance
+ * for each thread that will be performing operations. Unlike
  * {@link ContentResolver}, the methods here such as {@link #query} and
- * {@link #openFile} are not thread safe -- you must not call
- * {@link #release()} on the ContentProviderClient those calls are made from
- * until you are finished with the data they have returned.
+ * {@link #openFile} are not thread safe -- you must not call {@link #close()}
+ * on the ContentProviderClient those calls are made from until you are finished
+ * with the data they have returned.
  */
-public class ContentProviderClient {
+public class ContentProviderClient implements AutoCloseable {
     private static final String TAG = "ContentProviderClient";
 
     @GuardedBy("ContentProviderClient.class")
@@ -63,22 +71,23 @@ public class ContentProviderClient {
     private final String mPackageName;
     private final boolean mStable;
 
-    private final CloseGuard mGuard = CloseGuard.get();
+    private final AtomicBoolean mClosed = new AtomicBoolean();
+    private final CloseGuard mCloseGuard = CloseGuard.get();
 
     private long mAnrTimeout;
     private NotRespondingRunnable mAnrRunnable;
 
-    private boolean mReleased;
-
     /** {@hide} */
-    ContentProviderClient(
+    @VisibleForTesting
+    public ContentProviderClient(
             ContentResolver contentResolver, IContentProvider contentProvider, boolean stable) {
         mContentResolver = contentResolver;
         mContentProvider = contentProvider;
         mPackageName = contentResolver.mPackageName;
+
         mStable = stable;
 
-        mGuard.open("release");
+        mCloseGuard.open("close");
     }
 
     /** {@hide} */
@@ -133,8 +142,9 @@ public class ContentProviderClient {
                 remoteCancellationSignal = mContentProvider.createCancellationSignal();
                 cancellationSignal.setRemote(remoteCancellationSignal);
             }
-            return mContentProvider.query(mPackageName, url, projection, selection, selectionArgs,
-                    sortOrder, remoteCancellationSignal);
+            final Cursor cursor = mContentProvider.query(mPackageName, url, projection, selection,
+                    selectionArgs, sortOrder, remoteCancellationSignal);
+            return new CursorWrapperInner(cursor);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
@@ -446,29 +456,42 @@ public class ContentProviderClient {
     }
 
     /**
-     * Call this to indicate to the system that the associated {@link ContentProvider} is no
-     * longer needed by this {@link ContentProviderClient}.
-     * @return true if this was release, false if it was already released
+     * Closes this client connection, indicating to the system that the
+     * underlying {@link ContentProvider} is no longer needed.
      */
+    @Override
+    public void close() {
+        closeInternal();
+    }
+
+    /**
+     * @deprecated replaced by {@link #close()}.
+     */
+    @Deprecated
     public boolean release() {
-        synchronized (this) {
-            if (mReleased) {
-                throw new IllegalStateException("Already released");
-            }
-            mReleased = true;
-            mGuard.close();
+        return closeInternal();
+    }
+
+    private boolean closeInternal() {
+        mCloseGuard.close();
+        if (mClosed.compareAndSet(false, true)) {
             if (mStable) {
                 return mContentResolver.releaseProvider(mContentProvider);
             } else {
                 return mContentResolver.releaseUnstableProvider(mContentProvider);
             }
+        } else {
+            return false;
         }
     }
 
     @Override
     protected void finalize() throws Throwable {
-        if (mGuard != null) {
-            mGuard.warnIfOpen();
+        try {
+            mCloseGuard.warnIfOpen();
+            close();
+        } finally {
+            super.finalize();
         }
     }
 
@@ -502,4 +525,29 @@ public class ContentProviderClient {
             mContentResolver.appNotRespondingViaProvider(mContentProvider);
         }
     }
+
+    private final class CursorWrapperInner extends CrossProcessCursorWrapper {
+        private final CloseGuard mCloseGuard = CloseGuard.get();
+
+        CursorWrapperInner(Cursor cursor) {
+            super(cursor);
+            mCloseGuard.open("close");
+        }
+
+        @Override
+        public void close() {
+            mCloseGuard.close();
+            super.close();
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            try {
+                mCloseGuard.warnIfOpen();
+                close();
+            } finally {
+                super.finalize();
+            }
+        }
+    }
 }
index 9d0ebc2..ce5d3b1 100644 (file)
@@ -47,11 +47,11 @@ import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
 
-import dalvik.system.CloseGuard;
-
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 
+import dalvik.system.CloseGuard;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -59,9 +59,9 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Random;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * This class provides applications access to the content model.
@@ -514,8 +514,9 @@ public abstract class ContentResolver {
             maybeLogQueryToEventLog(durationMillis, uri, projection, selection, sortOrder);
 
             // Wrap the cursor object into CursorWrapperInner object.
-            CursorWrapperInner wrapper = new CursorWrapperInner(qCursor,
-                    stableProvider != null ? stableProvider : acquireProvider(uri));
+            final IContentProvider provider = (stableProvider != null) ? stableProvider
+                    : acquireProvider(uri);
+            final CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, provider);
             stableProvider = null;
             qCursor = null;
             return wrapper;
@@ -2503,41 +2504,31 @@ public abstract class ContentResolver {
 
     private final class CursorWrapperInner extends CrossProcessCursorWrapper {
         private final IContentProvider mContentProvider;
-        public static final String TAG="CursorWrapperInner";
+        private final AtomicBoolean mProviderReleased = new AtomicBoolean();
 
         private final CloseGuard mCloseGuard = CloseGuard.get();
-        private boolean mProviderReleased;
 
-        CursorWrapperInner(Cursor cursor, IContentProvider icp) {
+        CursorWrapperInner(Cursor cursor, IContentProvider contentProvider) {
             super(cursor);
-            mContentProvider = icp;
+            mContentProvider = contentProvider;
             mCloseGuard.open("close");
         }
 
         @Override
         public void close() {
+            mCloseGuard.close();
             super.close();
-            ContentResolver.this.releaseProvider(mContentProvider);
-            mProviderReleased = true;
 
-            if (mCloseGuard != null) {
-                mCloseGuard.close();
+            if (mProviderReleased.compareAndSet(false, true)) {
+                ContentResolver.this.releaseProvider(mContentProvider);
             }
         }
 
         @Override
         protected void finalize() throws Throwable {
             try {
-                if (mCloseGuard != null) {
-                    mCloseGuard.warnIfOpen();
-                }
-
-                if (!mProviderReleased && mContentProvider != null) {
-                    // Even though we are using CloseGuard, log this anyway so that
-                    // application developers always see the message in the log.
-                    Log.w(TAG, "Cursor finalized without prior close()");
-                    ContentResolver.this.releaseProvider(mContentProvider);
-                }
+                mCloseGuard.warnIfOpen();
+                close();
             } finally {
                 super.finalize();
             }
@@ -2546,7 +2537,7 @@ public abstract class ContentResolver {
 
     private final class ParcelFileDescriptorInner extends ParcelFileDescriptor {
         private final IContentProvider mContentProvider;
-        private boolean mProviderReleased;
+        private final AtomicBoolean mProviderReleased = new AtomicBoolean();
 
         ParcelFileDescriptorInner(ParcelFileDescriptor pfd, IContentProvider icp) {
             super(pfd);
@@ -2555,9 +2546,8 @@ public abstract class ContentResolver {
 
         @Override
         public void releaseResources() {
-            if (!mProviderReleased) {
+            if (mProviderReleased.compareAndSet(false, true)) {
                 ContentResolver.this.releaseProvider(mContentProvider);
-                mProviderReleased = true;
             }
         }
     }
@@ -2584,7 +2574,9 @@ public abstract class ContentResolver {
 
     private static IContentService sContentService;
     private final Context mContext;
+
     final String mPackageName;
+
     private static final String TAG = "ContentResolver";
 
     /** @hide */
index 48b3c1a..89ac27c 100644 (file)
@@ -384,8 +384,14 @@ public final class MediaStore {
 
     public interface MediaColumns extends BaseColumns {
         /**
-         * The data stream for the file
-         * <P>Type: DATA STREAM</P>
+         * Path to the file on disk.
+         * <p>
+         * Note that apps may not have filesystem permissions to directly access
+         * this path. Instead of trying to open this path directly, apps should
+         * use {@link ContentResolver#openFileDescriptor(Uri, String)} to gain
+         * access.
+         * <p>
+         * Type: TEXT
          */
         public static final String DATA = "_data";
 
@@ -1149,8 +1155,15 @@ public final class MediaStore {
             public static final String DEFAULT_SORT_ORDER = "image_id ASC";
 
             /**
-             * The data stream for the thumbnail
-             * <P>Type: DATA STREAM</P>
+             * Path to the thumbnail file on disk.
+             * <p>
+             * Note that apps may not have filesystem permissions to directly
+             * access this path. Instead of trying to open this path directly,
+             * apps should use
+             * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain
+             * access.
+             * <p>
+             * Type: TEXT
              */
             public static final String DATA = "_data";
 
@@ -1596,8 +1609,15 @@ public final class MediaStore {
             public static final String NAME = "name";
 
             /**
-             * The data stream for the playlist file
-             * <P>Type: DATA STREAM</P>
+             * Path to the playlist file on disk.
+             * <p>
+             * Note that apps may not have filesystem permissions to directly
+             * access this path. Instead of trying to open this path directly,
+             * apps should use
+             * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain
+             * access.
+             * <p>
+             * Type: TEXT
              */
             public static final String DATA = "_data";
 
@@ -2192,8 +2212,15 @@ public final class MediaStore {
             public static final String DEFAULT_SORT_ORDER = "video_id ASC";
 
             /**
-             * The data stream for the thumbnail
-             * <P>Type: DATA STREAM</P>
+             * Path to the thumbnail file on disk.
+             * <p>
+             * Note that apps may not have filesystem permissions to directly
+             * access this path. Instead of trying to open this path directly,
+             * apps should use
+             * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain
+             * access.
+             * <p>
+             * Type: TEXT
              */
             public static final String DATA = "_data";
 
index c05ea2e..704f0ce 100644 (file)
@@ -38,13 +38,14 @@ import java.io.IOException;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * The main programming interface for the DRM framework. An application must instantiate this class
  * to access DRM agents through the DRM framework.
  *
  */
-public class DrmManagerClient {
+public class DrmManagerClient implements AutoCloseable {
     /**
      * Indicates that a request was successful or that no error occurred.
      */
@@ -61,6 +62,7 @@ public class DrmManagerClient {
     HandlerThread mEventThread;
     private static final String TAG = "DrmManagerClient";
 
+    private final AtomicBoolean mClosed = new AtomicBoolean();
     private final CloseGuard mCloseGuard = CloseGuard.get();
 
     static {
@@ -117,7 +119,6 @@ public class DrmManagerClient {
 
     private int mUniqueId;
     private long mNativeContext;
-    private volatile boolean mReleased;
     private Context mContext;
     private InfoHandler mInfoHandler;
     private EventHandler mEventHandler;
@@ -261,41 +262,47 @@ public class DrmManagerClient {
     @Override
     protected void finalize() throws Throwable {
         try {
-            if (mCloseGuard != null) {
-                mCloseGuard.warnIfOpen();
-            }
-            release();
+            mCloseGuard.warnIfOpen();
+            close();
         } finally {
             super.finalize();
         }
     }
 
     /**
-     * Releases resources associated with the current session of DrmManagerClient.
-     *
-     * It is considered good practice to call this method when the {@link DrmManagerClient} object
-     * is no longer needed in your application. After release() is called,
-     * {@link DrmManagerClient} is no longer usable since it has lost all of its required resource.
+     * Releases resources associated with the current session of
+     * DrmManagerClient. It is considered good practice to call this method when
+     * the {@link DrmManagerClient} object is no longer needed in your
+     * application. After this method is called, {@link DrmManagerClient} is no
+     * longer usable since it has lost all of its required resource.
      */
-    public void release() {
-        if (mReleased) return;
-        mReleased = true;
-
-        if (mEventHandler != null) {
-            mEventThread.quit();
-            mEventThread = null;
-        }
-        if (mInfoHandler != null) {
-            mInfoThread.quit();
-            mInfoThread = null;
-        }
-        mEventHandler = null;
-        mInfoHandler = null;
-        mOnEventListener = null;
-        mOnInfoListener = null;
-        mOnErrorListener = null;
-        _release(mUniqueId);
+    @Override
+    public void close() {
         mCloseGuard.close();
+        if (mClosed.compareAndSet(false, true)) {
+            if (mEventHandler != null) {
+                mEventThread.quit();
+                mEventThread = null;
+            }
+            if (mInfoHandler != null) {
+                mInfoThread.quit();
+                mInfoThread = null;
+            }
+            mEventHandler = null;
+            mInfoHandler = null;
+            mOnEventListener = null;
+            mOnInfoListener = null;
+            mOnErrorListener = null;
+            _release(mUniqueId);
+        }
+    }
+
+    /**
+     * @deprecated replaced by {@link #close()}.
+     */
+    @Deprecated
+    public void release() {
+        close();
     }
 
     /**
index 41b369d..dd06921 100644 (file)
@@ -16,8 +16,8 @@
 
 package android.media;
 
+import android.content.ContentProviderClient;
 import android.content.ContentValues;
-import android.content.IContentProvider;
 import android.net.Uri;
 import android.os.RemoteException;
 
@@ -37,13 +37,11 @@ public class MediaInserter {
     private final HashMap<Uri, List<ContentValues>> mPriorityRowMap =
             new HashMap<Uri, List<ContentValues>>();
 
-    private final IContentProvider mProvider;
-    private final String mPackageName;
+    private final ContentProviderClient mProvider;
     private final int mBufferSizePerUri;
 
-    public MediaInserter(IContentProvider provider, String packageName, int bufferSizePerUri) {
+    public MediaInserter(ContentProviderClient provider, int bufferSizePerUri) {
         mProvider = provider;
-        mPackageName = packageName;
         mBufferSizePerUri = bufferSizePerUri;
     }
 
@@ -90,7 +88,7 @@ public class MediaInserter {
         if (!list.isEmpty()) {
             ContentValues[] valuesArray = new ContentValues[list.size()];
             valuesArray = list.toArray(valuesArray);
-            mProvider.bulkInsert(mPackageName, tableUri, valuesArray);
+            mProvider.bulkInsert(tableUri, valuesArray);
             list.clear();
         }
     }
index 9ea6722..96c616b 100644 (file)
 
 package android.media;
 
-import org.xml.sax.Attributes;
-import org.xml.sax.ContentHandler;
-import org.xml.sax.SAXException;
-
+import android.content.ContentProviderClient;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.IContentProvider;
 import android.database.Cursor;
 import android.database.SQLException;
 import android.drm.DrmManagerClient;
@@ -50,6 +46,12 @@ import android.text.TextUtils;
 import android.util.Log;
 import android.util.Xml;
 
+import dalvik.system.CloseGuard;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileDescriptor;
@@ -61,6 +63,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Locale;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Internal service helper that no-one should use directly.
@@ -107,8 +110,7 @@ import java.util.Locale;
  *
  * {@hide}
  */
-public class MediaScanner
-{
+public class MediaScanner implements AutoCloseable {
     static {
         System.loadLibrary("media_jni");
         native_init();
@@ -302,21 +304,23 @@ public class MediaScanner
     };
 
     private long mNativeContext;
-    private Context mContext;
-    private String mPackageName;
-    private IContentProvider mMediaProvider;
-    private Uri mAudioUri;
-    private Uri mVideoUri;
-    private Uri mImagesUri;
-    private Uri mThumbsUri;
-    private Uri mPlaylistsUri;
-    private Uri mFilesUri;
-    private Uri mFilesUriNoNotify;
-    private boolean mProcessPlaylists, mProcessGenres;
+    private final Context mContext;
+    private final String mPackageName;
+    private final String mVolumeName;
+    private final ContentProviderClient mMediaProvider;
+    private final Uri mAudioUri;
+    private final Uri mVideoUri;
+    private final Uri mImagesUri;
+    private final Uri mThumbsUri;
+    private final Uri mPlaylistsUri;
+    private final Uri mFilesUri;
+    private final Uri mFilesUriNoNotify;
+    private final boolean mProcessPlaylists;
+    private final boolean mProcessGenres;
     private int mMtpObjectHandle;
 
-    private final String mExternalStoragePath;
-    private final boolean mExternalIsEmulated;
+    private final AtomicBoolean mClosed = new AtomicBoolean();
+    private final CloseGuard mCloseGuard = CloseGuard.get();
 
     /** whether to use bulk inserts or individual inserts for each item */
     private static final boolean ENABLE_BULK_INSERTS = true;
@@ -345,10 +349,6 @@ public class MediaScanner
      */
     private static final String DEFAULT_RINGTONE_PROPERTY_PREFIX = "ro.config.";
 
-    // set to true if file path comparisons should be case insensitive.
-    // this should be set when scanning files on a case insensitive file system.
-    private boolean mCaseInsensitivePaths;
-
     private final BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options();
 
     private static class FileEntry {
@@ -378,26 +378,59 @@ public class MediaScanner
         int bestmatchlevel;
     }
 
-    private ArrayList<PlaylistEntry> mPlaylistEntries = new ArrayList<PlaylistEntry>();
+    private final ArrayList<PlaylistEntry> mPlaylistEntries = new ArrayList<>();
+    private final ArrayList<FileEntry> mPlayLists = new ArrayList<>();
 
     private MediaInserter mMediaInserter;
 
-    private ArrayList<FileEntry> mPlayLists;
-
     private DrmManagerClient mDrmManagerClient = null;
 
-    public MediaScanner(Context c) {
+    public MediaScanner(Context c, String volumeName) {
         native_setup();
         mContext = c;
         mPackageName = c.getPackageName();
+        mVolumeName = volumeName;
+
         mBitmapOptions.inSampleSize = 1;
         mBitmapOptions.inJustDecodeBounds = true;
 
         setDefaultRingtoneFileNames();
 
-        mExternalStoragePath = Environment.getExternalStorageDirectory().getAbsolutePath();
-        mExternalIsEmulated = Environment.isExternalStorageEmulated();
-        //mClient.testGenreNameConverter();
+        mMediaProvider = mContext.getContentResolver()
+                .acquireContentProviderClient(MediaStore.AUTHORITY);
+
+        mAudioUri = Audio.Media.getContentUri(volumeName);
+        mVideoUri = Video.Media.getContentUri(volumeName);
+        mImagesUri = Images.Media.getContentUri(volumeName);
+        mThumbsUri = Images.Thumbnails.getContentUri(volumeName);
+        mFilesUri = Files.getContentUri(volumeName);
+        mFilesUriNoNotify = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build();
+
+        if (!volumeName.equals("internal")) {
+            // we only support playlists on external media
+            mProcessPlaylists = true;
+            mProcessGenres = true;
+            mPlaylistsUri = Playlists.getContentUri(volumeName);
+        } else {
+            mProcessPlaylists = false;
+            mProcessGenres = false;
+            mPlaylistsUri = null;
+        }
+
+        final Locale locale = mContext.getResources().getConfiguration().locale;
+        if (locale != null) {
+            String language = locale.getLanguage();
+            String country = locale.getCountry();
+            if (language != null) {
+                if (country != null) {
+                    setLocale(language + "_" + country);
+                } else {
+                    setLocale(language);
+                }
+            }
+        }
+
+        mCloseGuard.open("close");
     }
 
     private void setDefaultRingtoneFileNames() {
@@ -956,7 +989,7 @@ public class MediaScanner
                     if (inserter != null) {
                         inserter.flushAll();
                     }
-                    result = mMediaProvider.insert(mPackageName, tableUri, values);
+                    result = mMediaProvider.insert(tableUri, values);
                 } else if (entry.mFormat == MtpConstants.FORMAT_ASSOCIATION) {
                     inserter.insertwithPriority(tableUri, values);
                 } else {
@@ -988,7 +1021,7 @@ public class MediaScanner
                     }
                     values.put(FileColumns.MEDIA_TYPE, mediaType);
                 }
-                mMediaProvider.update(mPackageName, result, values, null, null);
+                mMediaProvider.update(result, values, null, null);
             }
 
             if(needToSetSettings) {
@@ -1055,11 +1088,7 @@ public class MediaScanner
         String where = null;
         String[] selectionArgs = null;
 
-        if (mPlayLists == null) {
-            mPlayLists = new ArrayList<FileEntry>();
-        } else {
-            mPlayLists.clear();
-        }
+        mPlayLists.clear();
 
         if (filePath != null) {
             // query for only one file
@@ -1077,8 +1106,7 @@ public class MediaScanner
         // filesystem is mounted and unmounted while the scanner is running).
         Uri.Builder builder = mFilesUri.buildUpon();
         builder.appendQueryParameter(MediaStore.PARAM_DELETE_DATA, "false");
-        MediaBulkDeleter deleter = new MediaBulkDeleter(mMediaProvider, mPackageName,
-                builder.build());
+        MediaBulkDeleter deleter = new MediaBulkDeleter(mMediaProvider, builder.build());
 
         // Build the list of files from the content provider
         try {
@@ -1097,7 +1125,7 @@ public class MediaScanner
                         c.close();
                         c = null;
                     }
-                    c = mMediaProvider.query(mPackageName, limitUri, FILES_PRESCAN_PROJECTION,
+                    c = mMediaProvider.query(limitUri, FILES_PRESCAN_PROJECTION,
                             where, selectionArgs, MediaStore.Files.FileColumns._ID, null);
                     if (c == null) {
                         break;
@@ -1138,8 +1166,7 @@ public class MediaScanner
                                     if (path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
                                         deleter.flush();
                                         String parent = new File(path).getParent();
-                                        mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL,
-                                                parent, null);
+                                        mMediaProvider.call(MediaStore.UNHIDE_CALL, parent, null);
                                     }
                                 }
                             }
@@ -1157,7 +1184,7 @@ public class MediaScanner
 
         // compute original size of images
         mOriginalCount = 0;
-        c = mMediaProvider.query(mPackageName, mImagesUri, ID_PROJECTION, null, null, null, null);
+        c = mMediaProvider.query(mImagesUri, ID_PROJECTION, null, null, null, null);
         if (c != null) {
             mOriginalCount = c.getCount();
             c.close();
@@ -1189,7 +1216,6 @@ public class MediaScanner
 
         try {
             c = mMediaProvider.query(
-                    mPackageName,
                     mThumbsUri,
                     new String [] { "_data" },
                     null,
@@ -1225,13 +1251,11 @@ public class MediaScanner
     static class MediaBulkDeleter {
         StringBuilder whereClause = new StringBuilder();
         ArrayList<String> whereArgs = new ArrayList<String>(100);
-        final IContentProvider mProvider;
-        final String mPackageName;
+        final ContentProviderClient mProvider;
         final Uri mBaseUri;
 
-        public MediaBulkDeleter(IContentProvider provider, String packageName, Uri baseUri) {
+        public MediaBulkDeleter(ContentProviderClient provider, Uri baseUri) {
             mProvider = provider;
-            mPackageName = packageName;
             mBaseUri = baseUri;
         }
 
@@ -1250,7 +1274,7 @@ public class MediaScanner
             if (size > 0) {
                 String [] foo = new String [size];
                 foo = whereArgs.toArray(foo);
-                int numrows = mProvider.delete(mPackageName, mBaseUri,
+                int numrows = mProvider.delete(mBaseUri,
                         MediaStore.MediaColumns._ID + " IN (" +
                         whereClause.toString() + ")", foo);
                 //Log.i("@@@@@@@@@", "rows deleted: " + numrows);
@@ -1271,48 +1295,26 @@ public class MediaScanner
             pruneDeadThumbnailFiles();
 
         // allow GC to clean up
-        mPlayLists = null;
-        mMediaProvider = null;
+        mPlayLists.clear();
     }
 
     private void releaseResources() {
         // release the DrmManagerClient resources
         if (mDrmManagerClient != null) {
-            mDrmManagerClient.release();
+            mDrmManagerClient.close();
             mDrmManagerClient = null;
         }
     }
 
-    private void initialize(String volumeName) {
-        mMediaProvider = mContext.getContentResolver().acquireProvider("media");
-
-        mAudioUri = Audio.Media.getContentUri(volumeName);
-        mVideoUri = Video.Media.getContentUri(volumeName);
-        mImagesUri = Images.Media.getContentUri(volumeName);
-        mThumbsUri = Images.Thumbnails.getContentUri(volumeName);
-        mFilesUri = Files.getContentUri(volumeName);
-        mFilesUriNoNotify = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build();
-
-        if (!volumeName.equals("internal")) {
-            // we only support playlists on external media
-            mProcessPlaylists = true;
-            mProcessGenres = true;
-            mPlaylistsUri = Playlists.getContentUri(volumeName);
-
-            mCaseInsensitivePaths = true;
-        }
-    }
-
-    public void scanDirectories(String[] directories, String volumeName) {
+    public void scanDirectories(String[] directories) {
         try {
             long start = System.currentTimeMillis();
-            initialize(volumeName);
             prescan(null, true);
             long prescan = System.currentTimeMillis();
 
             if (ENABLE_BULK_INSERTS) {
                 // create MediaInserter for bulk inserts
-                mMediaInserter = new MediaInserter(mMediaProvider, mPackageName, 500);
+                mMediaInserter = new MediaInserter(mMediaProvider, 500);
             }
 
             for (int i = 0; i < directories.length; i++) {
@@ -1349,9 +1351,8 @@ public class MediaScanner
     }
 
     // this function is used to scan a single file
-    public Uri scanSingleFile(String path, String volumeName, String mimeType) {
+    public Uri scanSingleFile(String path, String mimeType) {
         try {
-            initialize(volumeName);
             prescan(path, true);
 
             File file = new File(path);
@@ -1464,8 +1465,7 @@ public class MediaScanner
         return isNoMediaFile(path);
     }
 
-    public void scanMtpFile(String path, String volumeName, int objectHandle, int format) {
-        initialize(volumeName);
+    public void scanMtpFile(String path, int objectHandle, int format) {
         MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);
         int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType);
         File file = new File(path);
@@ -1481,7 +1481,7 @@ public class MediaScanner
             values.put(Files.FileColumns.DATE_MODIFIED, lastModifiedSeconds);
             try {
                 String[] whereArgs = new String[] {  Integer.toString(objectHandle) };
-                mMediaProvider.update(mPackageName, Files.getMtpObjectsUri(volumeName), values,
+                mMediaProvider.update(Files.getMtpObjectsUri(mVolumeName), values,
                         "_id=?", whereArgs);
             } catch (RemoteException e) {
                 Log.e(TAG, "RemoteException in scanMtpFile", e);
@@ -1498,7 +1498,7 @@ public class MediaScanner
 
                 FileEntry entry = makeEntryFor(path);
                 if (entry != null) {
-                    fileList = mMediaProvider.query(mPackageName, mFilesUri,
+                    fileList = mMediaProvider.query(mFilesUri,
                             FILES_PRESCAN_PROJECTION, null, null, null, null);
                     processPlayList(entry, fileList);
                 }
@@ -1529,7 +1529,7 @@ public class MediaScanner
         try {
             where = Files.FileColumns.DATA + "=?";
             selectionArgs = new String[] { path };
-            c = mMediaProvider.query(mPackageName, mFilesUriNoNotify, FILES_PRESCAN_PROJECTION,
+            c = mMediaProvider.query(mFilesUriNoNotify, FILES_PRESCAN_PROJECTION,
                     where, selectionArgs, null, null);
             if (c.moveToFirst()) {
                 long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
@@ -1641,7 +1641,7 @@ public class MediaScanner
                     values.clear();
                     values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, Integer.valueOf(index));
                     values.put(MediaStore.Audio.Playlists.Members.AUDIO_ID, Long.valueOf(entry.bestmatchid));
-                    mMediaProvider.insert(mPackageName, playlistUri, values);
+                    mMediaProvider.insert(playlistUri, values);
                     index++;
                 } catch (RemoteException e) {
                     Log.e(TAG, "RemoteException in MediaScanner.processCachedPlaylist()", e);
@@ -1806,16 +1806,16 @@ public class MediaScanner
 
         if (rowId == 0) {
             values.put(MediaStore.Audio.Playlists.DATA, path);
-            uri = mMediaProvider.insert(mPackageName, mPlaylistsUri, values);
+            uri = mMediaProvider.insert(mPlaylistsUri, values);
             rowId = ContentUris.parseId(uri);
             membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY);
         } else {
             uri = ContentUris.withAppendedId(mPlaylistsUri, rowId);
-            mMediaProvider.update(mPackageName, uri, values, null, null);
+            mMediaProvider.update(uri, values, null, null);
 
             // delete members of existing playlist
             membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY);
-            mMediaProvider.delete(mPackageName, membersUri, null, null);
+            mMediaProvider.delete(membersUri, null, null);
         }
 
         String playListDirectory = path.substring(0, lastSlash + 1);
@@ -1837,7 +1837,7 @@ public class MediaScanner
         try {
             // use the files uri and projection because we need the format column,
             // but restrict the query to just audio files
-            fileList = mMediaProvider.query(mPackageName, mFilesUri, FILES_PRESCAN_PROJECTION,
+            fileList = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION,
                     "media_type=2", null, null, null);
             while (iterator.hasNext()) {
                 FileEntry entry = iterator.next();
@@ -1856,7 +1856,7 @@ public class MediaScanner
 
     private native void processDirectory(String path, MediaScannerClient client);
     private native void processFile(String path, String mimeType, MediaScannerClient client);
-    public native void setLocale(String locale);
+    private native void setLocale(String locale);
 
     public native byte[] extractAlbumArt(FileDescriptor fd);
 
@@ -1864,19 +1864,22 @@ public class MediaScanner
     private native final void native_setup();
     private native final void native_finalize();
 
-    /**
-     * Releases resources associated with this MediaScanner object.
-     * It is considered good practice to call this method when
-     * one is done using the MediaScanner object. After this method
-     * is called, the MediaScanner object can no longer be used.
-     */
-    public void release() {
-        native_finalize();
+    @Override
+    public void close() {
+        mCloseGuard.close();
+        if (mClosed.compareAndSet(false, true)) {
+            mMediaProvider.close();
+            native_finalize();
+        }
     }
 
     @Override
-    protected void finalize() {
-        mContext.getContentResolver().releaseProvider(mMediaProvider);
-        native_finalize();
+    protected void finalize() throws Throwable {
+        try {
+            mCloseGuard.warnIfOpen();
+            close();
+        } finally {
+            super.finalize();
+        }
     }
 }
index 3541fba..bc96e2e 100755 (executable)
@@ -17,9 +17,9 @@
 package android.mtp;
 
 import android.content.BroadcastReceiver;
-import android.content.Context;
+import android.content.ContentProviderClient;
 import android.content.ContentValues;
-import android.content.IContentProvider;
+import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
@@ -37,23 +37,30 @@ import android.util.Log;
 import android.view.Display;
 import android.view.WindowManager;
 
+import dalvik.system.CloseGuard;
+
 import java.io.File;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Locale;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * {@hide}
  */
-public class MtpDatabase {
-
+public class MtpDatabase implements AutoCloseable {
     private static final String TAG = "MtpDatabase";
 
     private final Context mContext;
     private final String mPackageName;
-    private final IContentProvider mMediaProvider;
+    private final ContentProviderClient mMediaProvider;
     private final String mVolumeName;
     private final Uri mObjectsUri;
+    private final MediaScanner mMediaScanner;
+
+    private final AtomicBoolean mClosed = new AtomicBoolean();
+    private final CloseGuard mCloseGuard = CloseGuard.get();
+
     // path to primary storage
     private final String mMediaStoragePath;
     // if not null, restrict all queries to these subdirectories
@@ -120,7 +127,6 @@ public class MtpDatabase {
     private static final String STORAGE_FORMAT_PARENT_WHERE = STORAGE_FORMAT_WHERE + " AND "
                                             + Files.FileColumns.PARENT + "=?";
 
-    private final MediaScanner mMediaScanner;
     private MtpServer mServer;
 
     // read from native code
@@ -156,11 +162,12 @@ public class MtpDatabase {
 
         mContext = context;
         mPackageName = context.getPackageName();
-        mMediaProvider = context.getContentResolver().acquireProvider("media");
+        mMediaProvider = context.getContentResolver()
+                .acquireContentProviderClient(MediaStore.AUTHORITY);
         mVolumeName = volumeName;
         mMediaStoragePath = storagePath;
         mObjectsUri = Files.getMtpObjectsUri(volumeName);
-        mMediaScanner = new MediaScanner(context);
+        mMediaScanner = new MediaScanner(context, mVolumeName);
 
         mSubDirectories = subDirectories;
         if (subDirectories != null) {
@@ -187,20 +194,9 @@ public class MtpDatabase {
             }
         }
 
-        // Set locale to MediaScanner.
-        Locale locale = context.getResources().getConfiguration().locale;
-        if (locale != null) {
-            String language = locale.getLanguage();
-            String country = locale.getCountry();
-            if (language != null) {
-                if (country != null) {
-                    mMediaScanner.setLocale(language + "_" + country);
-                } else {
-                    mMediaScanner.setLocale(language);
-                }
-            }
-        }
         initDeviceProperties(context);
+
+        mCloseGuard.open("close");
     }
 
     public void setServer(MtpServer server) {
@@ -221,9 +217,20 @@ public class MtpDatabase {
     }
 
     @Override
+    public void close() {
+        mCloseGuard.close();
+        if (mClosed.compareAndSet(false, true)) {
+            mMediaScanner.close();
+            mMediaProvider.close();
+            native_finalize();
+        }
+    }
+
+    @Override
     protected void finalize() throws Throwable {
         try {
-            native_finalize();
+            mCloseGuard.warnIfOpen();
+            close();
         } finally {
             super.finalize();
         }
@@ -334,7 +341,7 @@ public class MtpDatabase {
         if (path != null) {
             Cursor c = null;
             try {
-                c = mMediaProvider.query(mPackageName, mObjectsUri, ID_PROJECTION, PATH_WHERE,
+                c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, PATH_WHERE,
                         new String[] { path }, null, null);
                 if (c != null && c.getCount() > 0) {
                     Log.w(TAG, "file already exists in beginSendObject: " + path);
@@ -359,7 +366,7 @@ public class MtpDatabase {
         values.put(Files.FileColumns.DATE_MODIFIED, modified);
 
         try {
-            Uri uri = mMediaProvider.insert(mPackageName, mObjectsUri, values);
+            Uri uri = mMediaProvider.insert(mObjectsUri, values);
             if (uri != null) {
                 return Integer.parseInt(uri.getPathSegments().get(2));
             } else {
@@ -394,13 +401,13 @@ public class MtpDatabase {
                 values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
                 values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
                 try {
-                    Uri uri = mMediaProvider.insert(mPackageName,
+                    Uri uri = mMediaProvider.insert(
                             Audio.Playlists.EXTERNAL_CONTENT_URI, values);
                 } catch (RemoteException e) {
                     Log.e(TAG, "RemoteException in endSendObject", e);
                 }
             } else {
-                mMediaScanner.scanMtpFile(path, mVolumeName, handle, format);
+                mMediaScanner.scanMtpFile(path, handle, format);
             }
         } else {
             deleteFile(handle);
@@ -503,7 +510,7 @@ public class MtpDatabase {
             }
         }
 
-        return mMediaProvider.query(mPackageName, mObjectsUri, ID_PROJECTION, where,
+        return mMediaProvider.query(mObjectsUri, ID_PROJECTION, where,
                 whereArgs, null, null);
     }
 
@@ -721,7 +728,7 @@ public class MtpDatabase {
              propertyGroup = mPropertyGroupsByFormat.get(format);
              if (propertyGroup == null) {
                 int[] propertyList = getSupportedObjectProperties(format);
-                propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mPackageName,
+                propertyGroup = new MtpPropertyGroup(this, mMediaProvider,
                         mVolumeName, propertyList);
                 mPropertyGroupsByFormat.put(new Integer(format), propertyGroup);
             }
@@ -729,7 +736,7 @@ public class MtpDatabase {
               propertyGroup = mPropertyGroupsByProperty.get(property);
              if (propertyGroup == null) {
                 int[] propertyList = new int[] { (int)property };
-                propertyGroup = new MtpPropertyGroup(this, mMediaProvider, mPackageName,
+                propertyGroup = new MtpPropertyGroup(this, mMediaProvider,
                         mVolumeName, propertyList);
                 mPropertyGroupsByProperty.put(new Integer((int)property), propertyGroup);
             }
@@ -745,7 +752,7 @@ public class MtpDatabase {
         String path = null;
         String[] whereArgs = new String[] {  Integer.toString(handle) };
         try {
-            c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_PROJECTION, ID_WHERE,
+            c = mMediaProvider.query(mObjectsUri, PATH_PROJECTION, ID_WHERE,
                     whereArgs, null, null);
             if (c != null && c.moveToNext()) {
                 path = c.getString(1);
@@ -788,7 +795,7 @@ public class MtpDatabase {
         try {
             // note - we are relying on a special case in MediaProvider.update() to update
             // the paths for all children in the case where this is a directory.
-            updated = mMediaProvider.update(mPackageName, mObjectsUri, values, ID_WHERE, whereArgs);
+            updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs);
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException in mMediaProvider.update", e);
         }
@@ -805,7 +812,7 @@ public class MtpDatabase {
             if (oldFile.getName().startsWith(".") && !newPath.startsWith(".")) {
                 // directory was unhidden
                 try {
-                    mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, newPath, null);
+                    mMediaProvider.call(MediaStore.UNHIDE_CALL, newPath, null);
                 } catch (RemoteException e) {
                     Log.e(TAG, "failed to unhide/rescan for " + newPath);
                 }
@@ -815,7 +822,7 @@ public class MtpDatabase {
             if (oldFile.getName().toLowerCase(Locale.US).equals(".nomedia")
                     && !newPath.toLowerCase(Locale.US).equals(".nomedia")) {
                 try {
-                    mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, oldFile.getParent(), null);
+                    mMediaProvider.call(MediaStore.UNHIDE_CALL, oldFile.getParent(), null);
                 } catch (RemoteException e) {
                     Log.e(TAG, "failed to unhide/rescan for " + newPath);
                 }
@@ -886,7 +893,7 @@ public class MtpDatabase {
                         char[] outName, long[] outCreatedModified) {
         Cursor c = null;
         try {
-            c = mMediaProvider.query(mPackageName, mObjectsUri, OBJECT_INFO_PROJECTION,
+            c = mMediaProvider.query(mObjectsUri, OBJECT_INFO_PROJECTION,
                             ID_WHERE, new String[] {  Integer.toString(handle) }, null, null);
             if (c != null && c.moveToNext()) {
                 outStorageFormatParent[0] = c.getInt(1);
@@ -933,7 +940,7 @@ public class MtpDatabase {
         }
         Cursor c = null;
         try {
-            c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_FORMAT_PROJECTION,
+            c = mMediaProvider.query(mObjectsUri, PATH_FORMAT_PROJECTION,
                             ID_WHERE, new String[] {  Integer.toString(handle) }, null, null);
             if (c != null && c.moveToNext()) {
                 String path = c.getString(1);
@@ -960,7 +967,7 @@ public class MtpDatabase {
     private int getObjectFormat(int handle) {
         Cursor c = null;
         try {
-            c = mMediaProvider.query(mPackageName, mObjectsUri, FORMAT_PROJECTION,
+            c = mMediaProvider.query(mObjectsUri, FORMAT_PROJECTION,
                             ID_WHERE, new String[] {  Integer.toString(handle) }, null, null);
             if (c != null && c.moveToNext()) {
                 return c.getInt(1);
@@ -984,7 +991,7 @@ public class MtpDatabase {
 
         Cursor c = null;
         try {
-            c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_FORMAT_PROJECTION,
+            c = mMediaProvider.query(mObjectsUri, PATH_FORMAT_PROJECTION,
                             ID_WHERE, new String[] {  Integer.toString(handle) }, null, null);
             if (c != null && c.moveToNext()) {
                 // don't convert to media path here, since we will be matching
@@ -1007,7 +1014,7 @@ public class MtpDatabase {
             if (format == MtpConstants.FORMAT_ASSOCIATION) {
                 // recursive case - delete all children first
                 Uri uri = Files.getMtpObjectsUri(mVolumeName);
-                int count = mMediaProvider.delete(mPackageName, uri,
+                int count = mMediaProvider.delete(uri,
                     // the 'like' makes it use the index, the 'lower()' makes it correct
                     // when the path contains sqlite wildcard characters
                     "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
@@ -1015,12 +1022,12 @@ public class MtpDatabase {
             }
 
             Uri uri = Files.getMtpObjectsUri(mVolumeName, handle);
-            if (mMediaProvider.delete(mPackageName, uri, null, null) > 0) {
+            if (mMediaProvider.delete(uri, null, null) > 0) {
                 if (format != MtpConstants.FORMAT_ASSOCIATION
                         && path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
                     try {
                         String parentPath = path.substring(0, path.lastIndexOf("/"));
-                        mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL, parentPath, null);
+                        mMediaProvider.call(MediaStore.UNHIDE_CALL, parentPath, null);
                     } catch (RemoteException e) {
                         Log.e(TAG, "failed to unhide/rescan for " + path);
                     }
@@ -1043,7 +1050,7 @@ public class MtpDatabase {
         Uri uri = Files.getMtpReferencesUri(mVolumeName, handle);
         Cursor c = null;
         try {
-            c = mMediaProvider.query(mPackageName, uri, ID_PROJECTION, null, null, null, null);
+            c = mMediaProvider.query(uri, ID_PROJECTION, null, null, null, null);
             if (c == null) {
                 return null;
             }
@@ -1077,7 +1084,7 @@ public class MtpDatabase {
             valuesList[i] = values;
         }
         try {
-            if (mMediaProvider.bulkInsert(mPackageName, uri, valuesList) > 0) {
+            if (mMediaProvider.bulkInsert(uri, valuesList) > 0) {
                 return MtpConstants.RESPONSE_OK;
             }
         } catch (RemoteException e) {
index c80adfa..dea3008 100644 (file)
@@ -16,7 +16,7 @@
 
 package android.mtp;
 
-import android.content.IContentProvider;
+import android.content.ContentProviderClient;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.RemoteException;
@@ -48,8 +48,7 @@ class MtpPropertyGroup {
     }
 
     private final MtpDatabase mDatabase;
-    private final IContentProvider mProvider;
-    private final String mPackageName;
+    private final ContentProviderClient mProvider;
     private final String mVolumeName;
     private final Uri mUri;
 
@@ -65,13 +64,12 @@ class MtpPropertyGroup {
     private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?";
     private static final String PARENT_FORMAT_WHERE = PARENT_WHERE + " AND " + FORMAT_WHERE;
     // constructs a property group for a list of properties
-    public MtpPropertyGroup(MtpDatabase database, IContentProvider provider, String packageName,
-            String volume, int[] properties) {
+    public MtpPropertyGroup(MtpDatabase database, ContentProviderClient provider, String volumeName,
+            int[] properties) {
         mDatabase = database;
         mProvider = provider;
-        mPackageName = packageName;
-        mVolumeName = volume;
-        mUri = Files.getMtpObjectsUri(volume);
+        mVolumeName = volumeName;
+        mUri = Files.getMtpObjectsUri(volumeName);
 
         int count = properties.length;
         ArrayList<String> columns = new ArrayList<String>(count);
@@ -201,7 +199,7 @@ class MtpPropertyGroup {
         Cursor c = null;
         try {
             // for now we are only reading properties from the "objects" table
-            c = mProvider.query(mPackageName, mUri,
+            c = mProvider.query(mUri,
                             new String [] { Files.FileColumns._ID, column },
                             ID_WHERE, new String[] { Integer.toString(id) }, null, null);
             if (c != null && c.moveToNext()) {
@@ -221,7 +219,7 @@ class MtpPropertyGroup {
     private String queryAudio(int id, String column) {
         Cursor c = null;
         try {
-            c = mProvider.query(mPackageName, Audio.Media.getContentUri(mVolumeName),
+            c = mProvider.query(Audio.Media.getContentUri(mVolumeName),
                             new String [] { Files.FileColumns._ID, column },
                             ID_WHERE, new String[] { Integer.toString(id) }, null, null);
             if (c != null && c.moveToNext()) {
@@ -242,7 +240,7 @@ class MtpPropertyGroup {
         Cursor c = null;
         try {
             Uri uri = Audio.Genres.getContentUriForAudioId(mVolumeName, id);
-            c = mProvider.query(mPackageName, uri,
+            c = mProvider.query(uri,
                             new String [] { Files.FileColumns._ID, Audio.GenresColumns.NAME },
                             null, null, null, null);
             if (c != null && c.moveToNext()) {
@@ -264,7 +262,7 @@ class MtpPropertyGroup {
         Cursor c = null;
         try {
             // for now we are only reading properties from the "objects" table
-            c = mProvider.query(mPackageName, mUri,
+            c = mProvider.query(mUri,
                             new String [] { Files.FileColumns._ID, column },
                             ID_WHERE, new String[] { Integer.toString(id) }, null, null);
             if (c != null && c.moveToNext()) {
@@ -335,7 +333,7 @@ class MtpPropertyGroup {
         try {
             // don't query if not necessary
             if (depth > 0 || handle == 0xFFFFFFFF || mColumns.length > 1) {
-                c = mProvider.query(mPackageName, mUri, mColumns, where, whereArgs, null, null);
+                c = mProvider.query(mUri, mColumns, where, whereArgs, null, null);
                 if (c == null) {
                     return new MtpPropertyList(0, MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE);
                 }
index 05df014..06efa90 100644 (file)
@@ -16,6 +16,7 @@
 
 package com.android.mediaframeworktest.unit;
 
+import android.content.ContentProviderClient;
 import android.content.ContentValues;
 import android.content.IContentProvider;
 import android.media.MediaInserter;
@@ -81,8 +82,9 @@ public class MediaInserterTest extends InstrumentationTestCase {
     protected void setUp() throws Exception {
         super.setUp();
         mMockProvider = EasyMock.createMock(IContentProvider.class);
-        mMediaInserter = new MediaInserter(mMockProvider,
-        mPackageName, TEST_BUFFER_SIZE);
+        final ContentProviderClient client = new ContentProviderClient(
+                getInstrumentation().getContext().getContentResolver(), mMockProvider, true);
+        mMediaInserter = new MediaInserter(client, TEST_BUFFER_SIZE);
         mPackageName = getInstrumentation().getContext().getPackageName();
         mFilesCounter = 0;
         mAudioCounter = 0;