org.opensilk.music.library.provider.LibraryProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.opensilk.music.library.provider.LibraryProvider.java

Source

/*
 * Copyright (c) 2015 OpenSilk Productions LLC
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.opensilk.music.library.provider;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;

import org.apache.commons.lang3.StringUtils;
import org.opensilk.music.library.compare.AlbumCompare;
import org.opensilk.music.library.compare.ArtistCompare;
import org.opensilk.music.library.compare.BundleableCompare;
import org.opensilk.music.library.compare.FolderTrackCompare;
import org.opensilk.music.library.compare.TrackCompare;
import org.opensilk.music.library.internal.BundleableListTransformer;
import org.opensilk.music.library.internal.BundleableSubscriber;
import org.opensilk.music.library.LibraryConfig;
import org.opensilk.music.library.internal.LibraryException;
import org.opensilk.music.library.sort.BundleableSortOrder;
import org.opensilk.music.library.provider.LibraryMethods.Extras;
import org.opensilk.music.model.Album;
import org.opensilk.music.model.Artist;
import org.opensilk.music.model.Folder;
import org.opensilk.music.model.Genre;
import org.opensilk.music.model.Playlist;
import org.opensilk.music.model.Track;
import org.opensilk.music.model.spi.Bundleable;

import java.lang.reflect.Method;
import java.util.List;

import hugo.weaving.DebugLog;
import rx.Observable;
import rx.Scheduler;
import rx.Subscriber;
import rx.functions.Func1;
import rx.schedulers.Schedulers;

import static org.opensilk.music.library.provider.LibraryUris.*;
import static org.opensilk.music.library.internal.LibraryException.Kind.*;

/**
 * Created by drew on 4/26/15.
 */
public abstract class LibraryProvider extends ContentProvider {
    public static final String TAG = LibraryProvider.class.getSimpleName();

    /**
     * Authority prefix all libraries must start with to be discoverable by orpheus
     */
    public static final String AUTHORITY_PFX = "orpheus.library.";

    /**
     * Our full authority
     */
    protected String mAuthority;

    private UriMatcher mMatcher;
    private Scheduler scheduler = Schedulers.computation();

    @Override
    public boolean onCreate() {
        mAuthority = AUTHORITY_PFX + getBaseAuthority();
        mMatcher = LibraryUris.makeMatcher(mAuthority);
        return true;
    }

    /**
     * Base authority for library appended to {@link #AUTHORITY_PFX}
     * default is package name, this is usually sufficient unless package contains
     * multiple libraries
     */
    protected String getBaseAuthority() {
        return getContext().getPackageName();
    }

    /**
     * @return This libraries config
     */
    protected abstract LibraryConfig getLibraryConfig();

