All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.shadows.ShadowContentResolver Maven / Gradle / Ivy

The newest version!
package org.robolectric.shadows;

import static android.content.ContentResolver.QUERY_ARG_SQL_SELECTION;
import static android.content.ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS;
import static android.content.ContentResolver.QUERY_ARG_SQL_SORT_ORDER;
import static android.content.ContentResolver.SCHEME_ANDROID_RESOURCE;
import static android.content.ContentResolver.SCHEME_CONTENT;
import static android.content.ContentResolver.SCHEME_FILE;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.Q;
import static org.robolectric.util.reflector.Reflector.reflector;

import android.accounts.Account;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.content.ContentProvider;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.IContentProvider;
import android.content.Intent;
import android.content.OperationApplicationException;
import android.content.PeriodicSync;
import android.content.SyncAdapterType;
import android.content.SyncInfo;
import android.content.UriPermission;
import android.content.pm.ProviderInfo;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import com.google.common.base.Splitter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Supplier;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.fakes.BaseCursor;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.NamedStream;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.ReflectionHelpers.ClassParameter;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;

@Implements(ContentResolver.class)
@SuppressLint("NewApi")
public class ShadowContentResolver {
  private int nextDatabaseIdForInserts;

  @RealObject ContentResolver realContentResolver;

  private BaseCursor cursor;
  private static final List statements = new CopyOnWriteArrayList<>();
  private static final List insertStatements = new CopyOnWriteArrayList<>();
  private static final List updateStatements = new CopyOnWriteArrayList<>();
  private static final List deleteStatements = new CopyOnWriteArrayList<>();
  private static final List notifiedUris = new ArrayList<>();
  private static final Map uriCursorMap = new HashMap<>();
  private static final Map> inputStreamMap = new HashMap<>();
  private static final Map> outputStreamMap = new HashMap<>();
  private static final Map> contentProviderOperations =
      new HashMap<>();
  private static final List uriPermissions = new ArrayList<>();

  private static final CopyOnWriteArrayList contentObservers =
      new CopyOnWriteArrayList<>();

  private static final Map> syncableAccounts = new HashMap<>();
  private static final Map providers =
      Collections.synchronizedMap(new HashMap<>());
  private static boolean masterSyncAutomatically;

  private static SyncAdapterType[] syncAdapterTypes;

  @Resetter
  public static void reset() {
    statements.clear();
    insertStatements.clear();
    updateStatements.clear();
    deleteStatements.clear();
    notifiedUris.clear();
    uriCursorMap.clear();
    inputStreamMap.clear();
    outputStreamMap.clear();
    contentProviderOperations.clear();
    uriPermissions.clear();
    contentObservers.clear();
    syncableAccounts.clear();
    providers.clear();
    masterSyncAutomatically = false;
  }

  private static class ContentObserverEntry {
    public final Uri uri;
    public final boolean notifyForDescendents;
    public final ContentObserver observer;

    private ContentObserverEntry(Uri uri, boolean notifyForDescendents, ContentObserver observer) {
      this.uri = uri;
      this.notifyForDescendents = notifyForDescendents;
      this.observer = observer;

      if (uri == null || observer == null) {
        throw new NullPointerException();
      }
    }

    public boolean matches(Uri test) {
      if (!Objects.equals(uri.getScheme(), test.getScheme())) {
        return false;
      }
      if (!Objects.equals(uri.getAuthority(), test.getAuthority())) {
        return false;
      }

      String uriPath = uri.getPath();
      String testPath = test.getPath();

      return Objects.equals(uriPath, testPath)
          || (notifyForDescendents && testPath != null && testPath.startsWith(uriPath));
    }
  }

  public static class NotifiedUri {
    public final Uri uri;
    public final boolean syncToNetwork;
    public final ContentObserver observer;
    public final int flags;

