io.github.hidroh.materialistic.data.FavoriteManager.java Source code

Java tutorial

Introduction

Here is the source code for io.github.hidroh.materialistic.data.FavoriteManager.java

Source

/*
 * Copyright (c) 2015 Ha Duy Trung
 *
 * 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 io.github.hidroh.materialistic.data;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.CursorWrapper;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.WorkerThread;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;

import java.util.ArrayList;
import java.util.Collection;

import javax.inject.Inject;

import io.github.hidroh.materialistic.annotation.Synthetic;
import rx.Observable;
import rx.Scheduler;
import rx.android.schedulers.AndroidSchedulers;

/**
 * Data repository for {@link Favorite}
 */
public class FavoriteManager implements LocalItemManager<Favorite> {

    private static final int LOADER = 0;
    /**
     * {@link android.content.Intent#getAction()} for broadcasting getting favorites matching query
     */
    public static final String ACTION_GET = FavoriteManager.class.getName() + ".ACTION_GET";
    /**
     * {@link android.os.Bundle} key for {@link #ACTION_GET} that contains {@link ArrayList} of
     * {@link Favorite}
     */
    public static final String ACTION_GET_EXTRA_DATA = ACTION_GET + ".EXTRA_DATA";
    private static final String URI_PATH_ADD = "add";
    private static final String URI_PATH_REMOVE = "remove";
    private static final String URI_PATH_CLEAR = "clear";
    private final Scheduler mIoScheduler;
    @Synthetic
    Cursor mCursor;

    @Override
    public int getSize() {
        return mCursor != null ? mCursor.getCount() : 0;
    }

    @Override
    public Favorite getItem(int position) {
        return mCursor.moveToPosition(position) ? mCursor.getFavorite() : null;
    }

    @Override
    public void attach(@NonNull Context context, @NonNull LoaderManager loaderManager, @NonNull Observer observer,
            String filter) {
        loaderManager.restartLoader(FavoriteManager.LOADER, null,
                new LoaderManager.LoaderCallbacks<android.database.Cursor>() {
                    @Override
                    public Loader<android.database.Cursor> onCreateLoader(int id, Bundle args) {
                        if (!TextUtils.isEmpty(filter)) {
                            return new FavoriteManager.CursorLoader(context, filter);
                        }
                        return new FavoriteManager.CursorLoader(context);
                    }

                    @Override
                    public void onLoadFinished(Loader<android.database.Cursor> loader,
                            android.database.Cursor data) {
                        if (data != null) {
                            data.setNotificationUri(context.getContentResolver(),
                                    MaterialisticProvider.URI_FAVORITE);
                            mCursor = new Cursor(data);
                        } else {
                            mCursor = null;
                        }
                        observer.onChanged();
                    }

                    @Override
                    public void onLoaderReset(Loader<android.database.Cursor> loader) {
                        mCursor = null;
                        observer.onChanged();
                    }
                });
    }

    @Override
    public void detach() {
        if (mCursor != null) {
            mCursor.close();
        }
    }

    @Inject
    public FavoriteManager(Scheduler ioScheduler) {
        mIoScheduler = ioScheduler;
    }

    /**
     * Gets all favorites matched given query, a {@link #ACTION_GET} broadcast will be sent upon
     * completion
     * @param context   an instance of {@link android.content.Context}
     * @param query     query to filter stories to be retrieved
     * @see #makeGetIntentFilter()
     */
    public void get(Context context, String query) {
        Observable.defer(() -> Observable.just(query)).map(filter -> query(context, filter))
                .filter(cursor -> cursor != null && cursor.moveToFirst()).map(cursor -> {
                    ArrayList<Favorite> favorites = new ArrayList<>(cursor.getCount());
                    Cursor favoriteCursor = new Cursor(cursor);
                    do {
                        favorites.add(favoriteCursor.getFavorite());
                    } while (favoriteCursor.moveToNext());
                    favoriteCursor.close();
                    return favorites;
                }).defaultIfEmpty(new ArrayList<>()).map(FavoriteManager::makeGetBroadcastIntent)
                .subscribeOn(mIoScheduler).observeOn(AndroidSchedulers.mainThread())
                .subscribe(LocalBroadcastManager.getInstance(context)::sendBroadcast);
    }

