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.content.res.AssetFileDescriptor;
     20 import android.database.Cursor;
     21 import android.net.Uri;
     22 import android.os.Bundle;
     23 import android.os.CancellationSignal;
     24 import android.os.DeadObjectException;
     25 import android.os.Handler;
     26 import android.os.ICancellationSignal;
     27 import android.os.Looper;
     28 import android.os.ParcelFileDescriptor;
     29 import android.os.RemoteException;
     30 import android.util.Log;
     31 
     32 import com.android.internal.annotations.GuardedBy;
     33 
     34 import dalvik.system.CloseGuard;
     35 
     36 import java.io.FileNotFoundException;
     37 import java.util.ArrayList;
     38 
     39 /**
     40  * The public interface object used to interact with a {@link ContentProvider}. This is obtained by
     41  * calling {@link ContentResolver#acquireContentProviderClient}. This object must be released
     42  * using {@link #release} in order to indicate to the system that the {@link ContentProvider} is
     43  * no longer needed and can be killed to free up resources.
     44  *
     45  * <p>Note that you should generally create a new ContentProviderClient instance
     46  * for each thread that will be performing operations.  Unlike
     47  * {@link ContentResolver}, the methods here such as {@link #query} and
     48  * {@link #openFile} are not thread safe -- you must not call
     49  * {@link #release()} on the ContentProviderClient those calls are made from
     50  * until you are finished with the data they have returned.
     51  */
     52 public class ContentProviderClient {
     53     private static final String TAG = "ContentProviderClient";
     54 
     55     @GuardedBy("ContentProviderClient.class")
     56     private static Handler sAnrHandler;
     57 
     58     private final ContentResolver mContentResolver;
     59     private final IContentProvider mContentProvider;
     60     private final String mPackageName;
     61     private final boolean mStable;
     62 
     63     private final CloseGuard mGuard = CloseGuard.get();
     64 
     65     private long mAnrTimeout;
     66     private NotRespondingRunnable mAnrRunnable;
     67 
     68     private boolean mReleased;
     69 
     70     /** {@hide} */
     71     ContentProviderClient(
     72             ContentResolver contentResolver, IContentProvider contentProvider, boolean stable) {
     73         mContentResolver = contentResolver;
     74         mContentProvider = contentProvider;
     75         mPackageName = contentResolver.mPackageName;
     76         mStable = stable;
     77 
     78         mGuard.open("release");
     79     }
     80 
     81     /** {@hide} */
     82     public void setDetectNotResponding(long timeoutMillis) {
     83         synchronized (ContentProviderClient.class) {
     84             mAnrTimeout = timeoutMillis;
     85 
     86             if (timeoutMillis > 0) {
     87                 if (mAnrRunnable == null) {
     88                     mAnrRunnable = new NotRespondingRunnable();
     89                 }
     90                 if (sAnrHandler == null) {
     91                     sAnrHandler = new Handler(Looper.getMainLooper(), null, true /* async */);
     92                 }
     93             } else {
     94                 mAnrRunnable = null;
     95             }
     96         }
     97     }
     98 
     99     private void beforeRemote() {
    100         if (mAnrRunnable != null) {
    101             sAnrHandler.postDelayed(mAnrRunnable, mAnrTimeout);
    102         }
    103     }
    104 
    105     private void afterRemote() {
    106         if (mAnrRunnable != null) {
    107             sAnrHandler.removeCallbacks(mAnrRunnable);
    108         }
    109     }
    110 
    111     /** See {@link ContentProvider#query ContentProvider.query} */
    112     public Cursor query(Uri url, String[] projection, String selection,
    113             String[] selectionArgs, String sortOrder) throws RemoteException {
    114         return query(url, projection, selection,  selectionArgs, sortOrder, null);
    115     }
    116 
    117     /** See {@link ContentProvider#query ContentProvider.query} */
    118     public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs,
    119             String sortOrder, CancellationSignal cancellationSignal) throws RemoteException {
    120         beforeRemote();
    121         try {
    122             ICancellationSignal remoteCancellationSignal = null;
    123             if (cancellationSignal != null) {
    124                 cancellationSignal.throwIfCanceled();
    125                 remoteCancellationSignal = mContentProvider.createCancellationSignal();
    126                 cancellationSignal.setRemote(remoteCancellationSignal);
    127             }
    128             return mContentProvider.query(mPackageName, url, projection, selection, selectionArgs,
    129                     sortOrder, remoteCancellationSignal);
    130         } catch (DeadObjectException e) {
    131             if (!mStable) {
    132                 mContentResolver.unstableProviderDied(mContentProvider);
    133             }
    134             throw e;
    135         } finally {
    136             afterRemote();
    137         }
    138     }
    139 
    140     /** See {@link ContentProvider#getType ContentProvider.getType} */
    141     public String getType(Uri url) throws RemoteException {
    142         beforeRemote();
    143         try {
    144             return mContentProvider.getType(url);
    145         } catch (DeadObjectException e) {
    146             if (!mStable) {
    147                 mContentResolver.unstableProviderDied(mContentProvider);
    148             }
    149             throw e;
    150         } finally {
    151             afterRemote();
    152         }
    153     }
    154 
    155     /** See {@link ContentProvider#getStreamTypes ContentProvider.getStreamTypes} */
    156     public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException {
    157         beforeRemote();
    158         try {
    159             return mContentProvider.getStreamTypes(url, mimeTypeFilter);
    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#canonicalize} */
    171     public final Uri canonicalize(Uri url) throws RemoteException {
    172         beforeRemote();
    173         try {
    174             return mContentProvider.canonicalize(mPackageName, url);
    175         } catch (DeadObjectException e) {
    176             if (!mStable) {
    177                 mContentResolver.unstableProviderDied(mContentProvider);
    178             }
    179             throw e;
    180         } finally {
    181             afterRemote();
    182         }
    183     }
    184 
    185     /** See {@link ContentProvider#uncanonicalize} */
    186     public final Uri uncanonicalize(Uri url) throws RemoteException {
    187         beforeRemote();
    188         try {
    189             return mContentProvider.uncanonicalize(mPackageName, url);
    190         } catch (DeadObjectException e) {
    191             if (!mStable) {
    192                 mContentResolver.unstableProviderDied(mContentProvider);
    193             }
    194             throw e;
    195         } finally {
    196             afterRemote();
    197         }
    198     }
    199 
    200     /** See {@link ContentProvider#insert ContentProvider.insert} */
    201     public Uri insert(Uri url, ContentValues initialValues) throws RemoteException {
    202         beforeRemote();
    203         try {
    204             return mContentProvider.insert(mPackageName, url, initialValues);
    205         } catch (DeadObjectException e) {
    206             if (!mStable) {
    207                 mContentResolver.unstableProviderDied(mContentProvider);
    208             }
    209             throw e;
    210         } finally {
    211             afterRemote();
    212         }
    213     }
    214 
    215     /** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */
    216     public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException {
    217         beforeRemote();
    218         try {
    219             return mContentProvider.bulkInsert(mPackageName, url, initialValues);
    220         } catch (DeadObjectException e) {
    221             if (!mStable) {
    222                 mContentResolver.unstableProviderDied(mContentProvider);
    223             }
    224             throw e;
    225         } finally {
    226             afterRemote();
    227         }
    228     }
    229 
    230     /** See {@link ContentProvider#delete ContentProvider.delete} */
    231     public int delete(Uri url, String selection, String[] selectionArgs)
    232             throws RemoteException {
    233         beforeRemote();
    234         try {
    235             return mContentProvider.delete(mPackageName, url, selection, selectionArgs);
    236         } catch (DeadObjectException e) {
    237             if (!mStable) {
    238                 mContentResolver.unstableProviderDied(mContentProvider);
    239             }
    240             throw e;
    241         } finally {
    242             afterRemote();
    243         }
    244     }
    245 
    246     /** See {@link ContentProvider#update ContentProvider.update} */
    247     public int update(Uri url, ContentValues values, String selection,
    248             String[] selectionArgs) throws RemoteException {
    249         beforeRemote();
    250         try {
    251             return mContentProvider.update(mPackageName, url, values, selection, selectionArgs);
    252         } catch (DeadObjectException e) {
    253             if (!mStable) {
    254                 mContentResolver.unstableProviderDied(mContentProvider);
    255             }
    256             throw e;
    257         } finally {
    258             afterRemote();
    259         }
    260     }
    261 
    262     /**
    263      * See {@link ContentProvider#openFile ContentProvider.openFile}.  Note that
    264      * this <em>does not</em>
    265      * take care of non-content: URIs such as file:.  It is strongly recommended
    266      * you use the {@link ContentResolver#openFileDescriptor
    267      * ContentResolver.openFileDescriptor} API instead.
    268      */
    269     public ParcelFileDescriptor openFile(Uri url, String mode)
    270             throws RemoteException, FileNotFoundException {
    271         return openFile(url, mode, null);
    272     }
    273 
    274     /**
    275      * See {@link ContentProvider#openFile ContentProvider.openFile}.  Note that
    276      * this <em>does not</em>
    277      * take care of non-content: URIs such as file:.  It is strongly recommended
    278      * you use the {@link ContentResolver#openFileDescriptor
    279      * ContentResolver.openFileDescriptor} API instead.
    280      */
    281     public ParcelFileDescriptor openFile(Uri url, String mode, CancellationSignal signal)
    282             throws RemoteException, FileNotFoundException {
    283         beforeRemote();
    284         try {
    285             ICancellationSignal remoteSignal = null;
    286             if (signal != null) {
    287                 signal.throwIfCanceled();
    288                 remoteSignal = mContentProvider.createCancellationSignal();
    289                 signal.setRemote(remoteSignal);
    290             }
    291             return mContentProvider.openFile(mPackageName, url, mode, remoteSignal);
    292         } catch (DeadObjectException e) {
    293             if (!mStable) {
    294                 mContentResolver.unstableProviderDied(mContentProvider);
    295             }
    296             throw e;
    297         } finally {
    298             afterRemote();
    299         }
    300     }
    301 
    302     /**
    303      * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}.
    304      * Note that this <em>does not</em>
    305      * take care of non-content: URIs such as file:.  It is strongly recommended
    306      * you use the {@link ContentResolver#openAssetFileDescriptor
    307      * ContentResolver.openAssetFileDescriptor} API instead.
    308      */
    309     public AssetFileDescriptor openAssetFile(Uri url, String mode)
    310             throws RemoteException, FileNotFoundException {
    311         return openAssetFile(url, mode, null);
    312     }
    313 
    314     /**
    315      * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}.
    316      * Note that this <em>does not</em>
    317      * take care of non-content: URIs such as file:.  It is strongly recommended
    318      * you use the {@link ContentResolver#openAssetFileDescriptor
    319      * ContentResolver.openAssetFileDescriptor} API instead.
    320      */
    321     public AssetFileDescriptor openAssetFile(Uri url, String mode, CancellationSignal signal)
    322             throws RemoteException, FileNotFoundException {
    323         beforeRemote();
    324         try {
    325             ICancellationSignal remoteSignal = null;
    326             if (signal != null) {
    327                 signal.throwIfCanceled();
    328                 remoteSignal = mContentProvider.createCancellationSignal();
    329                 signal.setRemote(remoteSignal);
    330             }
    331             return mContentProvider.openAssetFile(mPackageName, url, mode, remoteSignal);
    332         } catch (DeadObjectException e) {
    333             if (!mStable) {
    334                 mContentResolver.unstableProviderDied(mContentProvider);
    335             }
    336             throw e;
    337         } finally {
    338             afterRemote();
    339         }
    340     }
    341 
    342     /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
    343     public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri,
    344             String mimeType, Bundle opts) throws RemoteException, FileNotFoundException {
    345         return openTypedAssetFileDescriptor(uri, mimeType, opts, null);
    346     }
    347 
    348     /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
    349     public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri,
    350             String mimeType, Bundle opts, CancellationSignal signal)
    351             throws RemoteException, FileNotFoundException {
    352         beforeRemote();
    353         try {
    354             ICancellationSignal remoteSignal = null;
    355             if (signal != null) {
    356                 signal.throwIfCanceled();
    357                 remoteSignal = mContentProvider.createCancellationSignal();
    358                 signal.setRemote(remoteSignal);
    359             }
    360             return mContentProvider.openTypedAssetFile(
    361                     mPackageName, uri, mimeType, opts, remoteSignal);
    362         } catch (DeadObjectException e) {
    363             if (!mStable) {
    364                 mContentResolver.unstableProviderDied(mContentProvider);
    365             }
    366             throw e;
    367         } finally {
    368             afterRemote();
    369         }
    370     }
    371 
    372     /** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */
    373     public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
    374             throws RemoteException, OperationApplicationException {
    375         beforeRemote();
    376         try {
    377             return mContentProvider.applyBatch(mPackageName, operations);
    378         } catch (DeadObjectException e) {
    379             if (!mStable) {
    380                 mContentResolver.unstableProviderDied(mContentProvider);
    381             }
    382             throw e;
    383         } finally {
    384             afterRemote();
    385         }
    386     }
    387 
    388     /** See {@link ContentProvider#call(String, String, Bundle)} */
    389     public Bundle call(String method, String arg, Bundle extras) throws RemoteException {
    390         beforeRemote();
    391         try {
    392             return mContentProvider.call(mPackageName, method, arg, extras);
    393         } catch (DeadObjectException e) {
    394             if (!mStable) {
    395                 mContentResolver.unstableProviderDied(mContentProvider);
    396             }
    397             throw e;
    398         } finally {
    399             afterRemote();
    400         }
    401     }
    402 
    403     /**
    404      * Call this to indicate to the system that the associated {@link ContentProvider} is no
    405      * longer needed by this {@link ContentProviderClient}.
    406      * @return true if this was release, false if it was already released
    407      */
    408     public boolean release() {
    409         synchronized (this) {
    410             if (mReleased) {
    411                 throw new IllegalStateException("Already released");
    412             }
    413             mReleased = true;
    414             mGuard.close();
    415             if (mStable) {
    416                 return mContentResolver.releaseProvider(mContentProvider);
    417             } else {
    418                 return mContentResolver.releaseUnstableProvider(mContentProvider);
    419             }
    420         }
    421     }
    422 
    423     @Override
    424     protected void finalize() throws Throwable {
    425         if (mGuard != null) {
    426             mGuard.warnIfOpen();
    427         }
    428     }
    429 
    430     /**
    431      * Get a reference to the {@link ContentProvider} that is associated with this
    432      * client. If the {@link ContentProvider} is running in a different process then
    433      * null will be returned. This can be used if you know you are running in the same
    434      * process as a provider, and want to get direct access to its implementation details.
    435      *
    436      * @return If the associated {@link ContentProvider} is local, returns it.
    437      * Otherwise returns null.
    438      */
    439     public ContentProvider getLocalContentProvider() {
    440         return ContentProvider.coerceToLocalContentProvider(mContentProvider);
    441     }
    442 
    443     /** {@hide} */
    444     public static void releaseQuietly(ContentProviderClient client) {
    445         if (client != null) {
    446             try {
    447                 client.release();
    448             } catch (Exception ignored) {
    449             }
    450         }
    451     }
    452 
    453     private class NotRespondingRunnable implements Runnable {
    454         @Override
    455         public void run() {
    456             Log.w(TAG, "Detected provider not responding: " + mContentProvider);
    457             mContentResolver.appNotRespondingViaProvider(mContentProvider);
    458         }
    459     }
    460 }
    461