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
ContentProviderClient( ContentResolver contentResolver, IContentProvider contentProvider, boolean stable)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} */
setDetectNotResponding(long timeoutMillis)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 
beforeRemote()111     private void beforeRemote() {
112         if (mAnrRunnable != null) {
113             sAnrHandler.postDelayed(mAnrRunnable, mAnrTimeout);
114         }
115     }
116 
afterRemote()117     private void afterRemote() {
118         if (mAnrRunnable != null) {
119             sAnrHandler.removeCallbacks(mAnrRunnable);
120         }
121     }
122 
123     /** See {@link ContentProvider#query ContentProvider.query} */
query(@onNull Uri url, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder)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} */
query(@onNull Uri url, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal)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} */
getType(@onNull Uri url)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} */
getStreamTypes(@onNull Uri url, @NonNull String mimeTypeFilter)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} */
canonicalize(@onNull Uri url)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} */
uncanonicalize(@onNull Uri url)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} */
insert(@onNull Uri url, @Nullable ContentValues initialValues)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} */
bulkInsert(@onNull Uri url, @NonNull ContentValues[] initialValues)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} */
delete(@onNull Uri url, @Nullable String selection, @Nullable String[] selectionArgs)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} */
update(@onNull Uri url, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs)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      */
openFile(@onNull Uri url, @NonNull String mode)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      */
openFile(@onNull Uri url, @NonNull String mode, @Nullable CancellationSignal signal)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      */
openAssetFile(@onNull Uri url, @NonNull String mode)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      */
openAssetFile(@onNull Uri url, @NonNull String mode, @Nullable CancellationSignal signal)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} */
openTypedAssetFileDescriptor(@onNull Uri uri, @NonNull String mimeType, @Nullable Bundle opts)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} */
openTypedAssetFileDescriptor(@onNull Uri uri, @NonNull String mimeType, @Nullable Bundle opts, @Nullable CancellationSignal signal)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} */
applyBatch( @onNull ArrayList<ContentProviderOperation> operations)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)} */
call(@onNull String method, @Nullable String arg, @Nullable Bundle extras)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
close()472     public void close() {
473         closeInternal();
474     }
475 
476     /**
477      * @deprecated replaced by {@link #close()}.
478      */
479     @Deprecated
release()480     public boolean release() {
481         return closeInternal();
482     }
483 
closeInternal()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
finalize()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      */
getLocalContentProvider()516     public @Nullable ContentProvider getLocalContentProvider() {
517         return ContentProvider.coerceToLocalContentProvider(mContentProvider);
518     }
519 
520     /** {@hide} */
releaseQuietly(ContentProviderClient client)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
run()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 
CursorWrapperInner(Cursor cursor)541         CursorWrapperInner(Cursor cursor) {
542             super(cursor);
543             mCloseGuard.open("close");
544         }
545 
546         @Override
close()547         public void close() {
548             mCloseGuard.close();
549             super.close();
550         }
551 
552         @Override
finalize()553         protected void finalize() throws Throwable {
554             try {
555                 mCloseGuard.warnIfOpen();
556                 close();
557             } finally {
558                 super.finalize();
559             }
560         }
561     }
562 }
563