    /**
     * Adds given story as favorite
     * @param context   an instance of {@link android.content.Context}
     * @param story     story to be added as favorite
     */
    public void add(Context context, WebItem story) {
        Observable.defer(() -> Observable.just(story)).map(item -> {
            insert(context, item);
            return item.getId();
        }).map(itemId -> buildAdded().appendPath(story.getId()).build()).subscribeOn(mIoScheduler)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(uri -> context.getContentResolver().notifyChange(uri, null));
        ItemSyncAdapter.initSync(context, story.getId());
    }

    /**
     * Clears all stories matched given query from favorites
     * will be sent upon completion
     * @param context   an instance of {@link android.content.Context}
     * @param query     query to filter stories to be cleared
     */
    public void clear(Context context, String query) {
        Observable.defer(() -> Observable.just(query)).map(filter -> deleteMultiple(context, filter))
                .subscribeOn(mIoScheduler).observeOn(AndroidSchedulers.mainThread())
                .subscribe(count -> context.getContentResolver().notifyChange(buildCleared().build(), null));
    }

    /**
     * Removes story with given ID from favorites
     * upon completion
     * @param context   an instance of {@link android.content.Context}
     * @param itemId    story ID to be removed from favorites
     */
    public void remove(Context context, String itemId) {
        if (itemId == null) {
            return;
        }
        Observable.defer(() -> Observable.just(itemId)).map(id -> {
            delete(context, id);
            return id;
        }).map(id -> buildRemoved().appendPath(itemId).build()).subscribeOn(mIoScheduler)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(uri -> context.getContentResolver().notifyChange(uri, null));
    }

    /**
     * Removes multiple stories with given IDs from favorites
     * be sent upon completion
     * @param context   an instance of {@link android.content.Context}
     * @param itemIds   array of story IDs to be removed from favorites
     */
    public void remove(Context context, Collection<String> itemIds) {
        if (itemIds == null || itemIds.isEmpty()) {
            return;
        }
        Observable.defer(() -> Observable.from(itemIds)).subscribeOn(mIoScheduler).map(itemId -> {
            delete(context, itemId);
            return itemId;
        }).map(itemId -> buildRemoved().appendPath(itemId).build()).observeOn(AndroidSchedulers.mainThread())
                .subscribe(uri -> context.getContentResolver().notifyChange(uri, null));
    }

    @WorkerThread
    @NonNull
    Observable<Boolean> check(ContentResolver contentResolver, String itemId) {
        if (TextUtils.isEmpty(itemId)) {
            return Observable.just(false);
        }
        android.database.Cursor cursor = contentResolver.query(MaterialisticProvider.URI_FAVORITE, null,
                MaterialisticProvider.FavoriteEntry.COLUMN_NAME_ITEM_ID + " = ?", new String[] { itemId }, null);
        boolean result = false;
        if (cursor != null) {
            result = cursor.getCount() > 0;
            cursor.close();
        }
        return Observable.just(result);
    }

    @WorkerThread
    private android.database.Cursor query(Context context, String filter) {
        String selection;
        String[] selectionArgs;
        if (TextUtils.isEmpty(filter)) {
            selection = null;
            selectionArgs = null;

        } else {
            selection = MaterialisticProvider.FavoriteEntry.COLUMN_NAME_TITLE + " LIKE ?";
            selectionArgs = new String[] { "%" + filter + "%" };
        }
        return context.getContentResolver().query(MaterialisticProvider.URI_FAVORITE, null, selection,
                selectionArgs, null);
    }

    @WorkerThread
    private Uri insert(Context context, WebItem story) {
        ContentValues cv = new ContentValues();
        cv.put(MaterialisticProvider.FavoriteEntry.COLUMN_NAME_ITEM_ID, story.getId());
        cv.put(MaterialisticProvider.FavoriteEntry.COLUMN_NAME_URL, story.getUrl());
        cv.put(MaterialisticProvider.FavoriteEntry.COLUMN_NAME_TITLE, story.getDisplayedTitle());
        cv.put(MaterialisticProvider.FavoriteEntry.COLUMN_NAME_TIME,
                story instanceof Favorite ? String.valueOf(((Favorite) story).getTime())
                        : String.valueOf(System.currentTimeMillis()));
        return context.getContentResolver().insert(MaterialisticProvider.URI_FAVORITE, cv);
    }