    public NotifiedUri(Uri uri, ContentObserver observer, int flags) {
      this.uri = uri;
      this.syncToNetwork = flags == ContentResolver.NOTIFY_SYNC_TO_NETWORK;
      this.observer = observer;
      this.flags = flags;
    }
  }

  public static class Status {
    public int syncRequests;
    public int state = -1;
    public boolean syncAutomatically;
    public Bundle syncExtras;
    public List syncs = new ArrayList<>();
  }

  public void registerInputStream(Uri uri, InputStream inputStream) {
    inputStreamMap.put(uri, () -> inputStream);
  }

  public void registerInputStreamSupplier(Uri uri, Supplier supplier) {
    inputStreamMap.put(uri, supplier);
  }

  public void registerOutputStream(Uri uri, OutputStream outputStream) {
    outputStreamMap.put(uri, () -> outputStream);
  }

  public void registerOutputStreamSupplier(Uri uri, Supplier supplier) {
    outputStreamMap.put(uri, supplier);
  }

  @Implementation
  protected InputStream openInputStream(final Uri uri) throws FileNotFoundException {
    Supplier supplier = inputStreamMap.get(uri);
    if (supplier != null) {
      InputStream inputStream = supplier.get();
      if (inputStream != null) {
        return inputStream;
      }
    }
    String scheme = uri.getScheme();
    if (SCHEME_ANDROID_RESOURCE.equals(scheme)
        || SCHEME_FILE.equals(scheme)
        || (SCHEME_CONTENT.equals(scheme) && getProvider(uri, getContext()) != null)) {
      return reflector(ContentResolverReflector.class, realContentResolver).openInputStream(uri);
    }
    return new UnregisteredInputStream(uri);
  }

  @Implementation
  protected OutputStream openOutputStream(final Uri uri) throws FileNotFoundException {
    try {
      return openOutputStream(uri, "w");
    } catch (SecurityException | FileNotFoundException e) {
      // This is legacy behavior is only supported because existing users require it.
      return new OutputStream() {
        @Override
        public void write(int arg0) throws IOException {}

        @Override
        public String toString() {
          return "outputstream for " + uri;
        }
      };
    }
  }

  @Implementation
  protected OutputStream openOutputStream(Uri uri, String mode) throws FileNotFoundException {
    Supplier supplier = outputStreamMap.get(uri);
    if (supplier != null) {
      OutputStream outputStream = supplier.get();
      if (outputStream != null) {
        return outputStream;
      }
    }
    return reflector(ContentResolverReflector.class, realContentResolver)
        .openOutputStream(uri, mode);
  }

