ContentProviderClient.java revision 13deeafe46ac8669b0d66fbc3c8a8f8fec023d67
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 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            return new CursorWrapperInner(cursor);
151        } catch (DeadObjectException e) {
152            if (!mStable) {
153                mContentResolver.unstableProviderDied(mContentProvider);
154            }
155            throw e;
156        } finally {
157            afterRemote();
158        }
159    }
160
161    /** See {@link ContentProvider#getType ContentProvider.getType} */
162    public @Nullable String getType(@NonNull Uri url) throws RemoteException {
163        Preconditions.checkNotNull(url, "url");
164
165        beforeRemote();
166        try {
167            return mContentProvider.getType(url);
168        } catch (DeadObjectException e) {
169            if (!mStable) {
170                mContentResolver.unstableProviderDied(mContentProvider);
171            }
172            throw e;
173        } finally {
174            afterRemote();
175        }
176    }
177
178    /** See {@link ContentProvider#getStreamTypes ContentProvider.getStreamTypes} */
179    public @Nullable String[] getStreamTypes(@NonNull Uri url, @NonNull String mimeTypeFilter)
180            throws RemoteException {
181        Preconditions.checkNotNull(url, "url");
182        Preconditions.checkNotNull(mimeTypeFilter, "mimeTypeFilter");
183
184        beforeRemote();
185        try {
186            return mContentProvider.getStreamTypes(url, mimeTypeFilter);
187        } catch (DeadObjectException e) {
188            if (!mStable) {
189                mContentResolver.unstableProviderDied(mContentProvider);
190            }
191            throw e;
192        } finally {
193            afterRemote();
194        }
195    }
196
197    /** See {@link ContentProvider#canonicalize} */
198    public final @Nullable Uri canonicalize(@NonNull Uri url) throws RemoteException {
199        Preconditions.checkNotNull(url, "url");
200
201        beforeRemote();
202        try {
203            return mContentProvider.canonicalize(mPackageName, url);
204        } catch (DeadObjectException e) {
205            if (!mStable) {
206                mContentResolver.unstableProviderDied(mContentProvider);
207            }
208            throw e;
209        } finally {
210            afterRemote();
211        }
212    }
213
214    /** See {@link ContentProvider#uncanonicalize} */
215    public final @Nullable Uri uncanonicalize(@NonNull Uri url) throws RemoteException {
216        Preconditions.checkNotNull(url, "url");
217
218        beforeRemote();
219        try {
220            return mContentProvider.uncanonicalize(mPackageName, url);
221        } catch (DeadObjectException e) {
222            if (!mStable) {
223                mContentResolver.unstableProviderDied(mContentProvider);
224            }
225            throw e;
226        } finally {
227            afterRemote();
228        }
229    }
230
231    /** @hide */
232    public boolean refresh(Uri url, @Nullable Bundle args,
233            @Nullable CancellationSignal cancellationSignal) throws RemoteException {
234        Preconditions.checkNotNull(url, "url");
235
236        beforeRemote();
237        try {
238            ICancellationSignal remoteCancellationSignal = null;
239            if (cancellationSignal != null) {
240                cancellationSignal.throwIfCanceled();
241                remoteCancellationSignal = mContentProvider.createCancellationSignal();
242                cancellationSignal.setRemote(remoteCancellationSignal);
243            }
244            return mContentProvider.refresh(mPackageName, url, args, remoteCancellationSignal);
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#insert ContentProvider.insert} */
256    public @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues initialValues)
257            throws RemoteException {
258        Preconditions.checkNotNull(url, "url");
259
260        beforeRemote();
261        try {
262            return mContentProvider.insert(mPackageName, url, initialValues);
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#bulkInsert ContentProvider.bulkInsert} */
274    public int bulkInsert(@NonNull Uri url, @NonNull ContentValues[] initialValues)
275            throws RemoteException {
276        Preconditions.checkNotNull(url, "url");
277        Preconditions.checkNotNull(initialValues, "initialValues");
278
279        beforeRemote();
280        try {
281            return mContentProvider.bulkInsert(mPackageName, url, initialValues);
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#delete ContentProvider.delete} */
293    public int delete(@NonNull Uri url, @Nullable String selection,
294            @Nullable String[] selectionArgs) throws RemoteException {
295        Preconditions.checkNotNull(url, "url");
296
297        beforeRemote();
298        try {
299            return mContentProvider.delete(mPackageName, url, 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    /** See {@link ContentProvider#update ContentProvider.update} */
311    public int update(@NonNull Uri url, @Nullable ContentValues values, @Nullable String selection,
312            @Nullable String[] selectionArgs) throws RemoteException {
313        Preconditions.checkNotNull(url, "url");
314
315        beforeRemote();
316        try {
317            return mContentProvider.update(mPackageName, url, values, 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    /**
329     * See {@link ContentProvider#openFile ContentProvider.openFile}.  Note that
330     * this <em>does not</em>
331     * take care of non-content: URIs such as file:.  It is strongly recommended
332     * you use the {@link ContentResolver#openFileDescriptor
333     * ContentResolver.openFileDescriptor} API instead.
334     */
335    public @Nullable ParcelFileDescriptor openFile(@NonNull Uri url, @NonNull String mode)
336            throws RemoteException, FileNotFoundException {
337        return openFile(url, mode, null);
338    }
339
340    /**
341     * See {@link ContentProvider#openFile ContentProvider.openFile}.  Note that
342     * this <em>does not</em>
343     * take care of non-content: URIs such as file:.  It is strongly recommended
344     * you use the {@link ContentResolver#openFileDescriptor
345     * ContentResolver.openFileDescriptor} API instead.
346     */
347    public @Nullable ParcelFileDescriptor openFile(@NonNull Uri url, @NonNull String mode,
348            @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
349        Preconditions.checkNotNull(url, "url");
350        Preconditions.checkNotNull(mode, "mode");
351
352        beforeRemote();
353        try {
354            ICancellationSignal remoteSignal = null;
355            if (signal != null) {
356                signal.throwIfCanceled();
357                remoteSignal = mContentProvider.createCancellationSignal();
358                signal.setRemote(remoteSignal);
359            }
360            return mContentProvider.openFile(mPackageName, url, mode, remoteSignal, null);
361        } catch (DeadObjectException e) {
362            if (!mStable) {
363                mContentResolver.unstableProviderDied(mContentProvider);
364            }
365            throw e;
366        } finally {
367            afterRemote();
368        }
369    }
370
371    /**
372     * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}.
373     * Note that this <em>does not</em>
374     * take care of non-content: URIs such as file:.  It is strongly recommended
375     * you use the {@link ContentResolver#openAssetFileDescriptor
376     * ContentResolver.openAssetFileDescriptor} API instead.
377     */
378    public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri url, @NonNull String mode)
379            throws RemoteException, FileNotFoundException {
380        return openAssetFile(url, mode, null);
381    }
382
383    /**
384     * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}.
385     * Note that this <em>does not</em>
386     * take care of non-content: URIs such as file:.  It is strongly recommended
387     * you use the {@link ContentResolver#openAssetFileDescriptor
388     * ContentResolver.openAssetFileDescriptor} API instead.
389     */
390    public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri url, @NonNull String mode,
391            @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
392        Preconditions.checkNotNull(url, "url");
393        Preconditions.checkNotNull(mode, "mode");
394
395        beforeRemote();
396        try {
397            ICancellationSignal remoteSignal = null;
398            if (signal != null) {
399                signal.throwIfCanceled();
400                remoteSignal = mContentProvider.createCancellationSignal();
401                signal.setRemote(remoteSignal);
402            }
403            return mContentProvider.openAssetFile(mPackageName, url, mode, remoteSignal);
404        } catch (DeadObjectException e) {
405            if (!mStable) {
406                mContentResolver.unstableProviderDied(mContentProvider);
407            }
408            throw e;
409        } finally {
410            afterRemote();
411        }
412    }
413
414    /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
415    public final @Nullable AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull Uri uri,
416            @NonNull String mimeType, @Nullable Bundle opts)
417                    throws RemoteException, FileNotFoundException {
418        return openTypedAssetFileDescriptor(uri, mimeType, opts, null);
419    }
420
421    /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
422    public final @Nullable AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull Uri uri,
423            @NonNull String mimeType, @Nullable Bundle opts, @Nullable CancellationSignal signal)
424                    throws RemoteException, FileNotFoundException {
425        Preconditions.checkNotNull(uri, "uri");
426        Preconditions.checkNotNull(mimeType, "mimeType");
427
428        beforeRemote();
429        try {
430            ICancellationSignal remoteSignal = null;
431            if (signal != null) {
432                signal.throwIfCanceled();
433                remoteSignal = mContentProvider.createCancellationSignal();
434                signal.setRemote(remoteSignal);
435            }
436            return mContentProvider.openTypedAssetFile(
437                    mPackageName, uri, mimeType, opts, remoteSignal);
438        } catch (DeadObjectException e) {
439            if (!mStable) {
440                mContentResolver.unstableProviderDied(mContentProvider);
441            }
442            throw e;
443        } finally {
444            afterRemote();
445        }
446    }
447
448    /** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */
449    public @NonNull ContentProviderResult[] applyBatch(
450            @NonNull ArrayList<ContentProviderOperation> operations)
451                    throws RemoteException, OperationApplicationException {
452        Preconditions.checkNotNull(operations, "operations");
453
454        beforeRemote();
455        try {
456            return mContentProvider.applyBatch(mPackageName, operations);
457        } catch (DeadObjectException e) {
458            if (!mStable) {
459                mContentResolver.unstableProviderDied(mContentProvider);
460            }
461            throw e;
462        } finally {
463            afterRemote();
464        }
465    }
466
467    /** See {@link ContentProvider#call(String, String, Bundle)} */
468    public @Nullable Bundle call(@NonNull String method, @Nullable String arg,
469            @Nullable Bundle extras) throws RemoteException {
470        Preconditions.checkNotNull(method, "method");
471
472        beforeRemote();
473        try {
474            return mContentProvider.call(mPackageName, method, arg, extras);
475        } catch (DeadObjectException e) {
476            if (!mStable) {
477                mContentResolver.unstableProviderDied(mContentProvider);
478            }
479            throw e;
480        } finally {
481            afterRemote();
482        }
483    }
484
485    /**
486     * Closes this client connection, indicating to the system that the
487     * underlying {@link ContentProvider} is no longer needed.
488     */
489    @Override
490    public void close() {
491        closeInternal();
492    }
493
494    /**
495     * @deprecated replaced by {@link #close()}.
496     */
497    @Deprecated
498    public boolean release() {
499        return closeInternal();
500    }
501
502    private boolean closeInternal() {
503        mCloseGuard.close();
504        if (mClosed.compareAndSet(false, true)) {
505            if (mStable) {
506                return mContentResolver.releaseProvider(mContentProvider);
507            } else {
508                return mContentResolver.releaseUnstableProvider(mContentProvider);
509            }
510        } else {
511            return false;
512        }
513    }
514
515    @Override
516    protected void finalize() throws Throwable {
517        try {
518            mCloseGuard.warnIfOpen();
519            close();
520        } finally {
521            super.finalize();
522        }
523    }
524
525    /**
526     * Get a reference to the {@link ContentProvider} that is associated with this
527     * client. If the {@link ContentProvider} is running in a different process then
528     * null will be returned. This can be used if you know you are running in the same
529     * process as a provider, and want to get direct access to its implementation details.
530     *
531     * @return If the associated {@link ContentProvider} is local, returns it.
532     * Otherwise returns null.
533     */
534    public @Nullable ContentProvider getLocalContentProvider() {
535        return ContentProvider.coerceToLocalContentProvider(mContentProvider);
536    }
537
538    /** {@hide} */
539    public static void releaseQuietly(ContentProviderClient client) {
540        if (client != null) {
541            try {
542                client.release();
543            } catch (Exception ignored) {
544            }
545        }
546    }
547
548    private class NotRespondingRunnable implements Runnable {
549        @Override
550        public void run() {
551            Log.w(TAG, "Detected provider not responding: " + mContentProvider);
552            mContentResolver.appNotRespondingViaProvider(mContentProvider);
553        }
554    }
555
556    private final class CursorWrapperInner extends CrossProcessCursorWrapper {
557        private final CloseGuard mCloseGuard = CloseGuard.get();
558
559        CursorWrapperInner(Cursor cursor) {
560            super(cursor);
561            mCloseGuard.open("close");
562        }
563
564        @Override
565        public void close() {
566            mCloseGuard.close();
567            super.close();
568        }
569
570        @Override
571        protected void finalize() throws Throwable {
572            try {
573                mCloseGuard.warnIfOpen();
574                close();
575            } finally {
576                super.finalize();
577            }
578        }
579    }
580}
581