    @WorkerThread
    private int delete(Context context, String itemId) {
        return context.getContentResolver().delete(MaterialisticProvider.URI_FAVORITE,
                MaterialisticProvider.FavoriteEntry.COLUMN_NAME_ITEM_ID + " = ?", new String[] { itemId });
    }

    @WorkerThread
    private int deleteMultiple(Context context, String query) {
        String selection;
        String[] selectionArgs;
        if (TextUtils.isEmpty(query)) {
            selection = null;
            selectionArgs = null;
        } else {
            selection = MaterialisticProvider.FavoriteEntry.COLUMN_NAME_TITLE + " LIKE ?";
            selectionArgs = new String[] { "%" + query + "%" };
        }
        return context.getContentResolver().delete(MaterialisticProvider.URI_FAVORITE, selection, selectionArgs);
    }

    /**
     * Creates an intent filter for get action broadcast
     * @return get intent filter
     * @see #get(android.content.Context, String)
     */
    public static IntentFilter makeGetIntentFilter() {
        return new IntentFilter(ACTION_GET);
    }

    public static boolean isAdded(Uri uri) {
        return uri.toString().startsWith(buildAdded().toString());
    }

    public static boolean isRemoved(Uri uri) {
        return uri.toString().startsWith(buildRemoved().toString());
    }

    public static boolean isCleared(Uri uri) {
        return uri.toString().startsWith(buildCleared().toString());
    }

    private static Uri.Builder buildAdded() {
        return MaterialisticProvider.URI_FAVORITE.buildUpon().appendPath(URI_PATH_ADD);
    }

    private static Uri.Builder buildRemoved() {
        return MaterialisticProvider.URI_FAVORITE.buildUpon().appendPath(URI_PATH_REMOVE);
    }

    private static Uri.Builder buildCleared() {
        return MaterialisticProvider.URI_FAVORITE.buildUpon().appendPath(URI_PATH_CLEAR);
    }

    private static Intent makeGetBroadcastIntent(ArrayList<Favorite> favorites) {
        final Intent intent = new Intent(ACTION_GET);
        intent.putExtra(ACTION_GET_EXTRA_DATA, favorites);
        return intent;
    }

    /**
     * A cursor wrapper to retrieve associated {@link Favorite}
     */
    static class Cursor extends CursorWrapper {
        Cursor(android.database.Cursor cursor) {
            super(cursor);
        }

        Favorite getFavorite() {
            final String itemId = getString(
                    getColumnIndexOrThrow(MaterialisticProvider.FavoriteEntry.COLUMN_NAME_ITEM_ID));
            final String url = getString(
                    getColumnIndexOrThrow(MaterialisticProvider.FavoriteEntry.COLUMN_NAME_URL));
            final String title = getString(
                    getColumnIndexOrThrow(MaterialisticProvider.FavoriteEntry.COLUMN_NAME_TITLE));
            final String time = getString(
                    getColumnIndexOrThrow(MaterialisticProvider.FavoriteEntry.COLUMN_NAME_TIME));
            return new Favorite(itemId, url, title, Long.valueOf(time));
        }
    }

    /**
     * A {@link android.support.v4.content.CursorLoader} to query {@link Favorite}
     */
    static class CursorLoader extends android.support.v4.content.CursorLoader {
        /**
         * Constructs a cursor loader to query all {@link Favorite}
         * @param context    an instance of {@link android.content.Context}
         */
        CursorLoader(Context context) {
            super(context, MaterialisticProvider.URI_FAVORITE, null, null, null, null);
        }

        /**
         * Constructs a cursor loader to query {@link Favorite}
         * with title matching given query
         * @param context   an instance of {@link android.content.Context}
         * @param query     query to filter
         */
        CursorLoader(Context context, String query) {
            super(context, MaterialisticProvider.URI_FAVORITE, null,
                    MaterialisticProvider.FavoriteEntry.COLUMN_NAME_TITLE + " LIKE ?",
                    new String[] { "%" + query + "%" }, null);
        }
    }
}