    @Override
    public final Bundle call(String method, String arg, Bundle extras) {

        final Bundle ok = new Bundle();
        ok.putBoolean(Extras.OK, true);

        if (method == null)
            method = "";
        switch (method) {
        case LibraryMethods.QUERY: {
            extras.setClassLoader(getClass().getClassLoader());

            final IBinder binder = getBinderCallbackFromBundle(extras);
            if (binder == null || !binder.isBinderAlive()) {
                //this is mostly for the null, if the binder is dead then
                //sending them a reason is moot. but we check the binder here
                //so we dont have to do it 50 times below and we can be pretty
                //sure the linkToDeath will succeed
                ok.putBoolean(Extras.OK, false);
                writeCause(ok, new LibraryException(BAD_BINDER, null));
                return ok;
            }

            final Uri uri = extras.getParcelable(Extras.URI);
            final List<String> pathSegments = uri.getPathSegments();
            if (pathSegments.size() < 3 || pathSegments.size() > 5) {
                Log.e(TAG, "Wrong number of path segments: uri=" + uri);
                ok.putBoolean(Extras.OK, false);
                writeCause(ok, new LibraryException(ILLEGAL_URI, new IllegalArgumentException(uri.toString())));
                return ok;
            }

            final String library = pathSegments.get(0);
            final String identity;
            if (pathSegments.size() > 3) {
                identity = pathSegments.get(3);
            } else {
                identity = null;
            }

            final Bundle args = new Bundle();
            args.putParcelable(Extras.URI, uri);
            String sortOrder = extras.getString(Extras.SORTORDER);
            args.putString(Extras.SORTORDER, sortOrder != null ? sortOrder : BundleableSortOrder.A_Z);

            switch (mMatcher.match(uri)) {
            case M_ALBUMS: {
                final BundleableSubscriber<Album> subscriber = new BundleableSubscriber<>(binder);
                queryAlbumsInternal(library, subscriber, args);
                break;
            }
            case M_ALBUM: {
                final BundleableSubscriber<Album> subscriber = new BundleableSubscriber<>(binder);
                getAlbumInternal(library, identity, subscriber, args);
                break;
            }
            case M_ALBUM_TRACKS: {
                final BundleableSubscriber<Track> subscriber = new BundleableSubscriber<>(binder);
                getAlbumTracksInternal(library, identity, subscriber, args);
                break;
            }
            case M_ARTISTS: {
                final BundleableSubscriber<Artist> subscriber = new BundleableSubscriber<>(binder);
                queryArtistsInternal(library, subscriber, args);
                break;
            }
            case M_ARTIST: {
                final BundleableSubscriber<Artist> subscriber = new BundleableSubscriber<>(binder);
                getArtistInternal(library, identity, subscriber, args);
                break;
            }
            case M_ARTIST_ALBUMS: {
                final BundleableSubscriber<Album> subscriber = new BundleableSubscriber<>(binder);
                getArtistAlbumsInternal(library, identity, subscriber, args);
                break;
            }
            case M_ARTIST_TRACKS: {
                final BundleableSubscriber<Track> subscriber = new BundleableSubscriber<>(binder);
                getArtistTracksInternal(library, identity, subscriber, args);
                break;
            }
            case M_FOLDERS:
            case M_FOLDER: {
                final BundleableSubscriber<Bundleable> subscriber = new BundleableSubscriber<>(binder);
                browseFoldersInternal(library, identity, subscriber, args);
                break;
            }
            case M_GENRES: {
                final BundleableSubscriber<Genre> subscriber = new BundleableSubscriber<>(binder);
                queryGenresInternal(library, subscriber, args);
                break;
            }
            case M_GENRE: {
                final BundleableSubscriber<Genre> subscriber = new BundleableSubscriber<>(binder);
                getGenreInternal(library, identity, subscriber, args);
                break;
            }
            case M_GENRE_ALBUMS: {
                final BundleableSubscriber<Album> subscriber = new BundleableSubscriber<>(binder);
                getGenreAlbumsInternal(library, identity, subscriber, args);
                break;
            }
            case M_GENRE_TRACKS: {
                final BundleableSubscriber<Track> subscriber = new BundleableSubscriber<>(binder);
                getGenreTracksInternal(library, identity, subscriber, args);
                break;
            }
            case M_PLAYLISTS: {
                final BundleableSubscriber<Playlist> subscriber = new BundleableSubscriber<>(binder);
                queryPlaylistsInternal(library, subscriber, args);
                break;
            }
            case M_PLAYLIST: {
                final BundleableSubscriber<Playlist> subscriber = new BundleableSubscriber<>(binder);
                getPlaylistInternal(library, identity, subscriber, args);
                break;
            }
            case M_PLAYLIST_TRACKS: {
                final BundleableSubscriber<Track> subscriber = new BundleableSubscriber<>(binder);
                getPlaylistTracksInternal(library, identity, subscriber, args);
                break;
            }
            case M_TRACKS: {
                final BundleableSubscriber<Track> subscriber = new BundleableSubscriber<>(binder);
                queryTracksInternal(library, subscriber, args);
                break;
            }
            case M_TRACK: {
                final BundleableSubscriber<Track> subscriber = new BundleableSubscriber<>(binder);
                getTrackInternal(library, identity, subscriber, args);
                break;
            }
            default: {
                ok.putBoolean(Extras.OK, false);
                writeCause(ok, new LibraryException(ILLEGAL_URI, new IllegalArgumentException(uri.toString())));
            }
            }
            return ok;
        }
        case LibraryMethods.LIBRARYCONF:
            return getLibraryConfig().dematerialize();
        default:
            Log.e(TAG, "Unknown method " + method);
            ok.putBoolean(Extras.OK, false);
            writeCause(ok, new LibraryException(METHOD_NOT_IMPLEMENTED, new UnsupportedOperationException(method)));
            return ok;
        }
    }

    //TODO cache all these observables

    /*
     * Start internal methods.
     * You can override these if you need specialized handling.
     *
     * You don't need to override these for caching, you can just send the cached list, then hit the network
     * and once it comes in send a notify on the Uri, Orpheus will requery and you can send the updated cached list.
     */

    @DebugLog
    protected void browseFoldersInternal(final String library, final String identity,
            final Subscriber<List<Bundleable>> subscriber, final Bundle args) {
        Observable<Bundleable> o = Observable.create(new Observable.OnSubscribe<Bundleable>() {
            @Override
            public void call(Subscriber<? super Bundleable> subscriber) {
                browseFolders(library, identity, subscriber, args);
            }
        }).subscribeOn(scheduler);
        final String q = args.<Uri>getParcelable(Extras.URI).getQueryParameter(Q.Q);
        if (StringUtils.equals(q, Q.FOLDERS_ONLY)) {
            o = o.filter(new Func1<Bundleable, Boolean>() {
                @Override
                public Boolean call(Bundleable bundleable) {
                    return bundleable instanceof Folder;
                }
            });
        } else if (StringUtils.equals(q, Q.TRACKS_ONLY)) {
            o = o.filter(new Func1<Bundleable, Boolean>() {
                @Override
                public Boolean call(Bundleable bundleable) {
                    return bundleable instanceof Track;
                }
            });
        }
        o.compose(new BundleableListTransformer<Bundleable>(
                FolderTrackCompare.func(args.getString(Extras.SORTORDER)))).subscribe(subscriber);
    }

    protected void queryAlbumsInternal(final String library, final Subscriber<List<Album>> subscriber,
            final Bundle args) {
        Observable.create(new Observable.OnSubscribe<Album>() {
            @Override
            public void call(Subscriber<? super Album> subscriber) {
                queryAlbums(library, subscriber, args);
            }
        }).subscribeOn(scheduler)
                .compose(new BundleableListTransformer<Album>(AlbumCompare.func(args.getString(Extras.SORTORDER))))
                .subscribe(subscriber);
    }

    protected void getAlbumInternal(final String library, final String identity,
            final Subscriber<List<Album>> subscriber, final Bundle args) {
        Observable.create(new Observable.OnSubscribe<Album>() {
            @Override
            public void call(Subscriber<? super Album> subscriber) {
                getAlbum(library, identity, subscriber, args);
            }
        }).subscribeOn(scheduler).first().compose(new BundleableListTransformer<Album>(null)).subscribe(subscriber);
    }

    protected void getAlbumTracksInternal(final String library, final String identity,
            final Subscriber<List<Track>> subscriber, final Bundle args) {
        Observable.create(new Observable.OnSubscribe<Track>() {
            @Override
            public void call(Subscriber<? super Track> subscriber) {
                getAlbumTracks(library, identity, subscriber, args);
            }
        }).subscribeOn(scheduler)
                .compose(new BundleableListTransformer<Track>(TrackCompare.func(args.getString(Extras.SORTORDER))))
                .subscribe(subscriber);
    }

    protected void queryArtistsInternal(final String library, final Subscriber<List<Artist>> subscriber,
            final Bundle args) {
        Observable.create(new Observable.OnSubscribe<Artist>() {
            @Override
            public void call(Subscriber<? super Artist> subscriber) {
                queryArtists(library, subscriber, args);
            }
        }).subscribeOn(scheduler)
                .compose(
                        new BundleableListTransformer<Artist>(ArtistCompare.func(args.getString(Extras.SORTORDER))))
                .subscribe(subscriber);
    }

    protected void getArtistInternal(final String library, final String identity,
            final Subscriber<List<Artist>> subscriber, final Bundle args) {
        Observable.create(new Observable.OnSubscribe<Artist>() {
            @Override
            public void call(Subscriber<? super Artist> subscriber) {
                getArtist(library, identity, subscriber, args);
            }
        }).subscribeOn(scheduler).first().compose(new BundleableListTransformer<Artist>(null))
                .subscribe(subscriber);
    }

    protected void getArtistAlbumsInternal(final String library, final String identity,
            final Subscriber<List<Album>> subscriber, final Bundle args) {
        Observable.create(new Observable.OnSubscribe<Album>() {
            @Override
            public void call(Subscriber<? super Album> subscriber) {
                getArtistAlbums(library, identity, subscriber, args);
            }
        }).subscribeOn(scheduler)
                .compose(new BundleableListTransformer<Album>(AlbumCompare.func(args.getString(Extras.SORTORDER))))
                .subscribe(subscriber);
    }

    protected void getArtistTracksInternal(final String library, final String identity,
            final Subscriber<List<Track>> subscriber, final Bundle args) {
        Observable.create(new Observable.OnSubscribe<Track>() {
            @Override
            public void call(Subscriber<? super Track> subscriber) {
                getArtistTracks(library, identity, subscriber, args);
            }
        }).subscribeOn(scheduler)
                .compose(new BundleableListTransformer<Track>(TrackCompare.func(args.getString(Extras.SORTORDER))))
                .subscribe(subscriber);
    }

    protected void queryGenresInternal(final String library, final Subscriber<List<Genre>> subscriber,
            final Bundle args) {
        Observable.create(new Observable.OnSubscribe<Genre>() {
            @Override
            public void call(Subscriber<? super Genre> subscriber) {
                queryGenres(library, subscriber, args);
            }
        }).subscribeOn(scheduler).compose(new BundleableListTransformer<Genre>(
                BundleableCompare.<Genre>func(args.getString(Extras.SORTORDER)))).subscribe(subscriber);
    }

    protected void getGenreInternal(final String library, final String identity,
            final Subscriber<List<Genre>> subscriber, final Bundle args) {
        Observable.create(new Observable.OnSubscribe<Genre>() {
            @Override
            public void call(Subscriber<? super Genre> subscriber) {
                getGenre(library, identity, subscriber, args);
            }
        }).subscribeOn(scheduler).first().compose(new BundleableListTransformer<Genre>(null)).subscribe(subscriber);
    }

    protected void getGenreAlbumsInternal(final String library, final String identity,
            final Subscriber<List<Album>> subscriber, final Bundle args) {
        Observable.create(new Observable.OnSubscribe<Album>() {
            @Override
            public void call(Subscriber<? super Album> subscriber) {
                getGenreAlbums(library, identity, subscriber, args);
            }
        }).subscribeOn(scheduler)
                .compose(new BundleableListTransformer<Album>(AlbumCompare.func(args.getString(Extras.SORTORDER))))
                .subscribe(subscriber);
    }

    protected void getGenreTracksInternal(final String library, final String identity,
            final Subscriber<List<Track>> subscriber, final Bundle args) {
        Observable.create(new Observable.OnSubscribe<Track>() {
            @Override
            public void call(Subscriber<? super Track> subscriber) {
                getGenreTracks(library, identity, subscriber, args);
            }
        }).subscribeOn(scheduler)
                .compose(new BundleableListTransformer<Track>(TrackCompare.func(args.getString(Extras.SORTORDER))))
                .subscribe(subscriber);
    }

    protected void queryPlaylistsInternal(final String library, final Subscriber<List<Playlist>> subscriber,
            final Bundle args) {
        Observable.create(new Observable.OnSubscribe<Playlist>() {
            @Override
            public void call(Subscriber<? super Playlist> subscriber) {
                queryPlaylists(library, subscriber, args);
            }
        }).subscribeOn(scheduler).compose(new BundleableListTransformer<Playlist>(
                BundleableCompare.<Playlist>func(args.getString(Extras.SORTORDER)))).subscribe(subscriber);
    }

    protected void getPlaylistInternal(final String library, final String identity,
            final Subscriber<List<Playlist>> subscriber, final Bundle args) {
        Observable.create(new Observable.OnSubscribe<Playlist>() {
            @Override
            public void call(Subscriber<? super Playlist> subscriber) {
                getPlaylist(library, identity, subscriber, args);
            }
        }).subscribeOn(scheduler).first().compose(new BundleableListTransformer<Playlist>(null))
                .subscribe(subscriber);
    }

    protected void getPlaylistTracksInternal(final String library, final String identity,
            final Subscriber<List<Track>> subscriber, final Bundle args) {
        Observable.create(new Observable.OnSubscribe<Track>() {
            @Override
            public void call(Subscriber<? super Track> subscriber) {
                getPlaylistTracks(library, identity, subscriber, args);
            }
        }).subscribeOn(scheduler).compose(new BundleableListTransformer<Track>(null))//No sort
                .subscribe(subscriber);
    }

    protected void queryTracksInternal(final String library, final Subscriber<List<Track>> subscriber,
            final Bundle args) {
        Observable.create(new Observable.OnSubscribe<Track>() {
            @Override
            public void call(Subscriber<? super Track> subscriber) {
                queryTracks(library, subscriber, args);
            }
        }).subscribeOn(scheduler)
                .compose(new BundleableListTransformer<Track>(TrackCompare.func(args.getString(Extras.SORTORDER))))
                .subscribe(subscriber);
    }

    protected void getTrackInternal(final String library, final String identity,
            final Subscriber<List<Track>> subscriber, final Bundle args) {
        Observable.create(new Observable.OnSubscribe<Track>() {
            @Override
            public void call(Subscriber<? super Track> subscriber) {
                getTrack(library, identity, subscriber, args);
            }
        }).subscribeOn(scheduler).first().compose(new BundleableListTransformer<Track>(null)).subscribe(subscriber);
    }

