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 url, @Nullable String[] projection,
    132             @Nullable String selection, @Nullable String[] selectionArgs,
    133             @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal)
    134                     throws RemoteException {
    135         Preconditions.checkNotNull(url, "url");
    136 
    137         beforeRemote();
    138         try {
    139             ICancellationSignal remoteCancellationSignal = null;
    140             if (cancellationSignal != null) {
    141                 cancellationSignal.throwIfCanceled();
    142                 remoteCancellationSignal = mContentProvider.createCancellationSignal();
    143                 cancellationSignal.setRemote(remoteCancellationSignal);
    144             }
    145             final Cursor cursor = mContentProvider.query(mPackageName, url, projection, selection,
    146                     selectionArgs, sortOrder, remoteCancellationSignal);
    147             if (cursor == null) {
    148                 return null;
    149             }
    150 
    151             if ("com.google.android.gms".equals(mPackageName)) {
    152                 // They're casting to a concrete subclass, sigh
    153                 return cursor;
    154             } else {
    155                 return new CursorWrapperInner(cursor);
    156             }
    157         } catch (DeadObjectException e) {
    158             if (!mStable) {
    159                 mContentResolver.unstableProviderDied(mContentProvider);
    160             }
    161             throw e;
    162         } finally {
    163             afterRemote();
    164         }
    165     }
    166 
    167     /** See {@link ContentProvider#getType ContentProvider.getType} */
    168     public @Nullable String getType(@NonNull Uri url) throws RemoteException {
    169         Preconditions.checkNotNull(url, "url");
    170 
    171         beforeRemote();
    172         try {
    173             return mContentProvider.getType(url);
    174         } catch (DeadObjectException e) {
    175             if (!mStable) {
    176                 mContentResolver.unstableProviderDied(mContentProvider);
    177             }
    178             throw e;
    179         } finally {
    180             afterRemote();
    181         }
    182     }
    183 
    184     /** See {@link ContentProvider#getStreamTypes ContentProvider.getStreamTypes} */
    185     public @Nullable String[] getStreamTypes(@NonNull Uri url, @NonNull String mimeTypeFilter)
    186             throws RemoteException {
    187         Preconditions.checkNotNull(url, "url");
    188         Preconditions.checkNotNull(mimeTypeFilter, "mimeTypeFilter");
    189 
    190         beforeRemote();
    191         try {
    192             return mContentProvider.getStreamTypes(url, mimeTypeFilter);
    193         } catch (DeadObjectException e) {
    194             if (!mStable) {
    195                 mContentResolver.unstableProviderDied(mContentProvider);
    196             }
    197             throw e;
    198         } finally {
    199             afterRemote();
    200         }
    201     }
    202 
    203     /** See {@link ContentProvider#canonicalize} */
    204     public final @Nullable Uri canonicalize(@NonNull Uri url) throws RemoteException {
    205         Preconditions.checkNotNull(url, "url");
    206 
    207         beforeRemote();
    208         try {
    209             return mContentProvider.canonicalize(mPackageName, url);
    210         } catch (DeadObjectException e) {
    211             if (!mStable) {
    212                 mContentResolver.unstableProviderDied(mContentProvider);
    213             }
    214             throw e;
    215         } finally {
    216             afterRemote();
    217         }
    218     }
    219 
    220     /** See {@link ContentProvider#uncanonicalize} */
    221     public final @Nullable Uri uncanonicalize(@NonNull Uri url) throws RemoteException {
    222         Preconditions.checkNotNull(url, "url");
    223 
    224         beforeRemote();
    225         try {
    226             return mContentProvider.uncanonicalize(mPackageName, url);
    227         } catch (DeadObjectException e) {
    228             if (!mStable) {
    229                 mContentResolver.unstableProviderDied(mContentProvider);
    230             }
    231             throw e;
    232         } finally {
    233             afterRemote();
    234         }
    235     }
    236 
    237     /** See {@link ContentProvider#insert ContentProvider.insert} */
    238     public @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues initialValues)
    239             throws RemoteException {
    240         Preconditions.checkNotNull(url, "url");
    241 
    242         beforeRemote();
    243         try {
    244             return mContentProvider.insert(mPackageName, url, initialValues);
    245         } catch (DeadObjectException e) {
    246             if (!mStable) {
    247                 mContentResolver.unstableProviderDied(mContentProvider);
    248             }
    249             throw e;
    250         } finally {
    251             afterRemote();
    252         }
    253     }
    254 
    255     /** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */
    256     public int bulkInsert(@NonNull Uri url, @NonNull ContentValues[] initialValues)
    257             throws RemoteException {
    258         Preconditions.checkNotNull(url, "url");
    259         Preconditions.checkNotNull(initialValues, "initialValues");
    260 
    261         beforeRemote();
    262         try {
    263             return mContentProvider.bulkInsert(mPackageName, url, initialValues);
    264         } catch (DeadObjectException e) {
    265             if (!mStable) {
    266                 mContentResolver.unstableProviderDied(mContentProvider);
    267             }
    268             throw e;
    269         } finally {
    270             afterRemote();
    271         }
    272     }
    273 
    274     /** See {@link ContentProvider#delete ContentProvider.delete} */
    275     public int delete(@NonNull Uri url, @Nullable String selection,
    276             @Nullable String[] selectionArgs) throws RemoteException {
    277         Preconditions.checkNotNull(url, "url");
    278 
    279         beforeRemote();
    280         try {
    281             return mContentProvider.delete(mPackageName, url, selection, selectionArgs);
    282         } catch (DeadObjectException e) {
    283             if (!mStable) {
    284                 mContentResolver.unstableProviderDied(mContentProvider);
    285             }
    286             throw e;
    287         } finally {
    288             afterRemote();
    289         }
    290     }
    291 
    292     /** See {@link ContentProvider#update ContentProvider.update} */
    293     public int update(@NonNull Uri url, @Nullable ContentValues values, @Nullable String selection,
    294             @Nullable String[] selectionArgs) throws RemoteException {
    295         Preconditions.checkNotNull(url, "url");
    296 
    297         beforeRemote();
    298         try {
    299             return mContentProvider.update(mPackageName, url, values, selection, selectionArgs);
    300         } catch (DeadObjectException e) {
    301             if (!mStable) {
    302                 mContentResolver.unstableProviderDied(mContentProvider);
    303             }
    304             throw e;
    305         } finally {
    306             afterRemote();
    307         }
    308     }
    309 
    310     /**
    311      * See {@link ContentProvider#openFile ContentProvider.openFile}.  Note that
    312      * this <em>does not</em>
    313      * take care of non-content: URIs such as file:.  It is strongly recommended
    314      * you use the {@link ContentResolver#openFileDescriptor
    315      * ContentResolver.openFileDescriptor} API instead.
    316      */
    317     public @Nullable ParcelFileDescriptor openFile(@NonNull Uri url, @NonNull String mode)
    318             throws RemoteException, FileNotFoundException {
    319         return openFile(url, mode, null);
    320     }
    321 
    322     /**
    323      * See {@link ContentProvider#openFile ContentProvider.openFile}.  Note that
    324      * this <em>does not</em>
    325      * take care of non-content: URIs such as file:.  It is strongly recommended
    326      * you use the {@link ContentResolver#openFileDescriptor
    327      * ContentResolver.openFileDescriptor} API instead.
    328      */
    329     public @Nullable ParcelFileDescriptor openFile(@NonNull Uri url, @NonNull String mode,
    330             @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
    331         Preconditions.checkNotNull(url, "url");
    332         Preconditions.checkNotNull(mode, "mode");
    333 
    334         beforeRemote();
    335         try {
    336             ICancellationSignal remoteSignal = null;
    337             if (signal != null) {
    338                 signal.throwIfCanceled();
    339                 remoteSignal = mContentProvider.createCancellationSignal();
    340                 signal.setRemote(remoteSignal);
    341             }
    342             return mContentProvider.openFile(mPackageName, url, mode, remoteSignal, null);
    343         } catch (DeadObjectException e) {
    344             if (!mStable) {
    345                 mContentResolver.unstableProviderDied(mContentProvider);
    346             }
    347             throw e;
    348         } finally {
    349             afterRemote();
    350         }
    351     }
    352 
    353     /**
    354      * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}.
    355      * Note that this <em>does not</em>
    356      * take care of non-content: URIs such as file:.  It is strongly recommended
    357      * you use the {@link ContentResolver#openAssetFileDescriptor
    358      * ContentResolver.openAssetFileDescriptor} API instead.
    359      */
    360     public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri url, @NonNull String mode)
    361             throws RemoteException, FileNotFoundException {
    362         return openAssetFile(url, mode, null);
    363     }
    364 
    365     /**
    366      * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}.
    367      * Note that this <em>does not</em>
    368      * take care of non-content: URIs such as file:.  It is strongly recommended
    369      * you use the {@link ContentResolver#openAssetFileDescriptor
    370      * ContentResolver.openAssetFileDescriptor} API instead.
    371      */
    372     public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri url, @NonNull String mode,
    373             @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
    374         Preconditions.checkNotNull(url, "url");
    375         Preconditions.checkNotNull(mode, "mode");
    376 
    377         beforeRemote();
    378         try {
    379             ICancellationSignal remoteSignal = null;
    380             if (signal != null) {
    381                 signal.throwIfCanceled();
    382                 remoteSignal = mContentProvider.createCancellationSignal();
    383                 signal.setRemote(remoteSignal);
    384             }
    385             return mContentProvider.openAssetFile(mPackageName, url, mode, remoteSignal);
    386         } catch (DeadObjectException e) {
    387             if (!mStable) {
    388                 mContentResolver.unstableProviderDied(mContentProvider);
    389             }
    390             throw e;
    391         } finally {
    392             afterRemote();
    393         }
    394     }
    395 
    396     /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
    397     public final @Nullable AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull Uri uri,
    398             @NonNull String mimeType, @Nullable Bundle opts)
    399                     throws RemoteException, FileNotFoundException {
    400         return openTypedAssetFileDescriptor(uri, mimeType, opts, null);
    401     }
    402 
    403     /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
    404     public final @Nullable AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull Uri uri,
    405             @NonNull String mimeType, @Nullable Bundle opts, @Nullable CancellationSignal signal)
    406                     throws RemoteException, FileNotFoundException {
    407         Preconditions.checkNotNull(uri, "uri");
    408         Preconditions.checkNotNull(mimeType, "mimeType");
    409 
    410         beforeRemote();
    411         try {
    412             ICancellationSignal remoteSignal = null;
    413             if (signal != null) {
    414                 signal.throwIfCanceled();
    415                 remoteSignal = mContentProvider.createCancellationSignal();
    416                 signal.setRemote(remoteSignal);
    417             }
    418             return mContentProvider.openTypedAssetFile(
    419                     mPackageName, uri, mimeType, opts, remoteSignal);
    420         } catch (DeadObjectException e) {
    421             if (!mStable) {
    422                 mContentResolver.unstableProviderDied(mContentProvider);
    423             }
    424             throw e;
    425         } finally {
    426             afterRemote();
    427         }
    428     }
    429 
    430     /** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */
    431     public @NonNull ContentProviderResult[] applyBatch(
    432             @NonNull ArrayList<ContentProviderOperation> operations)
    433                     throws RemoteException, OperationApplicationException {
    434         Preconditions.checkNotNull(operations, "operations");
    435 
    436         beforeRemote();
    437         try {
    438             return mContentProvider.applyBatch(mPackageName, operations);
    439         } catch (DeadObjectException e) {
    440             if (!mStable) {
    441                 mContentResolver.unstableProviderDied(mContentProvider);
    442             }
    443             throw e;
    444         } finally {
    445             afterRemote();
    446         }
    447     }
    448 
    449     /** See {@link ContentProvider#call(String, String, Bundle)} */
    450     public @Nullable Bundle call(@NonNull String method, @Nullable String arg,
    451             @Nullable Bundle extras) throws RemoteException {
    452         Preconditions.checkNotNull(method, "method");
    453 
    454         beforeRemote();
    455         try {
    456             return mContentProvider.call(mPackageName, method, arg, extras);
    457         } catch (DeadObjectException e) {
    458             if (!mStable) {
    459                 mContentResolver.unstableProviderDied(mContentProvider);
    460             }
    461             throw e;
    462         } finally {
    463             afterRemote();
    464         }
    465     }
    466 
    467     /**
    468      * Closes this client connection, indicating to the system that the
    469      * underlying {@link ContentProvider} is no longer needed.
    470      */
    471     @Override
    472     public void close() {
    473         closeInternal();
    474     }
    475 
    476     /**
    477      * @deprecated replaced by {@link #close()}.
    478      */
    479     @Deprecated
    480     public boolean release() {
    481         return closeInternal();
    482     }
    483 
    484     private boolean closeInternal() {
    485         mCloseGuard.close();
    486         if (mClosed.compareAndSet(false, true)) {
    487             if (mStable) {
    488                 return mContentResolver.releaseProvider(mContentProvider);
    489             } else {
    490                 return mContentResolver.releaseUnstableProvider(mContentProvider);
    491             }
    492         } else {
    493             return false;
    494         }
    495     }
    496 
    497     @Override
    498     protected void finalize() throws Throwable {
    499         try {
    500             mCloseGuard.warnIfOpen();
    501             close();
    502         } finally {
    503             super.finalize();
    504         }
    505     }
    506 
    507     /**
    508      * Get a reference to the {@link ContentProvider} that is associated with this
    509      * client. If the {@link ContentProvider} is running in a different process then
    510      * null will be returned. This can be used if you know you are running in the same
    511      * process as a provider, and want to get direct access to its implementation details.
    512      *
    513      * @return If the associated {@link ContentProvider} is local, returns it.
    514      * Otherwise returns null.
    515      */
    516     public @Nullable ContentProvider getLocalContentProvider() {
    517         return ContentProvider.coerceToLocalContentProvider(mContentProvider);
    518     }
    519 
    520     /** {@hide} */
    521     public static void releaseQuietly(ContentProviderClient client) {
    522         if (client != null) {
    523             try {
    524                 client.release();
    525             } catch (Exception ignored) {
    526             }
    527         }
    528     }
    529 
    530     private class NotRespondingRunnable implements Runnable {
    531         @Override
    532         public void run() {
    533             Log.w(TAG, "Detected provider not responding: " + mContentProvider);
    534             mContentResolver.appNotRespondingViaProvider(mContentProvider);
    535         }
    536     }
    537 
    538     private final class CursorWrapperInner extends CrossProcessCursorWrapper {
    539         private final CloseGuard mCloseGuard = CloseGuard.get();
    540 
    541         CursorWrapperInner(Cursor cursor) {
    542             super(cursor);
    543             mCloseGuard.open("close");
    544         }
    545 
    546         @Override
    547         public void close() {
    548             mCloseGuard.close();
    549             super.close();
    550         }
    551 
    552         @Override
    553         protected void finalize() throws Throwable {
    554             try {
    555                 mCloseGuard.warnIfOpen();
    556                 close();
    557             } finally {
    558                 super.finalize();
    559             }
    560         }
    561     }
    562 }
    563