  /**
   * If a {@link ContentProvider} is registered for the given {@link Uri}, its {@link
   * ContentProvider#insert(Uri, ContentValues)} method will be invoked.
   *
   * 

Tests can verify that this method was called using {@link #getStatements()} or {@link * #getInsertStatements()}. * *

If no appropriate {@link ContentProvider} is found, no action will be taken and a {@link * Uri} including the incremented value set with {@link #setNextDatabaseIdForInserts(int)} will * returned. */ @Implementation protected Uri insert(Uri url, ContentValues values) { ContentProvider provider = getProvider(url, getContext()); ContentValues valuesCopy = (values == null) ? null : new ContentValues(values); InsertStatement insertStatement = new InsertStatement(url, provider, valuesCopy); statements.add(insertStatement); insertStatements.add(insertStatement); if (provider != null) { return provider.insert(url, values); } else { return Uri.parse(url.toString() + "/" + ++nextDatabaseIdForInserts); } } private Context getContext() { return reflector(ContentResolverReflector.class, realContentResolver).getContext(); } /** * If a {@link ContentProvider} is registered for the given {@link Uri}, its {@link * ContentProvider#update(Uri, ContentValues, String, String[])} method will be invoked. * *

Tests can verify that this method was called using {@link #getStatements()} or {@link * #getUpdateStatements()}. * * @return If no appropriate {@link ContentProvider} is found, no action will be taken and 1 will * be returned. */ @Implementation protected int update(Uri uri, ContentValues values, String where, String[] selectionArgs) { ContentProvider provider = getProvider(uri, getContext()); ContentValues valuesCopy = (values == null) ? null : new ContentValues(values); UpdateStatement updateStatement = new UpdateStatement(uri, provider, valuesCopy, where, selectionArgs); statements.add(updateStatement); updateStatements.add(updateStatement); if (provider != null) { return provider.update(uri, values, where, selectionArgs); } else { return 1; } } @Implementation(minSdk = O) protected final Cursor query( Uri uri, String[] projection, Bundle queryArgs, CancellationSignal cancellationSignal) { ContentProvider provider = getProvider(uri, getContext()); if (provider != null) { return provider.query(uri, projection, queryArgs, cancellationSignal); } else { BaseCursor returnCursor = getCursor(uri); if (returnCursor == null) { return null; } String selection = queryArgs.getString(QUERY_ARG_SQL_SELECTION); String[] selectionArgs = queryArgs.getStringArray(QUERY_ARG_SQL_SELECTION_ARGS); String sortOrder = queryArgs.getString(QUERY_ARG_SQL_SORT_ORDER); returnCursor.setQuery(uri, projection, selection, selectionArgs, sortOrder); return returnCursor; } } @Implementation protected Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { ContentProvider provider = getProvider(uri, getContext()); if (provider != null) { return provider.query(uri, projection, selection, selectionArgs, sortOrder); } else { BaseCursor returnCursor = getCursor(uri); if (returnCursor == null) { return null; } returnCursor.setQuery(uri, projection, selection, selectionArgs, sortOrder); return returnCursor; } } @Implementation protected Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) { ContentProvider provider = getProvider(uri, getContext()); if (provider != null) { return provider.query( uri, projection, selection, selectionArgs, sortOrder, cancellationSignal); } else { BaseCursor returnCursor = getCursor(uri); if (returnCursor == null) { return null; } returnCursor.setQuery(uri, projection, selection, selectionArgs, sortOrder); return returnCursor; } } @Implementation protected String getType(Uri uri) { ContentProvider provider = getProvider(uri, getContext()); if (provider != null) { return provider.getType(uri); } else { return null; } } @Implementation protected Bundle call(Uri uri, String method, String arg, Bundle extras) { ContentProvider cp = getProvider(uri, getContext()); if (cp != null) { return cp.call(method, arg, extras); } else { return null; } } @Implementation protected ContentProviderClient acquireContentProviderClient(String name) { ContentProvider provider = getProvider(name, getContext()); if (provider == null) { return null; } return getContentProviderClient(provider, true); } @Implementation protected ContentProviderClient acquireContentProviderClient(Uri uri) { ContentProvider provider = getProvider(uri, getContext()); if (provider == null) { return null; } return getContentProviderClient(provider, true); } @Implementation protected ContentProviderClient acquireUnstableContentProviderClient(String name) { ContentProvider provider = getProvider(name, getContext()); if (provider == null) { return null; } return getContentProviderClient(provider, false); } @Implementation protected ContentProviderClient acquireUnstableContentProviderClient(Uri uri) { ContentProvider provider = getProvider(uri, getContext()); if (provider == null) { return null; } return getContentProviderClient(provider, false); } private ContentProviderClient getContentProviderClient(ContentProvider provider, boolean stable) { ContentProviderClient client = ReflectionHelpers.callConstructor( ContentProviderClient.class, ClassParameter.from(ContentResolver.class, realContentResolver), ClassParameter.from(IContentProvider.class, provider.getIContentProvider()), ClassParameter.from(boolean.class, stable)); ShadowContentProviderClient shadowContentProviderClient = Shadow.extract(client); shadowContentProviderClient.setContentProvider(provider); return client; } @Implementation protected IContentProvider acquireProvider(String name) { return acquireUnstableProvider(name); } @Implementation protected IContentProvider acquireProvider(Uri uri) { return acquireUnstableProvider(uri); } @Implementation protected IContentProvider acquireUnstableProvider(String name) { ContentProvider cp = getProvider(name, getContext()); if (cp != null) { return cp.getIContentProvider(); } return null; } @Implementation protected final IContentProvider acquireUnstableProvider(Uri uri) { ContentProvider cp = getProvider(uri, getContext()); if (cp != null) { return cp.getIContentProvider(); } return null; } /** * If a {@link ContentProvider} is registered for the given {@link Uri}, its {@link * ContentProvider#delete(Uri, String, String[])} method will be invoked. * *

Tests can verify that this method was called using {@link #getDeleteStatements()} or {@link * #getDeletedUris()}. * *

If no appropriate {@link ContentProvider} is found, no action will be taken and {@code 1} * will be returned. */ @Implementation protected int delete(Uri url, String where, String[] selectionArgs) { ContentProvider provider = getProvider(url, getContext()); DeleteStatement deleteStatement = new DeleteStatement(url, provider, where, selectionArgs); statements.add(deleteStatement); deleteStatements.add(deleteStatement); if (provider != null) { return provider.delete(url, where, selectionArgs); } else { return 1; } } /** * If a {@link ContentProvider} is registered for the given {@link Uri}, its {@link * ContentProvider#bulkInsert(Uri, ContentValues[])} method will be invoked. * *

Tests can verify that this method was called using {@link #getStatements()} or {@link * #getInsertStatements()}. * *

If no appropriate {@link ContentProvider} is found, no action will be taken and the number * of rows in {@code values} will be returned. */ @Implementation protected int bulkInsert(Uri url, ContentValues[] values) { ContentProvider provider = getProvider(url, getContext()); InsertStatement insertStatement = new InsertStatement(url, provider, values); statements.add(insertStatement); insertStatements.add(insertStatement); if (provider != null) { return provider.bulkInsert(url, values); } else { return values.length; } } @Implementation(minSdk = N) protected void notifyChange(Uri uri, ContentObserver observer, int flags) { notifiedUris.add(new NotifiedUri(uri, observer, flags)); for (ContentObserverEntry entry : contentObservers) { if (entry.matches(uri) && entry.observer != observer) { entry.observer.dispatchChange(false, uri); } } if (observer != null && observer.deliverSelfNotifications()) { observer.dispatchChange(true, uri); } } @Implementation protected void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { notifyChange(uri, observer, syncToNetwork ? ContentResolver.NOTIFY_SYNC_TO_NETWORK : 0); } @Implementation protected void notifyChange(Uri uri, ContentObserver observer) { notifyChange(uri, observer, false); } @Implementation protected @NonNull ContentProviderResult[] applyBatch( String authority, ArrayList operations) throws OperationApplicationException { ContentProvider provider = getProvider(authority, getContext()); if (provider != null) { return provider.applyBatch(operations); } else { contentProviderOperations.put(authority, operations); return new ContentProviderResult[0]; } } @Implementation protected static void requestSync(Account account, String authority, Bundle extras) { validateSyncExtrasBundle(extras); Status status = getStatus(account, authority, true); status.syncRequests++; status.syncExtras = extras; } @Implementation protected static void cancelSync(Account account, String authority) { Status status = getStatus(account, authority); if (status != null) { status.syncRequests = 0; if (status.syncExtras != null) { status.syncExtras.clear(); } // This may be too much, as the above should be sufficient. if (status.syncs != null) { status.syncs.clear(); } } } @Implementation protected static boolean isSyncActive(Account account, String authority) { ShadowContentResolver.Status status = getStatus(account, authority); // TODO: this means a sync is *perpetually* active after one request return status != null && status.syncRequests > 0; } @Implementation protected static List getCurrentSyncs() { List list = new ArrayList<>(); for (Map.Entry> map : syncableAccounts.entrySet()) { if (map.getValue() == null) { continue; } for (Map.Entry mp : map.getValue().entrySet()) { if (isSyncActive(mp.getKey(), map.getKey())) { SyncInfo si = new SyncInfo(0, mp.getKey(), map.getKey(), 0); list.add(si); } } } return list; } @Implementation protected static void setIsSyncable(Account account, String authority, int syncable) { getStatus(account, authority, true).state = syncable; } @Implementation protected static int getIsSyncable(Account account, String authority) { return getStatus(account, authority, true).state; } @Implementation protected static boolean getSyncAutomatically(Account account, String authority) { return getStatus(account, authority, true).syncAutomatically; } @Implementation protected static void setSyncAutomatically(Account account, String authority, boolean sync) { getStatus(account, authority, true).syncAutomatically = sync; } @Implementation protected static void addPeriodicSync( Account account, String authority, Bundle extras, long pollFrequency) { validateSyncExtrasBundle(extras); removePeriodicSync(account, authority, extras); getStatus(account, authority, true) .syncs .add(new PeriodicSync(account, authority, extras, pollFrequency)); } @Implementation protected static void removePeriodicSync(Account account, String authority, Bundle extras) { validateSyncExtrasBundle(extras); Status status = getStatus(account, authority); if (status != null) { for (int i = 0; i < status.syncs.size(); ++i) { if (isBundleEqual(extras, status.syncs.get(i).extras)) { status.syncs.remove(i); break; } } } } @Implementation protected static List getPeriodicSyncs(Account account, String authority) { return getStatus(account, authority, true).syncs; } @Implementation protected static void validateSyncExtrasBundle(Bundle extras) { for (String key : extras.keySet()) { Object value = extras.get(key); if (value == null || value instanceof Long || value instanceof Integer || value instanceof Boolean || value instanceof Float || value instanceof Double || value instanceof String || value instanceof Account) { continue; } throw new IllegalArgumentException("unexpected value type: " + value.getClass().getName()); } } @Implementation protected static void setMasterSyncAutomatically(boolean sync) { masterSyncAutomatically = sync; } @Implementation protected static boolean getMasterSyncAutomatically() { return masterSyncAutomatically; } @Implementation protected void takePersistableUriPermission(@NonNull Uri uri, int modeFlags) { Objects.requireNonNull(uri, "uri may not be null"); modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); // If neither read nor write permission is specified there is nothing to do. if (modeFlags == 0) { return; } // Attempt to locate an existing record for the uri. for (Iterator i = uriPermissions.iterator(); i.hasNext(); ) { UriPermission perm = i.next(); if (uri.equals(perm.getUri())) { if (perm.isReadPermission()) { modeFlags |= Intent.FLAG_GRANT_READ_URI_PERMISSION; } if (perm.isWritePermission()) { modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION; } i.remove(); break; } } addUriPermission(uri, modeFlags); } @Implementation protected void releasePersistableUriPermission(@NonNull Uri uri, int modeFlags) { Objects.requireNonNull(uri, "uri may not be null"); modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); // If neither read nor write permission is specified there is nothing to do. if (modeFlags == 0) { return; } // Attempt to locate an existing record for the uri. for (Iterator i = uriPermissions.iterator(); i.hasNext(); ) { UriPermission perm = i.next(); if (uri.equals(perm.getUri())) { // Reconstruct the current mode flags. int oldModeFlags = (perm.isReadPermission() ? Intent.FLAG_GRANT_READ_URI_PERMISSION : 0) | (perm.isWritePermission() ? Intent.FLAG_GRANT_WRITE_URI_PERMISSION : 0); // Apply the requested permission change. int newModeFlags = oldModeFlags & ~modeFlags; // Update the permission record if a change occurred. if (newModeFlags != oldModeFlags) { i.remove(); if (newModeFlags != 0) { addUriPermission(uri, newModeFlags); } } break; } } } @Implementation @NonNull protected List getPersistedUriPermissions() { return uriPermissions; } private void addUriPermission(@NonNull Uri uri, int modeFlags) { UriPermission perm = ReflectionHelpers.callConstructor( UriPermission.class, ClassParameter.from(Uri.class, uri), ClassParameter.from(int.class, modeFlags), ClassParameter.from(long.class, System.currentTimeMillis())); uriPermissions.add(perm); } public static ContentProvider getProvider(Uri uri) { return getProvider(uri, RuntimeEnvironment.getApplication()); } private static ContentProvider getProvider(Uri uri, Context context) { if (uri == null || !SCHEME_CONTENT.equals(uri.getScheme())) { return null; } return getProvider(uri.getAuthority(), context); } private static ContentProvider getProvider(String authority, Context context) { synchronized (providers) { if (!providers.containsKey(authority)) { ProviderInfo providerInfo = context.getPackageManager().resolveContentProvider(authority, 0); if (providerInfo != null) { ContentProvider contentProvider = createAndInitialize(providerInfo); for (String auth : Splitter.on(';').split(providerInfo.authority)) { providers.put(auth, contentProvider); } } } return providers.get(authority); } } /** * Internal-only method, do not use! * *

Instead, use * *

   * ProviderInfo info = new ProviderInfo();
   * info.authority = authority;
   * Robolectric.buildContentProvider(ContentProvider.class).create(info);
   * 
*/ public static void registerProviderInternal(String authority, ContentProvider provider) { providers.put(authority, provider); } public static Status getStatus(Account account, String authority) { return getStatus(account, authority, false); } /** * Retrieve information on the status of the given account. * * @param account the account * @param authority the authority * @param create whether to create if no such account is found * @return the account's status */ public static Status getStatus(Account account, String authority, boolean create) { Map map = syncableAccounts.get(authority); if (map == null) { map = new HashMap<>(); syncableAccounts.put(authority, map); } Status status = map.get(account); if (status == null && create) { status = new Status(); map.put(account, status); } return status; } /** * @deprecated This method affects all calls, and does not work with {@link * android.content.ContentResolver#acquireContentProviderClient} */ @Deprecated public void setCursor(BaseCursor cursor) { this.cursor = cursor; } /** * @deprecated This method does not work with {@link * android.content.ContentResolver#acquireContentProviderClient} */ @Deprecated public void setCursor(Uri uri, BaseCursor cursorForUri) { uriCursorMap.put(uri, cursorForUri); } /** * @deprecated This method affects all calls, and does not work with {@link * android.content.ContentResolver#acquireContentProviderClient} */ @Deprecated @SuppressWarnings({"unused", "WeakerAccess"}) public void setNextDatabaseIdForInserts(int nextId) { nextDatabaseIdForInserts = nextId; } /** * Returns the list of {@link InsertStatement}s, {@link UpdateStatement}s, and {@link * DeleteStatement}s invoked on this {@link ContentResolver}. * * @return a list of statements * @deprecated This method does not work with {@link * android.content.ContentResolver#acquireContentProviderClient} */ @Deprecated @SuppressWarnings({"unused", "WeakerAccess"}) public List getStatements() { return statements; } /** * Returns the list of {@link InsertStatement}s for corresponding calls to {@link * ContentResolver#insert(Uri, ContentValues)} or {@link ContentResolver#bulkInsert(Uri, * ContentValues[])}. * * @return a list of insert statements * @deprecated This method does not work with {@link * android.content.ContentResolver#acquireContentProviderClient} */ @Deprecated @SuppressWarnings({"unused", "WeakerAccess"}) public List getInsertStatements() { return insertStatements; } /** * Returns the list of {@link UpdateStatement}s for corresponding calls to {@link * ContentResolver#update(Uri, ContentValues, String, String[])}. * * @return a list of update statements * @deprecated This method does not work with {@link * android.content.ContentResolver#acquireContentProviderClient} */ @Deprecated @SuppressWarnings({"unused", "WeakerAccess"}) public List getUpdateStatements() { return updateStatements; } @Deprecated @SuppressWarnings({"unused", "WeakerAccess"}) public List getDeletedUris() { List uris = new ArrayList<>(); for (DeleteStatement deleteStatement : deleteStatements) { uris.add(deleteStatement.getUri()); } return uris; } /** * Returns the list of {@link DeleteStatement}s for corresponding calls to {@link * ContentResolver#delete(Uri, String, String[])}. * * @return a list of delete statements */ @Deprecated @SuppressWarnings({"unused", "WeakerAccess"}) public List getDeleteStatements() { return deleteStatements; } @Deprecated @SuppressWarnings({"unused", "WeakerAccess"}) public List getNotifiedUris() { return notifiedUris; } @Deprecated public List getContentProviderOperations(String authority) { List operations = contentProviderOperations.get(authority); if (operations == null) { return new ArrayList<>(); } return operations; } private final Map registerContentProviderExceptions = new HashMap<>(); /** Makes {@link #registerContentObserver} throw the specified exception for the specified URI. */ public void setRegisterContentProviderException(Uri uri, RuntimeException exception) { registerContentProviderExceptions.put(uri, exception); } /** * Clears an exception previously set with {@link #setRegisterContentProviderException(Uri, * RuntimeException)}. */ public void clearRegisterContentProviderException(Uri uri) { registerContentProviderExceptions.remove(uri); } @Implementation protected void registerContentObserver( Uri uri, boolean notifyForDescendents, ContentObserver observer) { if (uri == null || observer == null) { throw new NullPointerException(); } if (registerContentProviderExceptions.containsKey(uri)) { throw registerContentProviderExceptions.get(uri); } contentObservers.add(new ContentObserverEntry(uri, notifyForDescendents, observer)); } @Implementation protected void registerContentObserver( Uri uri, boolean notifyForDescendents, ContentObserver observer, int userHandle) { registerContentObserver(uri, notifyForDescendents, observer); } @Implementation protected void unregisterContentObserver(ContentObserver observer) { synchronized (contentObservers) { for (ContentObserverEntry entry : contentObservers) { if (entry.observer == observer) { contentObservers.remove(entry); } } } } @Implementation protected static SyncAdapterType[] getSyncAdapterTypes() { return syncAdapterTypes; } /** Sets the SyncAdapterType array which will be returned by {@link #getSyncAdapterTypes()}. */ public static void setSyncAdapterTypes(SyncAdapterType[] syncAdapterTypes) { ShadowContentResolver.syncAdapterTypes = syncAdapterTypes; } /** * Returns the content observers registered for updates under the given URI. * *

Will be empty if no observer is registered. * * @param uri Given URI * @return The content observers, or null */ public Collection getContentObservers(Uri uri) { ArrayList observers = new ArrayList<>(1); for (ContentObserverEntry entry : contentObservers) { if (entry.matches(uri)) { observers.add(entry.observer); } } return observers; } @Implementation(minSdk = Q) protected static void onDbCorruption(String tag, String message, Throwable stacktrace) { // No-op. } private static ContentProvider createAndInitialize(ProviderInfo providerInfo) { try { ContentProvider provider = (ContentProvider) Class.forName(providerInfo.name).getDeclaredConstructor().newInstance(); provider.attachInfo(RuntimeEnvironment.application, providerInfo); return provider; } catch (InstantiationException | ClassNotFoundException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { throw new RuntimeException("Error instantiating class " + providerInfo.name, e); } } private BaseCursor getCursor(Uri uri) { if (uriCursorMap.get(uri) != null) { return uriCursorMap.get(uri); } else if (cursor != null) { return cursor; } else { return null; } } private static boolean isBundleEqual(Bundle bundle1, Bundle bundle2) { if (bundle1 == null || bundle2 == null) { return false; } if (bundle1.size() != bundle2.size()) { return false; } for (String key : bundle1.keySet()) { if (!bundle1.get(key).equals(bundle2.get(key))) { return false; } } return true; } /** A statement used to modify content in a {@link ContentProvider}. */ public static class Statement { private final Uri uri; private final ContentProvider contentProvider; Statement(Uri uri, ContentProvider contentProvider) { this.uri = uri; this.contentProvider = contentProvider; } public Uri getUri() { return uri; } @SuppressWarnings({"unused", "WeakerAccess"}) public ContentProvider getContentProvider() { return contentProvider; } } /** A statement used to insert content into a {@link ContentProvider}. */ public static class InsertStatement extends Statement { private final ContentValues[] bulkContentValues; InsertStatement(Uri uri, ContentProvider contentProvider, ContentValues contentValues) { super(uri, contentProvider); this.bulkContentValues = new ContentValues[] {contentValues}; } InsertStatement(Uri uri, ContentProvider contentProvider, ContentValues[] bulkContentValues) { super(uri, contentProvider); this.bulkContentValues = bulkContentValues; } @SuppressWarnings({"unused", "WeakerAccess"}) public ContentValues getContentValues() { if (bulkContentValues.length != 1) { throw new ArrayIndexOutOfBoundsException("bulk insert, use getBulkContentValues() instead"); } return bulkContentValues[0]; } @SuppressWarnings({"unused", "WeakerAccess"}) public ContentValues[] getBulkContentValues() { return bulkContentValues; } } /** A statement used to update content in a {@link ContentProvider}. */ public static class UpdateStatement extends Statement { private final ContentValues values; private final String where; private final String[] selectionArgs; UpdateStatement( Uri uri, ContentProvider contentProvider, ContentValues values, String where, String[] selectionArgs) { super(uri, contentProvider); this.values = values; this.where = where; this.selectionArgs = selectionArgs; } @SuppressWarnings({"unused", "WeakerAccess"}) public ContentValues getContentValues() { return values; } @SuppressWarnings({"unused", "WeakerAccess"}) public String getWhere() { return where; } @SuppressWarnings({"unused", "WeakerAccess"}) public String[] getSelectionArgs() { return selectionArgs; } } /** A statement used to delete content in a {@link ContentProvider}. */ public static class DeleteStatement extends Statement { private final String where; private final String[] selectionArgs; DeleteStatement( Uri uri, ContentProvider contentProvider, String where, String[] selectionArgs) { super(uri, contentProvider); this.where = where; this.selectionArgs = selectionArgs; } @SuppressWarnings({"unused", "WeakerAccess"}) public String getWhere() { return where; } @SuppressWarnings({"unused", "WeakerAccess"}) public String[] getSelectionArgs() { return selectionArgs; } } private static class UnregisteredInputStream extends InputStream implements NamedStream { private final Uri uri; UnregisteredInputStream(Uri uri) { this.uri = uri; } @Override public int read() throws IOException { throw new UnsupportedOperationException( "You must use ShadowContentResolver.registerInputStream() in order to call read()"); } @Override public int read(byte[] b) throws IOException { throw new UnsupportedOperationException( "You must use ShadowContentResolver.registerInputStream() in order to call read()"); } @Override public int read(byte[] b, int off, int len) throws IOException { throw new UnsupportedOperationException( "You must use ShadowContentResolver.registerInputStream() in order to call read()"); } @Override public String toString() { return "stream for " + uri; } } @ForType(ContentResolver.class) interface ContentResolverReflector { @Accessor("mContext") Context getContext(); @Direct InputStream openInputStream(Uri uri) throws FileNotFoundException; @Direct OutputStream openOutputStream(Uri uri, String mode) throws FileNotFoundException; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy