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