content: /*
   * Copyright (C) 2009 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.content;
  
  import android.annotation.DurationMillisLong;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.RequiresPermission;
  import android.annotation.SystemApi;
  import android.annotation.TestApi;
  import android.annotation.UnsupportedAppUsage;
  import android.content.res.AssetFileDescriptor;
  import android.database.CrossProcessCursorWrapper;
  import android.database.Cursor;
  import android.net.Uri;
  import android.os.Binder;
  import android.os.Build;
  import android.os.Bundle;
  import android.os.CancellationSignal;
  import android.os.DeadObjectException;
  import android.os.Handler;
  import android.os.ICancellationSignal;
  import android.os.Looper;
  import android.os.ParcelFileDescriptor;
  import android.os.RemoteException;
  import android.util.Log;
  
  import com.android.internal.annotations.GuardedBy;
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.internal.util.Preconditions;
  
  import dalvik.system.CloseGuard;
  
  import libcore.io.IoUtils;
  
  import java.io.FileNotFoundException;
  import java.util.ArrayList;
  import java.util.concurrent.atomic.AtomicBoolean;
  
  /**
   * The public interface object used to interact with a specific
   * {@link ContentProvider}.
   * <p>
   * Instances can be obtained by calling
   * {@link ContentResolver#acquireContentProviderClient} or
   * {@link ContentResolver#acquireUnstableContentProviderClient}. Instances must
   * be released using {@link #close()} in order to indicate to the system that
   * the underlying {@link ContentProvider} is no longer needed and can be killed
   * to free up resources.
   * <p>
   * Note that you should generally create a new ContentProviderClient instance
   * for each thread that will be performing operations. Unlike
   * {@link ContentResolver}, the methods here such as {@link #query} and
   * {@link #openFile} are not thread safe -- you must not call {@link #close()}
   * on the ContentProviderClient those calls are made from until you are finished
   * with the data they have returned.
   */
  public class ContentProviderClient implements ContentInterface, AutoCloseable {
      private static final String TAG = "ContentProviderClient";
  
      @GuardedBy("ContentProviderClient.class")
      private static Handler sAnrHandler;
  
      private final ContentResolver mContentResolver;
      @UnsupportedAppUsage
      private final IContentProvider mContentProvider;
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
      private final String mPackageName;
      private final String mAuthority;
      private final boolean mStable;
  
      private final AtomicBoolean mClosed = new AtomicBoolean();
      private final CloseGuard mCloseGuard = CloseGuard.get();
  
      private long mAnrTimeout;
      private NotRespondingRunnable mAnrRunnable;
  
      /** {@hide} */
      @VisibleForTesting
      public ContentProviderClient(ContentResolver contentResolver, IContentProvider contentProvider,
              boolean stable) {
          // Only used for testing, so use a fake authority
          this(contentResolver, contentProvider, "unknown", stable);
      }
  
      /** {@hide} */
      public ContentProviderClient(ContentResolver contentResolver, IContentProvider contentProvider,
              String authority, boolean stable) {
          mContentResolver = contentResolver;
          mContentProvider = contentProvider;
          mPackageName = contentResolver.mPackageName;
  
          mAuthority = authority;
          mStable = stable;
  
          mCloseGuard.open("close");
      }
  
      /**
       * Configure this client to automatically detect and kill the remote
       * provider when an "application not responding" event is detected.
       *
       * @param timeoutMillis the duration for which a pending call is allowed
       *            block before the remote provider is considered to be
       *            unresponsive. Set to {@code 0} to allow pending calls to block
       *            indefinitely with no action taken.
       * @hide
       */
      @SystemApi
      @TestApi
      @RequiresPermission(android.Manifest.permission.REMOVE_TASKS)
      public void setDetectNotResponding(@DurationMillisLong long timeoutMillis) {
          synchronized (ContentProviderClient.class) {
              mAnrTimeout = timeoutMillis;
  
              if (timeoutMillis > 0) {
                  if (mAnrRunnable == null) {
                      mAnrRunnable = new NotRespondingRunnable();
                  }
                  if (sAnrHandler == null) {
                      sAnrHandler = new Handler(Looper.getMainLooper(), null, true /* async */);
                  }
  
                  // If the remote process hangs, we're going to kill it, so we're
                  // technically okay doing blocking calls.
                  Binder.allowBlocking(mContentProvider.asBinder());
              } else {
                  mAnrRunnable = null;
  
                  // If we're no longer watching for hangs, revert back to default
                  // blocking behavior.
                  Binder.defaultBlocking(mContentProvider.asBinder());
              }
          }
      }
  
      private void beforeRemote() {
          if (mAnrRunnable != null) {
              sAnrHandler.postDelayed(mAnrRunnable, mAnrTimeout);
          }
      }
  
      private void afterRemote() {
          if (mAnrRunnable != null) {
              sAnrHandler.removeCallbacks(mAnrRunnable);
          }
      }
  
      /** See {@link ContentProvider#query ContentProvider.query} */
      public @Nullable Cursor query(@NonNull Uri url, @Nullable String[] projection,
              @Nullable String selection, @Nullable String[] selectionArgs,
              @Nullable String sortOrder) throws RemoteException {
          return query(url, projection, selection,  selectionArgs, sortOrder, null);
      }
  
      /** See {@link ContentProvider#query ContentProvider.query} */
      public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
              @Nullable String selection, @Nullable String[] selectionArgs,
              @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal)
                      throws RemoteException {
          Bundle queryArgs =
                  ContentResolver.createSqlQueryBundle(selection, selectionArgs, sortOrder);
          return query(uri, projection, queryArgs, cancellationSignal);
      }
  
      /** See {@link ContentProvider#query ContentProvider.query} */
      @Override
      public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
              Bundle queryArgs, @Nullable CancellationSignal cancellationSignal)
                      throws RemoteException {
          Preconditions.checkNotNull(uri, "url");
  
          beforeRemote();
          try {
              ICancellationSignal remoteCancellationSignal = null;
              if (cancellationSignal != null) {
                  cancellationSignal.throwIfCanceled();
                  remoteCancellationSignal = mContentProvider.createCancellationSignal();
                  cancellationSignal.setRemote(remoteCancellationSignal);
              }
              final Cursor cursor = mContentProvider.query(
                      mPackageName, uri, projection, queryArgs, remoteCancellationSignal);
              if (cursor == null) {
                  return null;
              }
              return new CursorWrapperInner(cursor);
          } catch (DeadObjectException e) {
              if (!mStable) {
                  mContentResolver.unstableProviderDied(mContentProvider);
              }
              throw e;
          } finally {
              afterRemote();
          }
      }
  
      /** See {@link ContentProvider#getType ContentProvider.getType} */
      @Override
      public @Nullable String getType(@NonNull Uri url) throws RemoteException {
          Preconditions.checkNotNull(url, "url");
  
          beforeRemote();
          try {
              return mContentProvider.getType(url);
          } catch (DeadObjectException e) {
              if (!mStable) {
                  mContentResolver.unstableProviderDied(mContentProvider);
              }
              throw e;
          } finally {
              afterRemote();
          }
      }
  
      /** See {@link ContentProvider#getStreamTypes ContentProvider.getStreamTypes} */
      @Override
      public @Nullable String[] getStreamTypes(@NonNull Uri url, @NonNull String mimeTypeFilter)
              throws RemoteException {
          Preconditions.checkNotNull(url, "url");
          Preconditions.checkNotNull(mimeTypeFilter, "mimeTypeFilter");
  
          beforeRemote();
          try {
              return mContentProvider.getStreamTypes(url, mimeTypeFilter);
          } catch (DeadObjectException e) {
              if (!mStable) {
                  mContentResolver.unstableProviderDied(mContentProvider);
              }
              throw e;
          } finally {
              afterRemote();
          }
      }
  
      /** See {@link ContentProvider#canonicalize} */
      @Override
      public final @Nullable Uri canonicalize(@NonNull Uri url) throws RemoteException {
          Preconditions.checkNotNull(url, "url");
  
          beforeRemote();
          try {
              return mContentProvider.canonicalize(mPackageName, url);
          } catch (DeadObjectException e) {
              if (!mStable) {
                  mContentResolver.unstableProviderDied(mContentProvider);
              }
              throw e;
          } finally {
              afterRemote();
          }
      }
  
      /** See {@link ContentProvider#uncanonicalize} */
      @Override
      public final @Nullable Uri uncanonicalize(@NonNull Uri url) throws RemoteException {
          Preconditions.checkNotNull(url, "url");
  
          beforeRemote();
          try {
              return mContentProvider.uncanonicalize(mPackageName, url);
          } catch (DeadObjectException e) {
              if (!mStable) {
                  mContentResolver.unstableProviderDied(mContentProvider);
              }
              throw e;
          } finally {
              afterRemote();
          }
      }
  
      /** See {@link ContentProvider#refresh} */
      @Override
      public boolean refresh(Uri url, @Nullable Bundle args,
              @Nullable CancellationSignal cancellationSignal) throws RemoteException {
          Preconditions.checkNotNull(url, "url");
  
          beforeRemote();
          try {
              ICancellationSignal remoteCancellationSignal = null;
              if (cancellationSignal != null) {
                  cancellationSignal.throwIfCanceled();
                  remoteCancellationSignal = mContentProvider.createCancellationSignal();
                  cancellationSignal.setRemote(remoteCancellationSignal);
              }
              return mContentProvider.refresh(mPackageName, url, args, remoteCancellationSignal);
          } catch (DeadObjectException e) {
              if (!mStable) {
                  mContentResolver.unstableProviderDied(mContentProvider);
              }
              throw e;
          } finally {
              afterRemote();
          }
      }
  
      /** See {@link ContentProvider#insert ContentProvider.insert} */
      @Override
      public @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues initialValues)
              throws RemoteException {
          Preconditions.checkNotNull(url, "url");
  
          beforeRemote();
          try {
              return mContentProvider.insert(mPackageName, url, initialValues);
          } catch (DeadObjectException e) {
              if (!mStable) {
                  mContentResolver.unstableProviderDied(mContentProvider);
              }
              throw e;
          } finally {
              afterRemote();
          }
      }
  
      /** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */
      @Override
      public int bulkInsert(@NonNull Uri url, @NonNull ContentValues[] initialValues)
              throws RemoteException {
          Preconditions.checkNotNull(url, "url");
          Preconditions.checkNotNull(initialValues, "initialValues");
  
          beforeRemote();
          try {
              return mContentProvider.bulkInsert(mPackageName, url, initialValues);
          } catch (DeadObjectException e) {
              if (!mStable) {
                  mContentResolver.unstableProviderDied(mContentProvider);
              }
              throw e;
          } finally {
              afterRemote();
          }
      }
  
      /** See {@link ContentProvider#delete ContentProvider.delete} */
      @Override
      public int delete(@NonNull Uri url, @Nullable String selection,
              @Nullable String[] selectionArgs) throws RemoteException {
          Preconditions.checkNotNull(url, "url");
  
          beforeRemote();
          try {
              return mContentProvider.delete(mPackageName, url, selection, selectionArgs);
          } catch (DeadObjectException e) {
              if (!mStable) {
                  mContentResolver.unstableProviderDied(mContentProvider);
              }
              throw e;
          } finally {
              afterRemote();
          }
      }
  
      /** See {@link ContentProvider#update ContentProvider.update} */
      @Override
      public int update(@NonNull Uri url, @Nullable ContentValues values, @Nullable String selection,
              @Nullable String[] selectionArgs) throws RemoteException {
          Preconditions.checkNotNull(url, "url");
  
          beforeRemote();
          try {
              return mContentProvider.update(mPackageName, url, values, selection, selectionArgs);
          } catch (DeadObjectException e) {
              if (!mStable) {
                  mContentResolver.unstableProviderDied(mContentProvider);
              }
              throw e;
          } finally {
              afterRemote();
          }
      }
  
      /**
       * See {@link ContentProvider#openFile ContentProvider.openFile}.  Note that
       * this <em>does not</em>
       * take care of non-content: URIs such as file:.  It is strongly recommended
       * you use the {@link ContentResolver#openFileDescriptor
       * ContentResolver.openFileDescriptor} API instead.
       */
      public @Nullable ParcelFileDescriptor openFile(@NonNull Uri url, @NonNull String mode)
              throws RemoteException, FileNotFoundException {
          return openFile(url, mode, null);
      }
  
      /**
       * See {@link ContentProvider#openFile ContentProvider.openFile}.  Note that
       * this <em>does not</em>
       * take care of non-content: URIs such as file:.  It is strongly recommended
       * you use the {@link ContentResolver#openFileDescriptor
       * ContentResolver.openFileDescriptor} API instead.
       */
      @Override
      public @Nullable ParcelFileDescriptor openFile(@NonNull Uri url, @NonNull String mode,
              @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
          Preconditions.checkNotNull(url, "url");
          Preconditions.checkNotNull(mode, "mode");
  
          beforeRemote();
          try {
              ICancellationSignal remoteSignal = null;
              if (signal != null) {
                  signal.throwIfCanceled();
                  remoteSignal = mContentProvider.createCancellationSignal();
                  signal.setRemote(remoteSignal);
              }
              return mContentProvider.openFile(mPackageName, url, mode, remoteSignal, null);
          } catch (DeadObjectException e) {
              if (!mStable) {
                  mContentResolver.unstableProviderDied(mContentProvider);
              }
              throw e;
          } finally {
              afterRemote();
          }
      }
  
      /**
       * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}.
       * Note that this <em>does not</em>
       * take care of non-content: URIs such as file:.  It is strongly recommended
       * you use the {@link ContentResolver#openAssetFileDescriptor
       * ContentResolver.openAssetFileDescriptor} API instead.
       */
      public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri url, @NonNull String mode)
              throws RemoteException, FileNotFoundException {
          return openAssetFile(url, mode, null);
      }
  
      /**
       * See {@link ContentProvider#openAssetFile ContentProvider.openAssetFile}.
       * Note that this <em>does not</em>
       * take care of non-content: URIs such as file:.  It is strongly recommended
       * you use the {@link ContentResolver#openAssetFileDescriptor
       * ContentResolver.openAssetFileDescriptor} API instead.
       */
      @Override
      public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri url, @NonNull String mode,
              @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
          Preconditions.checkNotNull(url, "url");
          Preconditions.checkNotNull(mode, "mode");
  
          beforeRemote();
          try {
              ICancellationSignal remoteSignal = null;
              if (signal != null) {
                  signal.throwIfCanceled();
                  remoteSignal = mContentProvider.createCancellationSignal();
                  signal.setRemote(remoteSignal);
              }
              return mContentProvider.openAssetFile(mPackageName, url, mode, remoteSignal);
          } catch (DeadObjectException e) {
              if (!mStable) {
                  mContentResolver.unstableProviderDied(mContentProvider);
              }
              throw e;
          } finally {
              afterRemote();
          }
      }
  
      /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
      public final @Nullable AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull Uri uri,
              @NonNull String mimeType, @Nullable Bundle opts)
                      throws RemoteException, FileNotFoundException {
          return openTypedAssetFileDescriptor(uri, mimeType, opts, null);
      }
  
      /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
      public final @Nullable AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull Uri uri,
              @NonNull String mimeType, @Nullable Bundle opts, @Nullable CancellationSignal signal)
                      throws RemoteException, FileNotFoundException {
          return openTypedAssetFile(uri, mimeType, opts, signal);
      }
  
      @Override
      public final @Nullable AssetFileDescriptor openTypedAssetFile(@NonNull Uri uri,
              @NonNull String mimeTypeFilter, @Nullable Bundle opts,
              @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
          Preconditions.checkNotNull(uri, "uri");
          Preconditions.checkNotNull(mimeTypeFilter, "mimeTypeFilter");
  
          beforeRemote();
          try {
              ICancellationSignal remoteSignal = null;
              if (signal != null) {
                  signal.throwIfCanceled();
                  remoteSignal = mContentProvider.createCancellationSignal();
                  signal.setRemote(remoteSignal);
              }
              return mContentProvider.openTypedAssetFile(
                      mPackageName, uri, mimeTypeFilter, opts, remoteSignal);
          } catch (DeadObjectException e) {
              if (!mStable) {
                  mContentResolver.unstableProviderDied(mContentProvider);
              }
              throw e;
          } finally {
              afterRemote();
          }
      }
  
      /** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */
      public @NonNull ContentProviderResult[] applyBatch(
              @NonNull ArrayList<ContentProviderOperation> operations)
              throws RemoteException, OperationApplicationException {
          return applyBatch(mAuthority, operations);
      }
  
      /** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */
      @Override
      public @NonNull ContentProviderResult[] applyBatch(@NonNull String authority,
              @NonNull ArrayList<ContentProviderOperation> operations)
              throws RemoteException, OperationApplicationException {
          Preconditions.checkNotNull(operations, "operations");
  
          beforeRemote();
          try {
              return mContentProvider.applyBatch(mPackageName, authority, operations);
          } catch (DeadObjectException e) {
              if (!mStable) {
                  mContentResolver.unstableProviderDied(mContentProvider);
              }
              throw e;
          } finally {
              afterRemote();
          }
      }
  
      /** See {@link ContentProvider#call(String, String, Bundle)} */
      public @Nullable Bundle call(@NonNull String method, @Nullable String arg,
              @Nullable Bundle extras) throws RemoteException {
          return call(mAuthority, method, arg, extras);
      }
  
      /** See {@link ContentProvider#call(String, String, Bundle)} */
      @Override
      public @Nullable Bundle call(@NonNull String authority, @NonNull String method,
              @Nullable String arg, @Nullable Bundle extras) throws RemoteException {
          Preconditions.checkNotNull(authority, "authority");
          Preconditions.checkNotNull(method, "method");
  
          beforeRemote();
          try {
              return mContentProvider.call(mPackageName, authority, method, arg, extras);
          } catch (DeadObjectException e) {
              if (!mStable) {
                  mContentResolver.unstableProviderDied(mContentProvider);
              }
              throw e;
          } finally {
              afterRemote();
          }
      }
  
      /**
       * Closes this client connection, indicating to the system that the
       * underlying {@link ContentProvider} is no longer needed.
       */
      @Override
      public void close() {
          closeInternal();
      }
  
      /**
       * @deprecated replaced by {@link #close()}.
       */
      @Deprecated
      public boolean release() {
          return closeInternal();
      }
  
      private boolean closeInternal() {
          mCloseGuard.close();
          if (mClosed.compareAndSet(false, true)) {
              // We can't do ANR checks after we cease to exist! Reset any
              // blocking behavior changes we might have made.
              setDetectNotResponding(0);
  
              if (mStable) {
                  return mContentResolver.releaseProvider(mContentProvider);
              } else {
                  return mContentResolver.releaseUnstableProvider(mContentProvider);
              }
          } else {
              return false;
          }
      }
  
      @Override
      protected void finalize() throws Throwable {
          try {
              if (mCloseGuard != null) {
                  mCloseGuard.warnIfOpen();
              }
  
              close();
          } finally {
              super.finalize();
          }
      }
  
      /**
       * Get a reference to the {@link ContentProvider} that is associated with this
       * client. If the {@link ContentProvider} is running in a different process then
       * null will be returned. This can be used if you know you are running in the same
       * process as a provider, and want to get direct access to its implementation details.
       *
       * @return If the associated {@link ContentProvider} is local, returns it.
       * Otherwise returns null.
       */
      public @Nullable ContentProvider getLocalContentProvider() {
          return ContentProvider.coerceToLocalContentProvider(mContentProvider);
      }
  
      /** {@hide} */
      @Deprecated
      public static void closeQuietly(ContentProviderClient client) {
          IoUtils.closeQuietly(client);
      }
  
      /** {@hide} */
      @Deprecated
      public static void releaseQuietly(ContentProviderClient client) {
          IoUtils.closeQuietly(client);
      }
  
      private class NotRespondingRunnable implements Runnable {
          @Override
          public void run() {
              Log.w(TAG, "Detected provider not responding: " + mContentProvider);
              mContentResolver.appNotRespondingViaProvider(mContentProvider);
          }
      }
  
      private final class CursorWrapperInner extends CrossProcessCursorWrapper {
          private final CloseGuard mCloseGuard = CloseGuard.get();
  
          CursorWrapperInner(Cursor cursor) {
              super(cursor);
              mCloseGuard.open("close");
          }
  
          @Override
          public void close() {
              mCloseGuard.close();
              super.close();
          }
  
          @Override
          protected void finalize() throws Throwable {
              try {
                  if (mCloseGuard != null) {
                      mCloseGuard.warnIfOpen();
                  }
  
                  close();
              } finally {
                  super.finalize();
              }
          }
      }
  }
  