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.Cursor; 23 import android.net.Uri; 24 import android.os.Bundle; 25 import android.os.CancellationSignal; 26 import android.os.DeadObjectException; 27 import android.os.Handler; 28 import android.os.ICancellationSignal; 29 import android.os.Looper; 30 import android.os.ParcelFileDescriptor; 31 import android.os.RemoteException; 32 import android.util.Log; 33 34 import com.android.internal.annotations.GuardedBy; 35 import com.android.internal.util.Preconditions; 36 37 import dalvik.system.CloseGuard; 38 39 import java.io.FileNotFoundException; 40 import 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 */ 55 public 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