/* * Copyright (C) 2012 Andrew Neal * Copyright (C) 2014 The CyanogenMod Project * Copyright (C) 2015 Naman Dwivedi * * 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 com.naman14.timber; import android.app.Activity; import android.content.ComponentName; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.ServiceConnection; import android.database.Cursor; import android.net.Uri; import android.os.IBinder; import android.os.RemoteException; import android.provider.BaseColumns; import android.provider.MediaStore; import android.widget.Toast; import com.naman14.timber.dataloaders.SongLoader; import com.naman14.timber.helpers.MusicPlaybackTrack; import com.naman14.timber.utils.TimberUtils.IdType; import java.util.Arrays; import java.util.WeakHashMap; public class MusicPlayer { private static final WeakHashMap<Context, ServiceBinder> mConnectionMap; private static final long[] sEmptyList; public static ITimberService mService = null; private static ContentValues[] mContentValuesCache = null; static { mConnectionMap = new WeakHashMap<Context, ServiceBinder>(); sEmptyList = new long[0]; } public static final ServiceToken bindToService(final Context context, final ServiceConnection callback) { Activity realActivity = ((Activity) context).getParent(); if (realActivity == null) { realActivity = (Activity) context; } final ContextWrapper contextWrapper = new ContextWrapper(realActivity); contextWrapper.startService(new Intent(contextWrapper, MusicService.class)); final ServiceBinder binder = new ServiceBinder(callback, contextWrapper.getApplicationContext()); if (contextWrapper.bindService( new Intent().setClass(contextWrapper, MusicService.class), binder, 0)) { mConnectionMap.put(contextWrapper, binder); return new ServiceToken(contextWrapper); } return null; } public static void unbindFromService(final ServiceToken token) { if (token == null) { return; } final ContextWrapper mContextWrapper = token.mWrappedContext; final ServiceBinder mBinder = mConnectionMap.remove(mContextWrapper); if (mBinder == null) { return; } mContextWrapper.unbindService(mBinder); if (mConnectionMap.isEmpty()) { mService = null; } } public static final boolean isPlaybackServiceConnected() { return mService != null; } public static void next() { try { if (mService != null) { mService.next(); } } catch (final RemoteException ignored) { } } public static void initPlaybackServiceWithSettings(final Context context) { setShowAlbumArtOnLockscreen(true); } public static void setShowAlbumArtOnLockscreen(final boolean enabled) { try { if (mService != null) { mService.setLockscreenAlbumArt(enabled); } } catch (final RemoteException ignored) { } } public static void asyncNext(final Context context) { final Intent previous = new Intent(context, MusicService.class); previous.setAction(MusicService.NEXT_ACTION); context.startService(previous); } public static void previous(final Context context, final boolean force) { final Intent previous = new Intent(context, MusicService.class); if (force) { previous.setAction(MusicService.PREVIOUS_FORCE_ACTION); } else { previous.setAction(MusicService.PREVIOUS_ACTION); } context.startService(previous); } public static void playOrPause() { try { if (mService != null) { if (mService.isPlaying()) { mService.pause(); } else { mService.play(); } } } catch (final Exception ignored) { } } public static void cycleRepeat() { try { if (mService != null) { switch (mService.getRepeatMode()) { case MusicService.REPEAT_NONE: mService.setRepeatMode(MusicService.REPEAT_ALL); break; case MusicService.REPEAT_ALL: mService.setRepeatMode(MusicService.REPEAT_CURRENT); if (mService.getShuffleMode() != MusicService.SHUFFLE_NONE) { mService.setShuffleMode(MusicService.SHUFFLE_NONE); } break; default: mService.setRepeatMode(MusicService.REPEAT_NONE); break; } } } catch (final RemoteException ignored) { } } public static void cycleShuffle() { try { if (mService != null) { switch (mService.getShuffleMode()) { case MusicService.SHUFFLE_NONE: mService.setShuffleMode(MusicService.SHUFFLE_NORMAL); if (mService.getRepeatMode() == MusicService.REPEAT_CURRENT) { mService.setRepeatMode(MusicService.REPEAT_ALL); } break; case MusicService.SHUFFLE_NORMAL: mService.setShuffleMode(MusicService.SHUFFLE_NONE); break; case MusicService.SHUFFLE_AUTO: mService.setShuffleMode(MusicService.SHUFFLE_NONE); break; default: break; } } } catch (final RemoteException ignored) { } } public static final boolean isPlaying() { if (mService != null) { try { return mService.isPlaying(); } catch (final RemoteException ignored) { } } return false; } public static final int getShuffleMode() { if (mService != null) { try { return mService.getShuffleMode(); } catch (final RemoteException ignored) { } } return 0; } public static void setShuffleMode(int mode) { try { if (mService != null) { mService.setShuffleMode(mode); } } catch (RemoteException ignored) { } } public static final int getRepeatMode() { if (mService != null) { try { return mService.getRepeatMode(); } catch (final RemoteException ignored) { } } return 0; } public static final String getTrackName() { if (mService != null) { try { return mService.getTrackName(); } catch (final RemoteException ignored) { } } return null; } public static final String getArtistName() { if (mService != null) { try { return mService.getArtistName(); } catch (final RemoteException ignored) { } } return null; } public static final String getAlbumName() { if (mService != null) { try { return mService.getAlbumName(); } catch (final RemoteException ignored) { } } return null; } public static final long getCurrentAlbumId() { if (mService != null) { try { return mService.getAlbumId(); } catch (final RemoteException ignored) { } } return -1; } public static final long getCurrentAudioId() { if (mService != null) { try { return mService.getAudioId(); } catch (final RemoteException ignored) { } } return -1; } public static final MusicPlaybackTrack getCurrentTrack() { if (mService != null) { try { return mService.getCurrentTrack(); } catch (final RemoteException ignored) { } } return null; } public static final MusicPlaybackTrack getTrack(int index) { if (mService != null) { try { return mService.getTrack(index); } catch (final RemoteException ignored) { } } return null; } public static final long getNextAudioId() { if (mService != null) { try { return mService.getNextAudioId(); } catch (final RemoteException ignored) { } } return -1; } public static final long getPreviousAudioId() { if (mService != null) { try { return mService.getPreviousAudioId(); } catch (final RemoteException ignored) { } } return -1; } public static final long getCurrentArtistId() { if (mService != null) { try { return mService.getArtistId(); } catch (final RemoteException ignored) { } } return -1; } public static final int getAudioSessionId() { if (mService != null) { try { return mService.getAudioSessionId(); } catch (final RemoteException ignored) { } } return -1; } public static final long[] getQueue() { try { if (mService != null) { return mService.getQueue(); } else { } } catch (final RemoteException ignored) { } return sEmptyList; } public static final long getQueueItemAtPosition(int position) { try { if (mService != null) { return mService.getQueueItemAtPosition(position); } else { } } catch (final RemoteException ignored) { } return -1; } public static final int getQueueSize() { try { if (mService != null) { return mService.getQueueSize(); } else { } } catch (final RemoteException ignored) { } return 0; } public static final int getQueuePosition() { try { if (mService != null) { return mService.getQueuePosition(); } } catch (final RemoteException ignored) { } return 0; } public static void setQueuePosition(final int position) { if (mService != null) { try { mService.setQueuePosition(position); } catch (final RemoteException ignored) { } } } public static void refresh() { try { if (mService != null) { mService.refresh(); } } catch (final RemoteException ignored) { } } public static final int getQueueHistorySize() { if (mService != null) { try { return mService.getQueueHistorySize(); } catch (final RemoteException ignored) { } } return 0; } public static final int getQueueHistoryPosition(int position) { if (mService != null) { try { return mService.getQueueHistoryPosition(position); } catch (final RemoteException ignored) { } } return -1; } public static final int[] getQueueHistoryList() { if (mService != null) { try { return mService.getQueueHistoryList(); } catch (final RemoteException ignored) { } } return null; } public static final int removeTrack(final long id) { try { if (mService != null) { return mService.removeTrack(id); } } catch (final RemoteException ingored) { } return 0; } public static final boolean removeTrackAtPosition(final long id, final int position) { try { if (mService != null) { return mService.removeTrackAtPosition(id, position); } } catch (final RemoteException ingored) { } return false; } public static void moveQueueItem(final int from, final int to) { try { if (mService != null) { mService.moveQueueItem(from, to); } else { } } catch (final RemoteException ignored) { } } public static void playArtist(final Context context, final long artistId, int position, boolean shuffle) { final long[] artistList = getSongListForArtist(context, artistId); if (artistList != null) { playAll(context, artistList, position, artistId, IdType.Artist, shuffle); } } public static void playAlbum(final Context context, final long albumId, int position, boolean shuffle) { final long[] albumList = getSongListForAlbum(context, albumId); if (albumList != null) { playAll(context, albumList, position, albumId, IdType.Album, shuffle); } } public static void playAll(final Context context, final long[] list, int position, final long sourceId, final IdType sourceType, final boolean forceShuffle) { if (list == null || list.length == 0 || mService == null) { return; } try { if (forceShuffle) { mService.setShuffleMode(MusicService.SHUFFLE_NORMAL); } final long currentId = mService.getAudioId(); final int currentQueuePosition = getQueuePosition(); if (position != -1 && currentQueuePosition == position && currentId == list[position]) { final long[] playlist = getQueue(); if (Arrays.equals(list, playlist)) { mService.play(); return; } } if (position < 0) { position = 0; } mService.open(list, forceShuffle ? -1 : position, sourceId, sourceType.mId); mService.play(); } catch (final RemoteException ignored) { } catch (IllegalStateException e) { e.printStackTrace(); } } public static void playNext(Context context, final long[] list, final long sourceId, final IdType sourceType) { if (mService == null) { return; } try { mService.enqueue(list, MusicService.NEXT, sourceId, sourceType.mId); final String message = makeLabel(context, R.plurals.NNNtrackstoqueue, list.length); Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); } catch (final RemoteException ignored) { } } public static void shuffleAll(final Context context) { Cursor cursor = SongLoader.makeSongCursor(context, null, null); final long[] mTrackList = SongLoader.getSongListForCursor(cursor); final int position = 0; if (mTrackList.length == 0 || mService == null) { return; } try { mService.setShuffleMode(MusicService.SHUFFLE_NORMAL); final long mCurrentId = mService.getAudioId(); final int mCurrentQueuePosition = getQueuePosition(); if (position != -1 && mCurrentQueuePosition == position && mCurrentId == mTrackList[position]) { final long[] mPlaylist = getQueue(); if (Arrays.equals(mTrackList, mPlaylist)) { mService.play(); return; } } mService.open(mTrackList, -1, -1, IdType.NA.mId); mService.play(); cursor.close(); cursor = null; } catch (final RemoteException ignored) { } } public static final long[] getSongListForArtist(final Context context, final long id) { final String[] projection = new String[]{ BaseColumns._ID }; final String selection = MediaStore.Audio.AudioColumns.ARTIST_ID + "=" + id + " AND " + MediaStore.Audio.AudioColumns.IS_MUSIC + "=1"; Cursor cursor = context.getContentResolver().query( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection, null, MediaStore.Audio.AudioColumns.ALBUM_KEY + "," + MediaStore.Audio.AudioColumns.TRACK); if (cursor != null) { final long[] mList = SongLoader.getSongListForCursor(cursor); cursor.close(); cursor = null; return mList; } return sEmptyList; } public static final long[] getSongListForAlbum(final Context context, final long id) { final String[] projection = new String[]{ BaseColumns._ID }; final String selection = MediaStore.Audio.AudioColumns.ALBUM_ID + "=" + id + " AND " + MediaStore.Audio.AudioColumns.IS_MUSIC + "=1"; Cursor cursor = context.getContentResolver().query( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection, null, MediaStore.Audio.AudioColumns.TRACK + ", " + MediaStore.Audio.Media.DEFAULT_SORT_ORDER); if (cursor != null) { final long[] mList = SongLoader.getSongListForCursor(cursor); cursor.close(); cursor = null; return mList; } return sEmptyList; } public static final int getSongCountForAlbumInt(final Context context, final long id) { int songCount = 0; if (id == -1) { return songCount; } Uri uri = ContentUris.withAppendedId(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, id); Cursor cursor = context.getContentResolver().query(uri, new String[]{MediaStore.Audio.AlbumColumns.NUMBER_OF_SONGS}, null, null, null); if (cursor != null) { cursor.moveToFirst(); if (!cursor.isAfterLast()) { if (!cursor.isNull(0)) { songCount = cursor.getInt(0); } } cursor.close(); cursor = null; } return songCount; } public static final String getReleaseDateForAlbum(final Context context, final long id) { if (id == -1) { return null; } Uri uri = ContentUris.withAppendedId(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, id); Cursor cursor = context.getContentResolver().query(uri, new String[]{ MediaStore.Audio.AlbumColumns.FIRST_YEAR }, null, null, null); String releaseDate = null; if (cursor != null) { cursor.moveToFirst(); if (!cursor.isAfterLast()) { releaseDate = cursor.getString(0); } cursor.close(); cursor = null; } return releaseDate; } public static void seek(final long position) { if (mService != null) { try { mService.seek(position); } catch (final RemoteException ignored) { } } } public static void seekRelative(final long deltaInMs) { if (mService != null) { try { mService.seekRelative(deltaInMs); } catch (final RemoteException ignored) { } catch (final IllegalStateException ignored) { } } } public static final long position() { if (mService != null) { try { return mService.position(); } catch (final RemoteException ignored) { } catch (final IllegalStateException ex) { } } return 0; } public static final long duration() { if (mService != null) { try { return mService.duration(); } catch (final RemoteException ignored) { } catch (final IllegalStateException ignored) { } } return 0; } public static void clearQueue() { if (mService!=null) { try { mService.removeTracks(0, Integer.MAX_VALUE); } catch (final RemoteException ignored) { } } } public static void addToQueue(final Context context, final long[] list, long sourceId, IdType sourceType) { if (mService == null) { return; } try { mService.enqueue(list, MusicService.LAST, sourceId, sourceType.mId); final String message = makeLabel(context, R.plurals.NNNtrackstoqueue, list.length); Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); } catch (final RemoteException ignored) { } } public static final String makeLabel(final Context context, final int pluralInt, final int number) { return context.getResources().getQuantityString(pluralInt, number, number); } public static void addToPlaylist(final Context context, final long[] ids, final long playlistid) { final int size = ids.length; final ContentResolver resolver = context.getContentResolver(); final String[] projection = new String[]{ "max(" + "play_order" + ")", }; final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistid); Cursor cursor = null; int base = 0; try { cursor = resolver.query(uri, projection, null, null, null); if (cursor != null && cursor.moveToFirst()) { base = cursor.getInt(0) + 1; } } finally { if (cursor != null) { cursor.close(); cursor = null; } } int numinserted = 0; for (int offSet = 0; offSet < size; offSet += 1000) { makeInsertItems(ids, offSet, 1000, base); numinserted += resolver.bulkInsert(uri, mContentValuesCache); } final String message = context.getResources().getQuantityString( R.plurals.NNNtrackstoplaylist, numinserted, numinserted); Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); } public static void makeInsertItems(final long[] ids, final int offset, int len, final int base) { if (offset + len > ids.length) { len = ids.length - offset; } if (mContentValuesCache == null || mContentValuesCache.length != len) { mContentValuesCache = new ContentValues[len]; } for (int i = 0; i < len; i++) { if (mContentValuesCache[i] == null) { mContentValuesCache[i] = new ContentValues(); } mContentValuesCache[i].put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, base + offset + i); mContentValuesCache[i].put(MediaStore.Audio.Playlists.Members.AUDIO_ID, ids[offset + i]); } } public static final long createPlaylist(final Context context, final String name) { if (name != null && name.length() > 0) { final ContentResolver resolver = context.getContentResolver(); final String[] projection = new String[]{ MediaStore.Audio.PlaylistsColumns.NAME }; final String selection = MediaStore.Audio.PlaylistsColumns.NAME + " = '" + name + "'"; Cursor cursor = resolver.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, projection, selection, null, null); if (cursor.getCount() <= 0) { final ContentValues values = new ContentValues(1); values.put(MediaStore.Audio.PlaylistsColumns.NAME, name); final Uri uri = resolver.insert(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, values); return Long.parseLong(uri.getLastPathSegment()); } if (cursor != null) { cursor.close(); cursor = null; } return -1; } return -1; } public static final void openFile(final String path) { if (mService != null) { try { mService.openFile(path); } catch (final RemoteException ignored) { } } } public static final class ServiceBinder implements ServiceConnection { private final ServiceConnection mCallback; private final Context mContext; public ServiceBinder(final ServiceConnection callback, final Context context) { mCallback = callback; mContext = context; } @Override public void onServiceConnected(final ComponentName className, final IBinder service) { mService = ITimberService.Stub.asInterface(service); if (mCallback != null) { mCallback.onServiceConnected(className, service); } initPlaybackServiceWithSettings(mContext); } @Override public void onServiceDisconnected(final ComponentName className) { if (mCallback != null) { mCallback.onServiceDisconnected(className); } mService = null; } } public static final class ServiceToken { public ContextWrapper mWrappedContext; public ServiceToken(final ContextWrapper context) { mWrappedContext = context; } } }