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