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