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