package com.getbase.android.db.loaders; /* * Copyright (C) 2011 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. */ import com.getbase.android.db.common.QueryData; import com.google.common.base.Function; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.support.annotation.Nullable; import android.support.v4.util.Pair; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; import java.util.List; public class ComposedCursorLoader<T> extends AbstractLoader<T> { private final DisableableContentObserver mObserver; private final ImmutableList<Uri> mNotificationUris; private boolean mAdditionalUrisRegistered; Uri mUri; String[] mProjection; String mSelection; String[] mSelectionArgs; String mSortOrder; private final Function<Cursor, T> mCursorTransformation; private IdentityLinkedMap<T, Cursor> cursorsForResults = new IdentityLinkedMap<>(); private final List<Pair<T, Cursor>> pendingLoadResults = new LinkedList<>(); @Override protected void onStartLoading() { mObserver.setEnabled(true); super.onStartLoading(); } /* Runs on a worker thread */ @Override public T loadInBackground() { try { final Cursor cursor = loadCursorInBackground(); final T result = mCursorTransformation.apply(cursor); Preconditions.checkNotNull(result, "Function passed to this loader should never return null."); synchronized (pendingLoadResults) { pendingLoadResults.add(new Pair<>(result, cursor)); } return result; } catch (Throwable t) { throw new RuntimeException("Error occurred when running loader: " + this, t); } } private Cursor loadCursorInBackground() { Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection, mSelectionArgs, mSortOrder); if (cursor != null) { // Ensure the cursor window is filled cursor.getCount(); } return cursor; } @Override public void deliverResult(T result) { Cursor cursor = takePendingCursorForResult(result); if (cursor != null) { if (cursorsForResults.get(result) != null) { releaseCursor(cursor); } else { cursorsForResults.put(result, cursor); } } super.deliverResult(result); } @Override public void onCanceled(T result) { releaseCursor(takePendingCursorForResult(result)); } @Nullable private Cursor takePendingCursorForResult(T result) { synchronized (pendingLoadResults) { Iterator<Pair<T, Cursor>> i = pendingLoadResults.iterator(); while (i.hasNext()) { Pair<T, Cursor> entry = i.next(); if (entry.first == result) { i.remove(); return entry.second; } } return null; } } @Override protected void onNewDataDelivered(T data) { cursorsForResults.get(data).registerContentObserver(mObserver); if (!mAdditionalUrisRegistered) { ContentResolver resolver = getContext().getContentResolver(); for (Uri notificationUri : mNotificationUris) { resolver.registerContentObserver(notificationUri, true, mObserver); } mAdditionalUrisRegistered = true; } } public ComposedCursorLoader(Context context, QueryData queryData, ImmutableList<Uri> notificationUris, Function<Cursor, T> cursorTransformation) { super(context); mObserver = new DisableableContentObserver(new ForceLoadContentObserver()); mUri = queryData.getUri(); mProjection = queryData.getProjection(); mSelection = queryData.getSelection(); mSelectionArgs = queryData.getSelectionArgs(); mSortOrder = queryData.getOrderBy(); mCursorTransformation = cursorTransformation; mNotificationUris = notificationUris; } @Override protected void onAbandon() { mObserver.setEnabled(false); unregisterAdditionalUris(); } @Override protected void onReset() { mObserver.setEnabled(false); unregisterAdditionalUris(); super.onReset(); } @Override protected void releaseResources(T result) { releaseCursor(cursorsForResults.remove(result)); } private void unregisterAdditionalUris() { if (mAdditionalUrisRegistered) { getContext().getContentResolver().unregisterContentObserver(mObserver); mAdditionalUrisRegistered = false; } } private void releaseCursor(@Nullable Cursor cursor) { if (cursor != null && !cursor.isClosed()) { cursor.close(); } } @Override public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { super.dump(prefix, fd, writer, args); writer.print(prefix); writer.print("mUri="); writer.println(mUri); writer.print(prefix); writer.print("mProjection="); writer.println(Arrays.toString(mProjection)); writer.print(prefix); writer.print("mSelection="); writer.println(mSelection); writer.print(prefix); writer.print("mSelectionArgs="); writer.println(Arrays.toString(mSelectionArgs)); writer.print(prefix); writer.print("mSortOrder="); writer.println(mSortOrder); writer.print(prefix); writer.print("mResult="); writer.println(mResult); } @Override public String toString() { return MoreObjects .toStringHelper(this) .add("mId", getId()) .add("mUri", mUri) .add("mProjection", Arrays.toString(mProjection)) .add("mSelection", mSelection) .add("mSelectionArgs", Arrays.toString(mSelectionArgs)) .add("mSortOrder", mSortOrder) .toString(); } }