diff options
-rw-r--r-- | api/current.txt | 12 | ||||
-rw-r--r-- | api/system-current.txt | 12 | ||||
-rw-r--r-- | api/test-current.txt | 12 | ||||
-rw-r--r-- | core/java/android/app/DownloadManager.java | 48 | ||||
-rw-r--r-- | core/java/android/app/SystemServiceRegistry.java | 2 | ||||
-rw-r--r-- | core/java/android/content/ContentProviderClient.java | 106 | ||||
-rw-r--r-- | core/java/android/content/ContentResolver.java | 44 | ||||
-rw-r--r-- | core/java/android/provider/MediaStore.java | 43 | ||||
-rw-r--r-- | drm/java/android/drm/DrmManagerClient.java | 65 | ||||
-rw-r--r-- | media/java/android/media/MediaInserter.java | 10 | ||||
-rw-r--r-- | media/java/android/media/MediaScanner.java | 203 | ||||
-rwxr-xr-x | media/java/android/mtp/MtpDatabase.java | 91 | ||||
-rw-r--r-- | media/java/android/mtp/MtpPropertyGroup.java | 24 | ||||
-rw-r--r-- | media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java | 6 |
14 files changed, 396 insertions, 282 deletions
diff --git a/api/current.txt b/api/current.txt index 94bb21a4b362..53db7b2d9ea7 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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); diff --git a/api/system-current.txt b/api/system-current.txt index 79e44ea73727..967e6c800ea1 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -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); diff --git a/api/test-current.txt b/api/test-current.txt index 090644bd3dc6..66065083e3cf 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -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); diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java index fb0e79b849b2..a9516d03ba0d 100644 --- a/core/java/android/app/DownloadManager.java +++ b/core/java/android/app/DownloadManager.java @@ -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() { diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 288a2cb5fa4f..89d52f248bde 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -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, diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java index d12595f7ac07..dec0d9107d3a 100644 --- a/core/java/android/content/ContentProviderClient.java +++ b/core/java/android/content/ContentProviderClient.java @@ -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(); + } + } + } } diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 9d0ebc20bdfb..ce5d3b1f5405 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -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 */ diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 48b3c1a1b064..89ac27c9a816 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -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"; diff --git a/drm/java/android/drm/DrmManagerClient.java b/drm/java/android/drm/DrmManagerClient.java index c05ea2e1a1e5..704f0ce7624b 100644 --- a/drm/java/android/drm/DrmManagerClient.java +++ b/drm/java/android/drm/DrmManagerClient.java @@ -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(); } /** diff --git a/media/java/android/media/MediaInserter.java b/media/java/android/media/MediaInserter.java index 41b369dc38ae..dd069218463a 100644 --- a/media/java/android/media/MediaInserter.java +++ b/media/java/android/media/MediaInserter.java @@ -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(); } } diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index 9ea672287d88..96c616be359d 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -16,14 +16,10 @@ 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(); + } } } diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java index 3541fba7f3b1..bc96e2ea8989 100755 --- a/media/java/android/mtp/MtpDatabase.java +++ b/media/java/android/mtp/MtpDatabase.java @@ -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) { diff --git a/media/java/android/mtp/MtpPropertyGroup.java b/media/java/android/mtp/MtpPropertyGroup.java index c80adfa80fc9..dea300838385 100644 --- a/media/java/android/mtp/MtpPropertyGroup.java +++ b/media/java/android/mtp/MtpPropertyGroup.java @@ -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); } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java index 05df014ad611..06efa906af04 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaInserterTest.java @@ -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; |