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.Cursor;
23import android.net.Uri;
24import android.os.Bundle;
25import android.os.CancellationSignal;
26import android.os.DeadObjectException;
27import android.os.Handler;
28import android.os.ICancellationSignal;
29import android.os.Looper;
30import android.os.ParcelFileDescriptor;
31import android.os.RemoteException;
32import android.util.Log;
33
34import com.android.internal.annotations.GuardedBy;
35import com.android.internal.util.Preconditions;
36
37import dalvik.system.CloseGuard;
38
39import java.io.FileNotFoundException;
40import java.util.ArrayList;
41
42/**
43 * The public interface object used to interact with a {@link ContentProvider}. This is obtained by
44 * calling {@link ContentResolver#acquireContentProviderClient}. This object must be released
45 * using {@link #release} in order to indicate to the system that the {@link ContentProvider} is
46 * no longer needed and can be killed to free up resources.
47 *
48 * <p>Note that you should generally create a new ContentProviderClient instance
49 * for each thread that will be performing operations.  Unlike
50 * {@link ContentResolver}, the methods here such as {@link #query} and
51 * {@link #openFile} are not thread safe -- you must not call
52 * {@link #release()} on the ContentProviderClient those calls are made from
53 * until you are finished with the data they have returned.
54 */
55public class ContentProviderClient {
56    private static final String TAG = "ContentProviderClient";
57
58    @GuardedBy("ContentProviderClient.class")
59    private static Handler sAnrHandler;
60
61    private final ContentResolver mContentResolver;
62    private final IContentProvider mContentProvider;
63    private final String mPackageName;
64    private final boolean mStable;
65
66    private final CloseGuard mGuard = CloseGuard.get();
67
68    private long mAnrTimeout;
69    private NotRespondingRunnable mAnrRunnable;
70
71    private boolean mReleased;
72
73    /** {@hide} */
74    ContentProviderClient(
75            ContentResolver contentResolver, IContentProvider contentProvider, boolean stable) {
76        mContentResolver = contentResolver;
77        mContentProvider = contentProvider;
78        mPackageName = contentResolver.mPackageName;
79        mStable = stable;
80
81        mGuard.open("release");
82    }
83
84    /** {@hide} */
85    public void setDetectNotResponding(long timeoutMillis) {
86        synchronized (ContentProviderClient.class) {
87            mAnrTimeout = timeoutMillis;
88
89            if (timeoutMillis > 0) {
90                if (mAnrRunnable == null) {
91                    mAnrRunnable = new NotRespondingRunnable();
92                }
93                if (sAnrHandler == null) {
94                    sAnrHandler = new Handler(Looper.getMainLooper(), null, true /* async */);
95                }
96            } else {
97                mAnrRunnable = null;
98            }
99        }
100    }
101
102    private void beforeRemote() {
103        if (mAnrRunnable != null) {
104            sAnrHandler.postDelayed(mAnrRunnable, mAnrTimeout);
105        }
106    }
107
108    private void afterRemote() {
109        if (mAnrRunnable != null) {
110            sAnrHandler.removeCallbacks(mAnrRunnable);
111        }
112    }
113
114    /** See {@link ContentProvider#query ContentProvider.query} */
115    public @Nullable Cursor query(@NonNull Uri url, @Nullable String[] projection,
116            @Nullable String selection, @Nullable String[] selectionArgs,
117            @Nullable String sortOrder) throws RemoteException {
118        return query(url, projection, selection,  selectionArgs, sortOrder, null);
119    }
120
121    /** See {@link ContentProvider#query ContentProvider.query} */
122    public @Nullable Cursor query(@NonNull Uri url, @Nullable String[] projection,
123            @Nullable String selection, @Nullable String[] selectionArgs,
124            @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal)
125                    throws RemoteException {
126        Preconditions.checkNotNull(url, "url");
127
128        beforeRemote();
129        try {
130            ICancellationSignal remoteCancellationSignal = null;
131            if (cancellationSignal != null) {
132                cancellationSignal.throwIfCanceled();
133                remoteCancellationSignal = mContentProvider.createCancellationSignal();
134                cancellationSignal.setRemote(remoteCancellationSignal);
135            }
136            return mContentProvider.query(mPackageName, url, projection, selection, selectionArgs,
137                    sortOrder, remoteCancellationSignal);
138        } catch (DeadObjectException e) {
139            if (!mStable) {
140                mContentResolver.unstableProviderDied(mContentProvider);
141            }
142            throw e;
143        } finally {
144            afterRemote();
145        }
146    }
147
148    /** See {@link ContentProvider#getType ContentProvider.getType} */
149    public @Nullable String getType(@NonNull Uri url) throws RemoteException {
150        Preconditions.checkNotNull(url, "url");
151
152        beforeRemote();
153        try {
154            return mContentProvider.getType(url);
155        } catch (DeadObjectException e) {
156            if (!mStable) {
157                mContentResolver.unstableProviderDied(mContentProvider);
158            }
159            throw e;
160        } finally {
161            afterRemote();
162        }
163    }
164
165    /** See {@link ContentProvider#getStreamTypes ContentProvider.getStreamTypes} */
166    public @Nullable String[] getStreamTypes(@NonNull Uri url, @NonNull String mimeTypeFilter)
167            throws RemoteException {
168        Preconditions.checkNotNull(url, "url");
169        Preconditions.checkNotNull(mimeTypeFilter, "mimeTypeFilter");
170
171        beforeRemote();
172        try {
173            return mContentProvider.getStreamTypes(url, mimeTypeFilter);
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#canonicalize} */
185    public final @Nullable Uri canonicalize(@NonNull Uri url) throws RemoteException {
186        Preconditions.checkNotNull(url, "url");
187
188        beforeRemote();
189        try {
190            return mContentProvider.canonicalize(mPackageName, url);
191        } catch (DeadObjectException e) {
192            if (!mStable) {
193                mContentResolver.unstableProviderDied(mContentProvider);
194            }
195            throw e;
196        } finally {
197            afterRemote();
198        }
199    }
200
201    /** See {@link ContentProvider#uncanonicalize} */
202    public final @Nullable Uri uncanonicalize(@NonNull Uri url) throws RemoteException {
203        Preconditions.checkNotNull(url, "url");
204
205        beforeRemote();
206        try {
207            return mContentProvider.uncanonicalize(mPackageName, url);
208        } catch (DeadObjectException e) {
209            if (!mStable) {
210                mContentResolver.unstableProviderDied(mContentProvider);
211            }
212            throw e;
213        } finally {
214            afterRemote();
215        }
216    }
217
218    /** See {@link ContentProvider#insert ContentProvider.insert} */
219    public @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues initialValues)
220            throws RemoteException {
221        Preconditions.checkNotNull(url, "url");
222
223        beforeRemote();
224        try {
225            return mContentProvider.insert(mPackageName, url, initialValues);
226        } catch (DeadObjectException e) {
227            if (!mStable) {
228                mContentResolver.unstableProviderDied(mContentProvider);
229            }
230            throw e;
231        } finally {
232            afterRemote();
233        }
234    }
235
236    /** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */
237    public int bulkInsert(@NonNull Uri url, @NonNull ContentValues[] initialValues)
238            throws RemoteException {
239        Preconditions.checkNotNull(url, "url");
240        Preconditions.checkNotNull(initialValues, "initialValues");
241
242        beforeRemote();
243        try {
244            return mContentProvider.bulkInsert(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#delete ContentProvider.delete} */
256    public int delete(@NonNull Uri url, @Nullable String selection,
257            @Nullable String[] selectionArgs) throws RemoteException {
258        Preconditions.checkNotNull(url, "url");
259
260        beforeRemote();
261        try {
262            return mContentProvider.delete(mPackageName, url, selection, selectionArgs);
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#update ContentProvider.update} */
274    public int update(@NonNull Uri url, @Nullable ContentValues values, @Nullable String selection,
275            @Nullable String[] selectionArgs) throws RemoteException {
276        Preconditions.checkNotNull(url, "url");
277
278        beforeRemote();
279        try {
280            return mContentProvider.update(mPackageName, url, values, selection, selectionArgs);
281        } catch (DeadObjectException e) {
282            if (!mStable) {
283                mContentResolver.unstableProviderDied(mContentProvider);
284            }
285            throw e;
286        } finally {
287            afterRemote();
288        }
289    }
290
291    /**
292     * See {@link ContentProvider#openFile ContentProvider.openFile}.  Note that
293     * this <em>does not</em>
294     * take care of non-content: URIs such as file:.  It is strongly recommended
295     * you use the {@link ContentResolver#openFileDescriptor
296     * ContentResolver.openFileDescriptor} API instead.
297     */
298    public @Nullable ParcelFileDescriptor openFile(@NonNull Uri url, @NonNull String mode)
299            throws RemoteException, FileNotFoundException {
300        return openFile(url, mode, null);
301    }
302
303    /**
304     * See {@link ContentProvider#openFile ContentProvider.openFile}.  Note that
305     * this <em>does not</em>
306     * take care of non-content: URIs such as file:.  It is strongly recommended
307     * you use the {@link ContentResolver#openFileDescriptor
308     * ContentResolver.openFileDescriptor} API instead.
309     */
310    public @Nullable ParcelFileDescriptor openFile(@NonNull Uri url, @NonNull String mode,
311            @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
312        Preconditions.checkNotNull(url, "url");
313        Preconditions.checkNotNull(mode, "mode");
314
315        beforeRemote();
316        try {
317            ICancellationSignal remoteSignal = null;
318            if (signal != null) {
319                signal.throwIfCanceled();
320                remoteSignal = mContentProvider.createCancellationSignal();
321                signal.setRemote(remoteSignal);
322            }
323            return mContentProvider.openFile(mPackageName, url, mode, remoteSignal, null);
324        } catch (DeadObjectException e) {
325            if (!mStable) {
326                mContentResolver.unstableProviderDied(mContentProvider);
327            }
328            throw e;
329        } finally {
330            afterRemote();
331        }
332    }
333
334    /**
335     * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}.
336     * Note that this <em>does not</em>
337     * take care of non-content: URIs such as file:.  It is strongly recommended
338     * you use the {@link ContentResolver#openAssetFileDescriptor
339     * ContentResolver.openAssetFileDescriptor} API instead.
340     */
341    public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri url, @NonNull String mode)
342            throws RemoteException, FileNotFoundException {
343        return openAssetFile(url, mode, null);
344    }
345
346    /**
347     * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}.
348     * Note that 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#openAssetFileDescriptor
351     * ContentResolver.openAssetFileDescriptor} API instead.
352     */
353    public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri url, @NonNull String mode,
354            @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
355        Preconditions.checkNotNull(url, "url");
356        Preconditions.checkNotNull(mode, "mode");
357
358        beforeRemote();
359        try {
360            ICancellationSignal remoteSignal = null;
361            if (signal != null) {
362                signal.throwIfCanceled();
363                remoteSignal = mContentProvider.createCancellationSignal();
364                signal.setRemote(remoteSignal);
365            }
366            return mContentProvider.openAssetFile(mPackageName, url, mode, remoteSignal);
367        } catch (DeadObjectException e) {
368            if (!mStable) {
369                mContentResolver.unstableProviderDied(mContentProvider);
370            }
371            throw e;
372        } finally {
373            afterRemote();
374        }
375    }
376
377    /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
378    public final @Nullable AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull Uri uri,
379            @NonNull String mimeType, @Nullable Bundle opts)
380                    throws RemoteException, FileNotFoundException {
381        return openTypedAssetFileDescriptor(uri, mimeType, opts, null);
382    }
383
384    /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
385    public final @Nullable AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull Uri uri,
386            @NonNull String mimeType, @Nullable Bundle opts, @Nullable CancellationSignal signal)
387                    throws RemoteException, FileNotFoundException {
388        Preconditions.checkNotNull(uri, "uri");
389        Preconditions.checkNotNull(mimeType, "mimeType");
390
391        beforeRemote();
392        try {
393            ICancellationSignal remoteSignal = null;
394            if (signal != null) {
395                signal.throwIfCanceled();
396                remoteSignal = mContentProvider.createCancellationSignal();
397                signal.setRemote(remoteSignal);
398            }
399            return mContentProvider.openTypedAssetFile(
400                    mPackageName, uri, mimeType, opts, remoteSignal);
401        } catch (DeadObjectException e) {
402            if (!mStable) {
403                mContentResolver.unstableProviderDied(mContentProvider);
404            }
405            throw e;
406        } finally {
407            afterRemote();
408        }
409    }
410
411    /** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */
412    public @NonNull ContentProviderResult[] applyBatch(
413            @NonNull ArrayList<ContentProviderOperation> operations)
414                    throws RemoteException, OperationApplicationException {
415        Preconditions.checkNotNull(operations, "operations");
416
417        beforeRemote();
418        try {
419            return mContentProvider.applyBatch(mPackageName, operations);
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#call(String, String, Bundle)} */
431    public @Nullable Bundle call(@NonNull String method, @Nullable String arg,
432            @Nullable Bundle extras) throws RemoteException {
433        Preconditions.checkNotNull(method, "method");
434
435        beforeRemote();
436        try {
437            return mContentProvider.call(mPackageName, method, arg, extras);
438        } catch (DeadObjectException e) {
439            if (!mStable) {
440                mContentResolver.unstableProviderDied(mContentProvider);
441            }
442            throw e;
443        } finally {
444            afterRemote();
445        }
446    }
447
448    /**
449     * Call this to indicate to the system that the associated {@link ContentProvider} is no
450     * longer needed by this {@link ContentProviderClient}.
451     * @return true if this was release, false if it was already released
452     */
453    public boolean release() {
454        synchronized (this) {
455            if (mReleased) {
456                throw new IllegalStateException("Already released");
457            }
458            mReleased = true;
459            mGuard.close();
460            if (mStable) {
461                return mContentResolver.releaseProvider(mContentProvider);
462            } else {
463                return mContentResolver.releaseUnstableProvider(mContentProvider);
464            }
465        }
466    }
467
468    @Override
469    protected void finalize() throws Throwable {
470        if (mGuard != null) {
471            mGuard.warnIfOpen();
472        }
473    }
474
475    /**
476     * Get a reference to the {@link ContentProvider} that is associated with this
477     * client. If the {@link ContentProvider} is running in a different process then
478     * null will be returned. This can be used if you know you are running in the same
479     * process as a provider, and want to get direct access to its implementation details.
480     *
481     * @return If the associated {@link ContentProvider} is local, returns it.
482     * Otherwise returns null.
483     */
484    public @Nullable ContentProvider getLocalContentProvider() {
485        return ContentProvider.coerceToLocalContentProvider(mContentProvider);
486    }
487
488    /** {@hide} */
489    public static void releaseQuietly(ContentProviderClient client) {
490        if (client != null) {
491            try {
492                client.release();
493            } catch (Exception ignored) {
494            }
495        }
496    }
497
498    private class NotRespondingRunnable implements Runnable {
499        @Override
500        public void run() {
501            Log.w(TAG, "Detected provider not responding: " + mContentProvider);
502            mContentResolver.appNotRespondingViaProvider(mContentProvider);
503        }
504    }
505}
506