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