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
ContentProviderClient( ContentResolver contentResolver, IContentProvider contentProvider, boolean stable)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} */
setDetectNotResponding(long timeoutMillis)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 
beforeRemote()120     private void beforeRemote() {
121         if (mAnrRunnable != null) {
122             sAnrHandler.postDelayed(mAnrRunnable, mAnrTimeout);
123         }
124     }
125 
afterRemote()126     private void afterRemote() {
127         if (mAnrRunnable != null) {
128             sAnrHandler.removeCallbacks(mAnrRunnable);
129         }
130     }
131 
132     /** See {@link ContentProvider#query ContentProvider.query} */
query(@onNull Uri url, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder)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} */
query(@onNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal)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} */
query(@onNull Uri uri, @Nullable String[] projection, Bundle queryArgs, @Nullable CancellationSignal cancellationSignal)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} */
getType(@onNull Uri url)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} */
getStreamTypes(@onNull Uri url, @NonNull String mimeTypeFilter)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} */
canonicalize(@onNull Uri url)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} */
uncanonicalize(@onNull Uri url)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} */
refresh(Uri url, @Nullable Bundle args, @Nullable CancellationSignal cancellationSignal)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} */
insert(@onNull Uri url, @Nullable ContentValues initialValues)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} */
bulkInsert(@onNull Uri url, @NonNull ContentValues[] initialValues)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} */
delete(@onNull Uri url, @Nullable String selection, @Nullable String[] selectionArgs)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} */
update(@onNull Uri url, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs)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      */
openFile(@onNull Uri url, @NonNull String mode)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      */
openFile(@onNull Uri url, @NonNull String mode, @Nullable CancellationSignal signal)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      */
openAssetFile(@onNull Uri url, @NonNull String mode)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      */
openAssetFile(@onNull Uri url, @NonNull String mode, @Nullable CancellationSignal signal)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} */
openTypedAssetFileDescriptor(@onNull Uri uri, @NonNull String mimeType, @Nullable Bundle opts)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} */
openTypedAssetFileDescriptor(@onNull Uri uri, @NonNull String mimeType, @Nullable Bundle opts, @Nullable CancellationSignal signal)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} */
applyBatch( @onNull ArrayList<ContentProviderOperation> operations)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)} */
call(@onNull String method, @Nullable String arg, @Nullable Bundle extras)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
close()508     public void close() {
509         closeInternal();
510     }
511 
512     /**
513      * @deprecated replaced by {@link #close()}.
514      */
515     @Deprecated
release()516     public boolean release() {
517         return closeInternal();
518     }
519 
closeInternal()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
finalize()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      */
getLocalContentProvider()559     public @Nullable ContentProvider getLocalContentProvider() {
560         return ContentProvider.coerceToLocalContentProvider(mContentProvider);
561     }
562 
563     /** {@hide} */
releaseQuietly(ContentProviderClient client)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
run()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 
CursorWrapperInner(Cursor cursor)584         CursorWrapperInner(Cursor cursor) {
585             super(cursor);
586             mCloseGuard.open("close");
587         }
588 
589         @Override
close()590         public void close() {
591             mCloseGuard.close();
592             super.close();
593         }
594 
595         @Override
finalize()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