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
17package android.content;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.content.res.AssetFileDescriptor;
22import android.database.CrossProcessCursorWrapper;
23import android.database.Cursor;
24import android.net.Uri;
25import android.os.Bundle;
26import android.os.CancellationSignal;
27import android.os.DeadObjectException;
28import android.os.Handler;
29import android.os.ICancellationSignal;
30import android.os.Looper;
31import android.os.ParcelFileDescriptor;
32import android.os.RemoteException;
33import android.util.Log;
34
35import com.android.internal.annotations.GuardedBy;
36import com.android.internal.annotations.VisibleForTesting;
37import com.android.internal.util.Preconditions;
38
39import dalvik.system.CloseGuard;
40
41import java.io.FileNotFoundException;
42import java.util.ArrayList;
43import 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 */
63public class ContentProviderClient implements AutoCloseable {
64    private static final String TAG = "ContentProviderClient";
65
66    @GuardedBy("ContentProviderClient.class")
67    private static Handler sAnrHandler;
68
69    private final ContentResolver mContentResolver;
70    private final IContentProvider mContentProvider;
71    private final String mPackageName;
72    private final boolean mStable;
73
74    private final AtomicBoolean mClosed = new AtomicBoolean();
75    private final CloseGuard mCloseGuard = CloseGuard.get();
76
77    private long mAnrTimeout;
78    private NotRespondingRunnable mAnrRunnable;
79
80    /** {@hide} */
81    @VisibleForTesting
82    public ContentProviderClient(
83            ContentResolver contentResolver, IContentProvider contentProvider, boolean stable) {
84        mContentResolver = contentResolver;
85        mContentProvider = contentProvider;
86        mPackageName = contentResolver.mPackageName;
87
88        mStable = stable;
89
90        mCloseGuard.open("close");
91    }
92
93    /** {@hide} */
94    public void setDetectNotResponding(long timeoutMillis) {
95        synchronized (ContentProviderClient.class) {
96            mAnrTimeout = timeoutMillis;
97
98            if (timeoutMillis > 0) {
99                if (mAnrRunnable == null) {
100                    mAnrRunnable = new NotRespondingRunnable();
101                }
102                if (sAnrHandler == null) {
103                    sAnrHandler = new Handler(Looper.getMainLooper(), null, true /* async */);
104                }
105            } else {
106                mAnrRunnable = null;
107            }
108        }
109    }
110
111    private void beforeRemote() {
112        if (mAnrRunnable != null) {
113            sAnrHandler.postDelayed(mAnrRunnable, mAnrTimeout);
114        }
115    }
116
117    private void afterRemote() {
118        if (mAnrRunnable != null) {
119            sAnrHandler.removeCallbacks(mAnrRunnable);
120        }
121    }
122
123    /** See {@link ContentProvider#query ContentProvider.query} */
124    public @Nullable Cursor query(@NonNull Uri url, @Nullable String[] projection,
125            @Nullable String selection, @Nullable String[] selectionArgs,
126            @Nullable String sortOrder) throws RemoteException {
127        return query(url, projection, selection,  selectionArgs, sortOrder, null);
128    }
129
130    /** See {@link ContentProvider#query ContentProvider.query} */
131    public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
132            @Nullable String selection, @Nullable String[] selectionArgs,
133            @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal)
134                    throws RemoteException {
135        Bundle queryArgs =
136                ContentResolver.createSqlQueryBundle(selection, selectionArgs, sortOrder);
137        return query(uri, projection, queryArgs, cancellationSignal);
138    }
139
140    /** See {@link ContentProvider#query ContentProvider.query} */
141    public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
142            Bundle queryArgs, @Nullable CancellationSignal cancellationSignal)
143                    throws RemoteException {
144        Preconditions.checkNotNull(uri, "url");
145
146        beforeRemote();
147        try {
148            ICancellationSignal remoteCancellationSignal = null;
149            if (cancellationSignal != null) {
150                cancellationSignal.throwIfCanceled();
151                remoteCancellationSignal = mContentProvider.createCancellationSignal();
152                cancellationSignal.setRemote(remoteCancellationSignal);
153            }
154            final Cursor cursor = mContentProvider.query(
155                    mPackageName, uri, projection, queryArgs, remoteCancellationSignal);
156            if (cursor == null) {
157                return null;
158            }
159            return new CursorWrapperInner(cursor);
160        } catch (DeadObjectException e) {
161            if (!mStable) {
162                mContentResolver.unstableProviderDied(mContentProvider);
163            }
164            throw e;
165        } finally {
166            afterRemote();
167        }
168    }
169
170    /** See {@link ContentProvider#getType ContentProvider.getType} */
171    public @Nullable String getType(@NonNull Uri url) throws RemoteException {
172        Preconditions.checkNotNull(url, "url");
173
174        beforeRemote();
175        try {
176            return mContentProvider.getType(url);
177        } catch (DeadObjectException e) {
178            if (!mStable) {
179                mContentResolver.unstableProviderDied(mContentProvider);
180            }
181            throw e;
182        } finally {
183            afterRemote();
184        }
185    }
186
187    /** See {@link ContentProvider#getStreamTypes ContentProvider.getStreamTypes} */
188    public @Nullable String[] getStreamTypes(@NonNull Uri url, @NonNull String mimeTypeFilter)
189            throws RemoteException {
190        Preconditions.checkNotNull(url, "url");
191        Preconditions.checkNotNull(mimeTypeFilter, "mimeTypeFilter");
192
193        beforeRemote();
194        try {
195            return mContentProvider.getStreamTypes(url, mimeTypeFilter);
196        } catch (DeadObjectException e) {
197            if (!mStable) {
198                mContentResolver.unstableProviderDied(mContentProvider);
199            }
200            throw e;
201        } finally {
202            afterRemote();
203        }
204    }
205
206    /** See {@link ContentProvider#canonicalize} */
207    public final @Nullable Uri canonicalize(@NonNull Uri url) throws RemoteException {
208        Preconditions.checkNotNull(url, "url");
209
210        beforeRemote();
211        try {
212            return mContentProvider.canonicalize(mPackageName, url);
213        } catch (DeadObjectException e) {
214            if (!mStable) {
215                mContentResolver.unstableProviderDied(mContentProvider);
216            }
217            throw e;
218        } finally {
219            afterRemote();
220        }
221    }
222
223    /** See {@link ContentProvider#uncanonicalize} */
224    public final @Nullable Uri uncanonicalize(@NonNull Uri url) throws RemoteException {
225        Preconditions.checkNotNull(url, "url");
226
227        beforeRemote();
228        try {
229            return mContentProvider.uncanonicalize(mPackageName, url);
230        } catch (DeadObjectException e) {
231            if (!mStable) {
232                mContentResolver.unstableProviderDied(mContentProvider);
233            }
234            throw e;
235        } finally {
236            afterRemote();
237        }
238    }
239
240    /** See {@link ContentProvider#refresh} */
241    public boolean refresh(Uri url, @Nullable Bundle args,
242            @Nullable CancellationSignal cancellationSignal) throws RemoteException {
243        Preconditions.checkNotNull(url, "url");
244
245        beforeRemote();
246        try {
247            ICancellationSignal remoteCancellationSignal = null;
248            if (cancellationSignal != null) {
249                cancellationSignal.throwIfCanceled();
250                remoteCancellationSignal = mContentProvider.createCancellationSignal();
251                cancellationSignal.setRemote(remoteCancellationSignal);
252            }
253            return mContentProvider.refresh(mPackageName, url, args, remoteCancellationSignal);
254        } catch (DeadObjectException e) {
255            if (!mStable) {
256                mContentResolver.unstableProviderDied(mContentProvider);
257            }
258            throw e;
259        } finally {
260            afterRemote();
261        }
262    }
263
264    /** See {@link ContentProvider#insert ContentProvider.insert} */
265    public @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues initialValues)
266            throws RemoteException {
267        Preconditions.checkNotNull(url, "url");
268
269        beforeRemote();
270        try {
271            return mContentProvider.insert(mPackageName, url, initialValues);
272        } catch (DeadObjectException e) {
273            if (!mStable) {
274                mContentResolver.unstableProviderDied(mContentProvider);
275            }
276            throw e;
277        } finally {
278            afterRemote();
279        }
280    }
281
282    /** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */
283    public int bulkInsert(@NonNull Uri url, @NonNull ContentValues[] initialValues)
284            throws RemoteException {
285        Preconditions.checkNotNull(url, "url");
286        Preconditions.checkNotNull(initialValues, "initialValues");
287
288        beforeRemote();
289        try {
290            return mContentProvider.bulkInsert(mPackageName, url, initialValues);
291        } catch (DeadObjectException e) {
292            if (!mStable) {
293                mContentResolver.unstableProviderDied(mContentProvider);
294            }
295            throw e;
296        } finally {
297            afterRemote();
298        }
299    }
300
301    /** See {@link ContentProvider#delete ContentProvider.delete} */
302    public int delete(@NonNull Uri url, @Nullable String selection,
303            @Nullable String[] selectionArgs) throws RemoteException {
304        Preconditions.checkNotNull(url, "url");
305
306        beforeRemote();
307        try {
308            return mContentProvider.delete(mPackageName, url, selection, selectionArgs);
309        } catch (DeadObjectException e) {
310            if (!mStable) {
311                mContentResolver.unstableProviderDied(mContentProvider);
312            }
313            throw e;
314        } finally {
315            afterRemote();
316        }
317    }
318
319    /** See {@link ContentProvider#update ContentProvider.update} */
320    public int update(@NonNull Uri url, @Nullable ContentValues values, @Nullable String selection,
321            @Nullable String[] selectionArgs) throws RemoteException {
322        Preconditions.checkNotNull(url, "url");
323
324        beforeRemote();
325        try {
326            return mContentProvider.update(mPackageName, url, values, selection, selectionArgs);
327        } catch (DeadObjectException e) {
328            if (!mStable) {
329                mContentResolver.unstableProviderDied(mContentProvider);
330            }
331            throw e;
332        } finally {
333            afterRemote();
334        }
335    }
336
337    /**
338     * See {@link ContentProvider#openFile ContentProvider.openFile}.  Note that
339     * this <em>does not</em>
340     * take care of non-content: URIs such as file:.  It is strongly recommended
341     * you use the {@link ContentResolver#openFileDescriptor
342     * ContentResolver.openFileDescriptor} API instead.
343     */
344    public @Nullable ParcelFileDescriptor openFile(@NonNull Uri url, @NonNull String mode)
345            throws RemoteException, FileNotFoundException {
346        return openFile(url, mode, null);
347    }
348
349    /**
350     * See {@link ContentProvider#openFile ContentProvider.openFile}.  Note that
351     * this <em>does not</em>
352     * take care of non-content: URIs such as file:.  It is strongly recommended
353     * you use the {@link ContentResolver#openFileDescriptor
354     * ContentResolver.openFileDescriptor} API instead.
355     */
356    public @Nullable ParcelFileDescriptor openFile(@NonNull Uri url, @NonNull String mode,
357            @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
358        Preconditions.checkNotNull(url, "url");
359        Preconditions.checkNotNull(mode, "mode");
360
361        beforeRemote();
362        try {
363            ICancellationSignal remoteSignal = null;
364            if (signal != null) {
365                signal.throwIfCanceled();
366                remoteSignal = mContentProvider.createCancellationSignal();
367                signal.setRemote(remoteSignal);
368            }
369            return mContentProvider.openFile(mPackageName, url, mode, remoteSignal, null);
370        } catch (DeadObjectException e) {
371            if (!mStable) {
372                mContentResolver.unstableProviderDied(mContentProvider);
373            }
374            throw e;
375        } finally {
376            afterRemote();
377        }
378    }
379
380    /**
381     * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}.
382     * Note that this <em>does not</em>
383     * take care of non-content: URIs such as file:.  It is strongly recommended
384     * you use the {@link ContentResolver#openAssetFileDescriptor
385     * ContentResolver.openAssetFileDescriptor} API instead.
386     */
387    public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri url, @NonNull String mode)
388            throws RemoteException, FileNotFoundException {
389        return openAssetFile(url, mode, null);
390    }
391
392    /**
393     * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}.
394     * Note that this <em>does not</em>
395     * take care of non-content: URIs such as file:.  It is strongly recommended
396     * you use the {@link ContentResolver#openAssetFileDescriptor
397     * ContentResolver.openAssetFileDescriptor} API instead.
398     */
399    public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri url, @NonNull String mode,
400            @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
401        Preconditions.checkNotNull(url, "url");
402        Preconditions.checkNotNull(mode, "mode");
403
404        beforeRemote();
405        try {
406            ICancellationSignal remoteSignal = null;
407            if (signal != null) {
408                signal.throwIfCanceled();
409                remoteSignal = mContentProvider.createCancellationSignal();
410                signal.setRemote(remoteSignal);
411            }
412            return mContentProvider.openAssetFile(mPackageName, url, mode, remoteSignal);
413        } catch (DeadObjectException e) {
414            if (!mStable) {
415                mContentResolver.unstableProviderDied(mContentProvider);
416            }
417            throw e;
418        } finally {
419            afterRemote();
420        }
421    }
422
423    /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
424    public final @Nullable AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull Uri uri,
425            @NonNull String mimeType, @Nullable Bundle opts)
426                    throws RemoteException, FileNotFoundException {
427        return openTypedAssetFileDescriptor(uri, mimeType, opts, null);
428    }
429
430    /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
431    public final @Nullable AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull Uri uri,
432            @NonNull String mimeType, @Nullable Bundle opts, @Nullable CancellationSignal signal)
433                    throws RemoteException, FileNotFoundException {
434        Preconditions.checkNotNull(uri, "uri");
435        Preconditions.checkNotNull(mimeType, "mimeType");
436
437        beforeRemote();
438        try {
439            ICancellationSignal remoteSignal = null;
440            if (signal != null) {
441                signal.throwIfCanceled();
442                remoteSignal = mContentProvider.createCancellationSignal();
443                signal.setRemote(remoteSignal);
444            }
445            return mContentProvider.openTypedAssetFile(
446                    mPackageName, uri, mimeType, opts, remoteSignal);
447        } catch (DeadObjectException e) {
448            if (!mStable) {
449                mContentResolver.unstableProviderDied(mContentProvider);
450            }
451            throw e;
452        } finally {
453            afterRemote();
454        }
455    }
456
457    /** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */
458    public @NonNull ContentProviderResult[] applyBatch(
459            @NonNull ArrayList<ContentProviderOperation> operations)
460                    throws RemoteException, OperationApplicationException {
461        Preconditions.checkNotNull(operations, "operations");
462
463        beforeRemote();
464        try {
465            return mContentProvider.applyBatch(mPackageName, operations);
466        } catch (DeadObjectException e) {
467            if (!mStable) {
468                mContentResolver.unstableProviderDied(mContentProvider);
469            }
470            throw e;
471        } finally {
472            afterRemote();
473        }
474    }
475
476    /** See {@link ContentProvider#call(String, String, Bundle)} */
477    public @Nullable Bundle call(@NonNull String method, @Nullable String arg,
478            @Nullable Bundle extras) throws RemoteException {
479        Preconditions.checkNotNull(method, "method");
480
481        beforeRemote();
482        try {
483            return mContentProvider.call(mPackageName, method, arg, extras);
484        } catch (DeadObjectException e) {
485            if (!mStable) {
486                mContentResolver.unstableProviderDied(mContentProvider);
487            }
488            throw e;
489        } finally {
490            afterRemote();
491        }
492    }
493
494    /**
495     * Closes this client connection, indicating to the system that the
496     * underlying {@link ContentProvider} is no longer needed.
497     */
498    @Override
499    public void close() {
500        closeInternal();
501    }
502
503    /**
504     * @deprecated replaced by {@link #close()}.
505     */
506    @Deprecated
507    public boolean release() {
508        return closeInternal();
509    }
510
511    private boolean closeInternal() {
512        mCloseGuard.close();
513        if (mClosed.compareAndSet(false, true)) {
514            if (mStable) {
515                return mContentResolver.releaseProvider(mContentProvider);
516            } else {
517                return mContentResolver.releaseUnstableProvider(mContentProvider);
518            }
519        } else {
520            return false;
521        }
522    }
523
524    @Override
525    protected void finalize() throws Throwable {
526        try {
527            if (mCloseGuard != null) {
528                mCloseGuard.warnIfOpen();
529            }
530
531            close();
532        } finally {
533            super.finalize();
534        }
535    }
536
537    /**
538     * Get a reference to the {@link ContentProvider} that is associated with this
539     * client. If the {@link ContentProvider} is running in a different process then
540     * null will be returned. This can be used if you know you are running in the same
541     * process as a provider, and want to get direct access to its implementation details.
542     *
543     * @return If the associated {@link ContentProvider} is local, returns it.
544     * Otherwise returns null.
545     */
546    public @Nullable ContentProvider getLocalContentProvider() {
547        return ContentProvider.coerceToLocalContentProvider(mContentProvider);
548    }
549
550    /** {@hide} */
551    public static void releaseQuietly(ContentProviderClient client) {
552        if (client != null) {
553            try {
554                client.release();
555            } catch (Exception ignored) {
556            }
557        }
558    }
559
560    private class NotRespondingRunnable implements Runnable {
561        @Override
562        public void run() {
563            Log.w(TAG, "Detected provider not responding: " + mContentProvider);
564            mContentResolver.appNotRespondingViaProvider(mContentProvider);
565        }
566    }
567
568    private final class CursorWrapperInner extends CrossProcessCursorWrapper {
569        private final CloseGuard mCloseGuard = CloseGuard.get();
570
571        CursorWrapperInner(Cursor cursor) {
572            super(cursor);
573            mCloseGuard.open("close");
574        }
575
576        @Override
577        public void close() {
578            mCloseGuard.close();
579            super.close();
580        }
581
582        @Override
583        protected void finalize() throws Throwable {
584            try {
585                if (mCloseGuard != null) {
586                    mCloseGuard.warnIfOpen();
587                }
588
589                close();
590            } finally {
591                super.finalize();
592            }
593        }
594    }
595}
596