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