    /*
     * Start query stubs
     *
     * Primary handlers for library, You must override all methods corresponding to the abilitys
     * you declare in your config
     *
     * You must call onComplete after emitting the list
     */

    protected void browseFolders(String library, String identity, Subscriber<? super Bundleable> subscriber,
            Bundle args) {
        subscriber.onError(new UnsupportedOperationException());
    }

    protected void queryAlbums(String library, Subscriber<? super Album> subscriber, Bundle args) {
        subscriber.onError(new UnsupportedOperationException());
    }

    protected void getAlbum(String library, String identity, Subscriber<? super Album> subscriber, Bundle args) {
        subscriber.onError(new UnsupportedOperationException());
    }

    protected void getAlbumTracks(String library, String identity, Subscriber<? super Track> subscriber,
            Bundle args) {
        subscriber.onError(new UnsupportedOperationException());
    }

    protected void queryArtists(String library, Subscriber<? super Artist> subscriber, Bundle args) {
        subscriber.onError(new UnsupportedOperationException());
    }

    protected void getArtist(String library, String identity, Subscriber<? super Artist> subscriber, Bundle args) {
        subscriber.onError(new UnsupportedOperationException());
    }

    protected void getArtistAlbums(String library, String identity, Subscriber<? super Album> subscriber,
            Bundle args) {
        subscriber.onError(new UnsupportedOperationException());
    }

    protected void getArtistTracks(String library, String identity, Subscriber<? super Track> subscriber,
            Bundle args) {
        subscriber.onError(new UnsupportedOperationException());
    }

    protected void queryGenres(String library, Subscriber<? super Genre> subscriber, Bundle args) {
        subscriber.onError(new UnsupportedOperationException());
    }

    protected void getGenre(String library, String identity, Subscriber<? super Genre> subscriber, Bundle args) {
        subscriber.onError(new UnsupportedOperationException());
    }

    protected void getGenreAlbums(String library, String identity, Subscriber<? super Album> subscriber,
            Bundle args) {
        subscriber.onError(new UnsupportedOperationException());
    }

    protected void getGenreTracks(String library, String identity, Subscriber<? super Track> subscriber,
            Bundle args) {
        subscriber.onError(new UnsupportedOperationException());
    }

    protected void queryPlaylists(String library, Subscriber<? super Playlist> subscriber, Bundle args) {
        subscriber.onError(new UnsupportedOperationException());
    }

    protected void getPlaylist(String library, String identity, Subscriber<? super Playlist> subscriber,
            Bundle args) {
        subscriber.onError(new UnsupportedOperationException());
    }

    protected void getPlaylistTracks(String library, String identity, Subscriber<? super Track> subscriber,
            Bundle args) {
        subscriber.onError(new UnsupportedOperationException());
    }

    protected void queryTracks(String library, Subscriber<? super Track> subscriber, Bundle args) {
        subscriber.onError(new UnsupportedOperationException());
    }

    protected void getTrack(String library, String identity, Subscriber<? super Track> subscriber, Bundle args) {
        subscriber.onError(new UnsupportedOperationException());
    }

    /*
     * End query stubs
     */

    /*
     * Start abstract methods, we are 100% out-of-band and do not support any of these
     */

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        throw new UnsupportedOperationException();
    }

    @Override
    public String getType(Uri uri) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        throw new UnsupportedOperationException();
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        throw new UnsupportedOperationException();
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        throw new UnsupportedOperationException();
    }

    /*
     * End abstract methods
     */

    //Blocking observable always throws RuntimeExceptions
    private static Exception unwrapE(Exception e) {
        if (e instanceof RuntimeException) {
            Throwable c = e.getCause();
            if (c instanceof Exception) {
                return (Exception) c;
            }
        }
        return e;
    }

    private void writeCause(Bundle ok, LibraryException cause) {
        //HAX since the bundle is returned (i guess)
        //the system classloader remarshals the bundle before we
        //can set our classloader...causing ClassNotFoundException.
        //To remedy nest the cause in another bundle.
        Bundle b = new Bundle();
        b.putParcelable(Extras.CAUSE, cause);
        ok.putBundle(Extras.CAUSE, b);
    }

    private Method _getIBinder = null;

    private IBinder getBinderCallbackFromBundle(Bundle b) {
        if (Build.VERSION.SDK_INT >= 18) {
            return b.getBinder(Extras.CALLBACK);
        } else {
            try {
                synchronized (this) {
                    if (_getIBinder == null) {
                        _getIBinder = Bundle.class.getDeclaredMethod("getIBinder", String.class);
                    }
                }
                return (IBinder) _getIBinder.invoke(b, Extras.CALLBACK);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}