Home | History | Annotate | Download | only in content
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.content;
     18 
     19 import android.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.content.res.AssetFileDescriptor;
     22 import android.database.CrossProcessCursorWrapper;
     23 import android.database.Cursor;
     24 import android.net.Uri;
     25 import android.os.Bundle;
     26 import android.os.CancellationSignal;
     27 import android.os.DeadObjectException;
     28 import android.os.Handler;
     29 import android.os.ICancellationSignal;
     30 import android.os.Looper;
     31 import android.os.ParcelFileDescriptor;
     32 import android.os.RemoteException;
     33 import android.util.Log;
     34 
     35 import com.android.internal.annotations.GuardedBy;
     36 import com.android.internal.annotations.VisibleForTesting;
     37 import com.android.internal.util.Preconditions;
     38 
     39 import dalvik.system.CloseGuard;
     40 
     41 import java.io.FileNotFoundException;
     42 import java.util.ArrayList;
     43 import java.util.concurrent.atomic.AtomicBoolean;
     44 
     45 /**
     46  * The public interface object used to interact with a specific
     47  * {@link ContentProvider}.
     48  * <p>
     49  * Instances can be obtained by calling
     50  * {@link ContentResolver#acquireContentProviderClient} or
     51  * {@link ContentResolver#acquireUnstableContentProviderClient}. Instances must
     52  * be released using {@link #close()} in order to indicate to the system that
     53  * the underlying {@link ContentProvider} is no longer needed and can be killed
     54  * to free up resources.
     55  * <p>
     56  * Note that you should generally create a new ContentProviderClient instance
     57  * for each thread that will be performing operations. Unlike
     58  * {@link ContentResolver}, the methods here such as {@link #query} and
     59  * {@link #openFile} are not thread safe -- you must not call {@link #close()}
     60  * on the ContentProviderClient those calls are made from until you are finished
     61  * with the data they have returned.
     62  */
     63 public class ContentProviderClient implements AutoCloseable {
     64     private static final String TAG = "ContentProviderClient";
     65 
     66     @GuardedBy("ContentProviderClient.class")
     67     private static Handler sAnrHandler;
     68 
     69     private final ContentResolver mContentResolver;
     70     private final IContentProvider mContentProvider;
     71     private final String mPackageName;
     72     private final boolean mStable;
     73 
     74     private final AtomicBoolean mClosed = new AtomicBoolean();
     75     private final CloseGuard mCloseGuard = CloseGuard.get();
     76 
     77     private long mAnrTimeout;
     78     private NotRespondingRunnable mAnrRunnable;
     79 
     80     /** {@hide} */
     81     @VisibleForTesting
     82     public ContentProviderClient(
     83             ContentResolver contentResolver, IContentProvider contentProvider, boolean stable) {
     84         mContentResolver = contentResolver;
     85         mContentProvider = contentProvider;
     86         mPackageName = contentResolver.mPackageName;
     87 
     88         mStable = stable;
     89 
     90         mCloseGuard.open("close");
     91     }
     92 
     93     /** {@hide} */
     94     public void setDetectNotResponding(long timeoutMillis) {
     95         synchronized (ContentProviderClient.class) {
     96             mAnrTimeout = timeoutMillis;
     97 
     98             if (timeoutMillis > 0) {
     99                 if (mAnrRunnable == null) {
    100                     mAnrRunnable = new NotRespondingRunnable();
    101                 }
    102                 if (sAnrHandler == null) {
    103                     sAnrHandler = new Handler(Looper.getMainLooper(), null, true /* async */);
    104                 }
    105             } else {
    106                 mAnrRunnable = null;
    107             }
    108         }
    109     }
    110 
    111     private void beforeRemote() {
    112         if (mAnrRunnable != null) {
    113             sAnrHandler.postDelayed(mAnrRunnable, mAnrTimeout);
    114         }
    115     }
    116 
    117     private void afterRemote() {
    118         if (mAnrRunnable != null) {
    119             sAnrHandler.removeCallbacks(mAnrRunnable);
    120         }
    121     }
    122 
    123     /** See {@link ContentProvider#query ContentProvider.query} */
    124     public @Nullable Cursor query(@NonNull Uri url, @Nullable String[] projection,
    125             @Nullable String selection, @Nullable String[] selectionArgs,
    126             @Nullable String sortOrder) throws RemoteException {
    127         return query(url, projection, selection,  selectionArgs, sortOrder, null);
    128     }
    129 
    130     /** See {@link ContentProvider#query ContentProvider.query} */
    131     public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
    132             @Nullable String selection, @Nullable String[] selectionArgs,
    133             @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal)
    134                     throws RemoteException {
    135         Bundle queryArgs =
    136                 ContentResolver.createSqlQueryBundle(selection, selectionArgs, sortOrder);
    137         return query(uri, projection, queryArgs, cancellationSignal);
    138     }
    139 
    140     /** See {@link ContentProvider#query ContentProvider.query} */
    141     public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
    142             Bundle queryArgs, @Nullable CancellationSignal cancellationSignal)
    143                     throws RemoteException {
    144         Preconditions.checkNotNull(uri, "url");
    145 
    146         beforeRemote();
    147         try {
    148             ICancellationSignal remoteCancellationSignal = null;
    149             if (cancellationSignal != null) {
    150                 cancellationSignal.throwIfCanceled();
    151                 remoteCancellationSignal = mContentProvider.createCancellationSignal();
    152                 cancellationSignal.setRemote(remoteCancellationSignal);
    153             }
    154             final Cursor cursor = mContentProvider.query(
    155                     mPackageName, uri, projection, queryArgs, remoteCancellationSignal);
    156             if (cursor == null) {
    157                 return null;
    158             }
    159             return new CursorWrapperInner(cursor);
    160         } catch (DeadObjectException e) {
    161             if (!mStable) {
    162                 mContentResolver.unstableProviderDied(mContentProvider);
    163             }
    164             throw e;
    165         } finally {
    166             afterRemote();
    167         }
    168     }
    169 
    170     /** See {@link ContentProvider#getType ContentProvider.getType} */
    171     public @Nullable String getType(@NonNull Uri url) throws RemoteException {
    172         Preconditions.checkNotNull(url, "url");
    173 
    174         beforeRemote();
    175         try {
    176             return mContentProvider.getType(url);
    177         } catch (DeadObjectException e) {
    178             if (!mStable) {
    179                 mContentResolver.unstableProviderDied(mContentProvider);
    180             }
    181             throw e;
    182         } finally {
    183             afterRemote();
    184         }
    185     }
    186 
    187     /** See {@link ContentProvider#getStreamTypes ContentProvider.getStreamTypes} */
    188     public @Nullable String[] getStreamTypes(@NonNull Uri url, @NonNull String mimeTypeFilter)
    189             throws RemoteException {
    190         Preconditions.checkNotNull(url, "url");
    191         Preconditions.checkNotNull(mimeTypeFilter, "mimeTypeFilter");
    192 
    193         beforeRemote();
    194         try {
    195             return mContentProvider.getStreamTypes(url, mimeTypeFilter);
    196         } catch (DeadObjectException e) {
    197             if (!mStable) {
    198                 mContentResolver.unstableProviderDied(mContentProvider);
    199             }
    200             throw e;
    201         } finally {
    202             afterRemote();
    203         }
    204     }
    205 
    206     /** See {@link ContentProvider#canonicalize} */
    207     public final @Nullable Uri canonicalize(@NonNull Uri url) throws RemoteException {
    208         Preconditions.checkNotNull(url, "url");
    209 
    210         beforeRemote();
    211         try {
    212             return mContentProvider.canonicalize(mPackageName, url);
    213         } catch (DeadObjectException e) {
    214             if (!mStable) {
    215                 mContentResolver.unstableProviderDied(mContentProvider);
    216             }
    217             throw e;
    218         } finally {
    219             afterRemote();
    220         }
    221     }
    222 
    223     /** See {@link ContentProvider#uncanonicalize} */
    224     public final @Nullable Uri uncanonicalize(@NonNull Uri url) throws RemoteException {
    225         Preconditions.checkNotNull(url, "url");
    226 
    227         beforeRemote();
    228         try {
    229             return mContentProvider.uncanonicalize(mPackageName, url);
    230         } catch (DeadObjectException e) {
    231             if (!mStable) {
    232                 mContentResolver.unstableProviderDied(mContentProvider);
    233             }
    234             throw e;
    235         } finally {
    236             afterRemote();
    237         }
    238     }
    239 
    240     /** See {@link ContentProvider#refresh} */
    241     public boolean refresh(Uri url, @Nullable Bundle args,
    242             @Nullable CancellationSignal cancellationSignal) throws RemoteException {
    243         Preconditions.checkNotNull(url, "url");
    244 
    245         beforeRemote();
    246         try {
    247             ICancellationSignal remoteCancellationSignal = null;
    248             if (cancellationSignal != null) {
    249                 cancellationSignal.throwIfCanceled();
    250                 remoteCancellationSignal = mContentProvider.createCancellationSignal();
    251                 cancellationSignal.setRemote(remoteCancellationSignal);
    252             }
    253             return mContentProvider.refresh(mPackageName, url, args, remoteCancellationSignal);
    254         } catch (DeadObjectException e) {
    255             if (!mStable) {
    256                 mContentResolver.unstableProviderDied(mContentProvider);
    257             }
    258             throw e;
    259         } finally {
    260             afterRemote();
    261         }
    262     }
    263 
    264     /** See {@link ContentProvider#insert ContentProvider.insert} */
    265     public @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues initialValues)
    266             throws RemoteException {
    267         Preconditions.checkNotNull(url, "url");
    268 
    269         beforeRemote();
    270         try {
    271             return mContentProvider.insert(mPackageName, url, initialValues);
    272         } catch (DeadObjectException e) {
    273             if (!mStable) {
    274                 mContentResolver.unstableProviderDied(mContentProvider);
    275             }
    276             throw e;
    277         } finally {
    278             afterRemote();
    279         }
    280     }
    281 
    282     /** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */
    283     public int bulkInsert(@NonNull Uri url, @NonNull ContentValues[] initialValues)
    284             throws RemoteException {
    285         Preconditions.checkNotNull(url, "url");
    286         Preconditions.checkNotNull(initialValues, "initialValues");
    287 
    288         beforeRemote();
    289         try {
    290             return mContentProvider.bulkInsert(mPackageName, url, initialValues);
    291         } catch (DeadObjectException e) {
    292             if (!mStable) {
    293                 mContentResolver.unstableProviderDied(mContentProvider);
    294             }
    295             throw e;
    296         } finally {
    297             afterRemote();
    298         }
    299     }
    300 
    301     /** See {@link ContentProvider#delete ContentProvider.delete} */
    302     public int delete(@NonNull Uri url, @Nullable String selection,
    303             @Nullable String[] selectionArgs) throws RemoteException {
    304         Preconditions.checkNotNull(url, "url");
    305 
    306         beforeRemote();
    307         try {
    308             return mContentProvider.delete(mPackageName, url, selection, selectionArgs);
    309         } catch (DeadObjectException e) {
    310             if (!mStable) {
    311                 mContentResolver.unstableProviderDied(mContentProvider);
    312             }
    313             throw e;
    314         } finally {
    315             afterRemote();
    316         }
    317     }
    318 
    319     /** See {@link ContentProvider#update ContentProvider.update} */
    320     public int update(@NonNull Uri url, @Nullable ContentValues values, @Nullable String selection,
    321             @Nullable String[] selectionArgs) throws RemoteException {
    322         Preconditions.checkNotNull(url, "url");
    323 
    324         beforeRemote();
    325         try {
    326             return mContentProvider.update(mPackageName, url, values, selection, selectionArgs);
    327         } catch (DeadObjectException e) {
    328             if (!mStable) {
    329                 mContentResolver.unstableProviderDied(mContentProvider);
    330             }
    331             throw e;
    332         } finally {
    333             afterRemote();
    334         }
    335     }
    336 
    337     /**
    338      * See {@link ContentProvider#openFile ContentProvider.openFile}.  Note that
    339      * this <em>does not</em>
    340      * take care of non-content: URIs such as file:.  It is strongly recommended
    341      * you use the {@link ContentResolver#openFileDescriptor
    342      * ContentResolver.openFileDescriptor} API instead.
    343      */
    344     public @Nullable ParcelFileDescriptor openFile(@NonNull Uri url, @NonNull String mode)
    345             throws RemoteException, FileNotFoundException {
    346         return openFile(url, mode, null);
    347     }
    348 
    349     /**
    350      * See {@link ContentProvider#openFile ContentProvider.openFile}.  Note that
    351      * this <em>does not</em>
    352      * take care of non-content: URIs such as file:.  It is strongly recommended
    353      * you use the {@link ContentResolver#openFileDescriptor
    354      * ContentResolver.openFileDescriptor} API instead.
    355      */
    356     public @Nullable ParcelFileDescriptor openFile(@NonNull Uri url, @NonNull String mode,
    357             @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
    358         Preconditions.checkNotNull(url, "url");
    359         Preconditions.checkNotNull(mode, "mode");
    360 
    361         beforeRemote();
    362         try {
    363             ICancellationSignal remoteSignal = null;
    364             if (signal != null) {
    365                 signal.throwIfCanceled();
    366                 remoteSignal = mContentProvider.createCancellationSignal();
    367                 signal.setRemote(remoteSignal);
    368             }
    369             return mContentProvider.openFile(mPackageName, url, mode, remoteSignal, null);
    370         } catch (DeadObjectException e) {
    371             if (!mStable) {
    372                 mContentResolver.unstableProviderDied(mContentProvider);
    373             }
    374             throw e;
    375         } finally {
    376             afterRemote();
    377         }
    378     }
    379 
    380     /**
    381      * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}.
    382      * Note that this <em>does not</em>
    383      * take care of non-content: URIs such as file:.  It is strongly recommended
    384      * you use the {@link ContentResolver#openAssetFileDescriptor
    385      * ContentResolver.openAssetFileDescriptor} API instead.
    386      */
    387     public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri url, @NonNull String mode)
    388             throws RemoteException, FileNotFoundException {
    389         return openAssetFile(url, mode, null);
    390     }
    391 
    392     /**
    393      * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}.
    394      * Note that this <em>does not</em>
    395      * take care of non-content: URIs such as file:.  It is strongly recommended
    396      * you use the {@link ContentResolver#openAssetFileDescriptor
    397      * ContentResolver.openAssetFileDescriptor} API instead.
    398      */
    399     public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri url, @NonNull String mode,
    400             @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
    401         Preconditions.checkNotNull(url, "url");
    402         Preconditions.checkNotNull(mode, "mode");
    403 
    404         beforeRemote();
    405         try {
    406             ICancellationSignal remoteSignal = null;
    407             if (signal != null) {
    408                 signal.throwIfCanceled();
    409                 remoteSignal = mContentProvider.createCancellationSignal();
    410                 signal.setRemote(remoteSignal);
    411             }
    412             return mContentProvider.openAssetFile(mPackageName, url, mode, remoteSignal);
    413         } catch (DeadObjectException e) {
    414             if (!mStable) {
    415                 mContentResolver.unstableProviderDied(mContentProvider);
    416             }
    417             throw e;
    418         } finally {
    419             afterRemote();
    420         }
    421     }
    422 
    423     /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
    424     public final @Nullable AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull Uri uri,
    425             @NonNull String mimeType, @Nullable Bundle opts)
    426                     throws RemoteException, FileNotFoundException {
    427         return openTypedAssetFileDescriptor(uri, mimeType, opts, null);
    428     }
    429 
    430     /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
    431     public final @Nullable AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull Uri uri,
    432             @NonNull String mimeType, @Nullable Bundle opts, @Nullable CancellationSignal signal)
    433                     throws RemoteException, FileNotFoundException {
    434         Preconditions.checkNotNull(uri, "uri");
    435         Preconditions.checkNotNull(mimeType, "mimeType");
    436 
    437         beforeRemote();
    438         try {
    439             ICancellationSignal remoteSignal = null;
    440             if (signal != null) {
    441                 signal.throwIfCanceled();
    442                 remoteSignal = mContentProvider.createCancellationSignal();
    443                 signal.setRemote(remoteSignal);
    444             }
    445             return mContentProvider.openTypedAssetFile(
    446                     mPackageName, uri, mimeType, opts, remoteSignal);
    447         } catch (DeadObjectException e) {
    448             if (!mStable) {
    449                 mContentResolver.unstableProviderDied(mContentProvider);
    450             }
    451             throw e;
    452         } finally {
    453             afterRemote();
    454         }
    455     }
    456 
    457     /** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */
    458     public @NonNull ContentProviderResult[] applyBatch(
    459             @NonNull ArrayList<ContentProviderOperation> operations)
    460                     throws RemoteException, OperationApplicationException {
    461         Preconditions.checkNotNull(operations, "operations");
    462 
    463         beforeRemote();
    464         try {
    465             return mContentProvider.applyBatch(mPackageName, operations);
    466         } catch (DeadObjectException e) {
    467             if (!mStable) {
    468                 mContentResolver.unstableProviderDied(mContentProvider);
    469             }
    470             throw e;
    471         } finally {
    472             afterRemote();
    473         }
    474     }
    475 
    476     /** See {@link ContentProvider#call(String, String, Bundle)} */
    477     public @Nullable Bundle call(@NonNull String method, @Nullable String arg,
    478             @Nullable Bundle extras) throws RemoteException {
    479         Preconditions.checkNotNull(method, "method");
    480 
    481         beforeRemote();
    482         try {
    483             return mContentProvider.call(mPackageName, method, arg, extras);
    484         } catch (DeadObjectException e) {
    485             if (!mStable) {
    486                 mContentResolver.unstableProviderDied(mContentProvider);
    487             }
    488             throw e;
    489         } finally {
    490             afterRemote();
    491         }
    492     }
    493 
    494     /**
    495      * Closes this client connection, indicating to the system that the
    496      * underlying {@link ContentProvider} is no longer needed.
    497      */
    498     @Override
    499     public void close() {
    500         closeInternal();
    501     }
    502 
    503     /**
    504      * @deprecated replaced by {@link #close()}.
    505      */
    506     @Deprecated
    507     public boolean release() {
    508         return closeInternal();
    509     }
    510 
    511     private boolean closeInternal() {
    512         mCloseGuard.close();
    513         if (mClosed.compareAndSet(false, true)) {
    514             if (mStable) {
    515                 return mContentResolver.releaseProvider(mContentProvider);
    516             } else {
    517                 return mContentResolver.releaseUnstableProvider(mContentProvider);
    518             }
    519         } else {
    520             return false;
    521         }
    522     }
    523 
    524     @Override
    525     protected void finalize() throws Throwable {
    526         try {
    527             mCloseGuard.warnIfOpen();
    528             close();
    529         } finally {
    530             super.finalize();
    531         }
    532     }
    533 
    534     /**
    535      * Get a reference to the {@link ContentProvider} that is associated with this
    536      * client. If the {@link ContentProvider} is running in a different process then
    537      * null will be returned. This can be used if you know you are running in the same
    538      * process as a provider, and want to get direct access to its implementation details.
    539      *
    540      * @return If the associated {@link ContentProvider} is local, returns it.
    541      * Otherwise returns null.
    542      */
    543     public @Nullable ContentProvider getLocalContentProvider() {
    544         return ContentProvider.coerceToLocalContentProvider(mContentProvider);
    545     }
    546 
    547     /** {@hide} */
    548     public static void releaseQuietly(ContentProviderClient client) {
    549         if (client != null) {
    550             try {
    551                 client.release();
    552             } catch (Exception ignored) {
    553             }
    554         }
    555     }
    556 
    557     private class NotRespondingRunnable implements Runnable {
    558         @Override
    559         public void run() {
    560             Log.w(TAG, "Detected provider not responding: " + mContentProvider);
    561             mContentResolver.appNotRespondingViaProvider(mContentProvider);
    562         }
    563     }
    564 
    565     private final class CursorWrapperInner extends CrossProcessCursorWrapper {
    566         private final CloseGuard mCloseGuard = CloseGuard.get();
    567 
    568         CursorWrapperInner(Cursor cursor) {
    569             super(cursor);
    570             mCloseGuard.open("close");
    571         }
    572 
    573         @Override
    574         public void close() {
    575             mCloseGuard.close();
    576             super.close();
    577         }
    578 
    579         @Override
    580         protected void finalize() throws Throwable {
    581             try {
    582                 mCloseGuard.warnIfOpen();
    583                 close();
    584             } finally {
    585                 super.finalize();
    586             }
    587         }
    588     }
    589 }
    590