1792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project/*
2792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project * Copyright (C) 2007 The Android Open Source Project
3792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project *
4792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License");
5792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project * you may not use this file except in compliance with the License.
6792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project * You may obtain a copy of the License at
7792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project *
8792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project *      http://www.apache.org/licenses/LICENSE-2.0
9792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project *
10792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project * Unless required by applicable law or agreed to in writing, software
11792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS,
12792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project * See the License for the specific language governing permissions and
14792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project * limitations under the License.
15792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project */
16792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
17792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Projectpackage com.android.music;
18792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
19792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Projectimport android.app.PendingIntent;
20792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Projectimport android.content.Context;
21792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Projectimport android.content.Intent;
228717d3490e179e9591eac98a91a14995d98291adMarco Nelissenimport android.graphics.Bitmap;
23f02d3c605f5d1d091ca7e27170010ad23cd22134Jack Heimport android.media.MediaDescription;
24f02d3c605f5d1d091ca7e27170010ad23cd22134Jack Heimport android.media.MediaMetadata;
25f02d3c605f5d1d091ca7e27170010ad23cd22134Jack Heimport android.media.browse.MediaBrowser.MediaItem;
26f02d3c605f5d1d091ca7e27170010ad23cd22134Jack Heimport android.media.session.MediaSession;
27f02d3c605f5d1d091ca7e27170010ad23cd22134Jack Heimport android.media.session.PlaybackState;
28f02d3c605f5d1d091ca7e27170010ad23cd22134Jack Heimport android.os.Bundle;
29792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Projectimport android.os.Handler;
30792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Projectimport android.os.Message;
31792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Projectimport android.os.SystemClock;
32f02d3c605f5d1d091ca7e27170010ad23cd22134Jack Heimport android.service.media.MediaBrowserService;
33f02d3c605f5d1d091ca7e27170010ad23cd22134Jack Heimport android.text.TextUtils;
34792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Projectimport android.util.Log;
35f02d3c605f5d1d091ca7e27170010ad23cd22134Jack Heimport com.android.music.utils.*;
36792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
372b0b91376a7101fafdf8b007ae660d8b2a039530Marco Nelissenimport java.lang.ref.WeakReference;
38f02d3c605f5d1d091ca7e27170010ad23cd22134Jack Heimport java.util.*;
39f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
40f02d3c605f5d1d091ca7e27170010ad23cd22134Jack Heimport static com.android.music.utils.MediaIDHelper.*;
41792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
42792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project/**
43792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project * Provides "background" audio playback capabilities, allowing the
44792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project * user to switch between activities without stopping playback.
45792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project */
46f02d3c605f5d1d091ca7e27170010ad23cd22134Jack Hepublic class MediaPlaybackService extends MediaBrowserService implements Playback.Callback {
47f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private static final String TAG = LogHelper.makeLogTag(MediaPlaybackService.class);
48f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
49f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    // Delay stopSelf by using a handler.
50f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private static final int STOP_DELAY = 30000;
51f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
52f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    public static final String ACTION_CMD = "com.android.music.ACTION_CMD";
53f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    public static final String CMD_NAME = "CMD_NAME";
54f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    public static final String CMD_PAUSE = "CMD_PAUSE";
55f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    public static final String CMD_REPEAT = "CMD_PAUSE";
56f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    public static final String REPEAT_MODE = "REPEAT_MODE";
57f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
58f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    public enum RepeatMode { REPEAT_NONE, REPEAT_ALL, REPEAT_CURRENT }
59f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
60f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    // Music catalog manager
61f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private MusicProvider mMusicProvider;
62f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private MediaSession mSession;
63f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    // "Now playing" queue:
64f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private List<MediaSession.QueueItem> mPlayingQueue = null;
65f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private int mCurrentIndexOnQueue = -1;
66f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private MediaNotificationManager mMediaNotificationManager;
67f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    // Indicates whether the service was started.
68f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private boolean mServiceStarted;
69f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private DelayedStopHandler mDelayedStopHandler = new DelayedStopHandler(this);
70f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private Playback mPlayback;
71f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    // Default mode is repeat none
72f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private RepeatMode mRepeatMode = RepeatMode.REPEAT_NONE;
73f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    // Extra information for this session
74f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private Bundle mExtras;
75085cbfd3da2c00f5eddf74f344802d16df151988Jean-Michel Trivi
76b0fba8b212a649f41c7cad4c9e7c33e94ca29bb0Jack He    public MediaPlaybackService() {}
77792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
78792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project    @Override
79792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project    public void onCreate() {
80f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        LogHelper.d(TAG, "onCreate()");
81792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project        super.onCreate();
82f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        LogHelper.d(TAG, "Create MusicProvider");
83f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mPlayingQueue = new ArrayList<>();
84f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mMusicProvider = new MusicProvider(this);
85f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
86f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        LogHelper.d(TAG, "Create MediaSession");
87f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        // Start a new MediaSession
88f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mSession = new MediaSession(this, "MediaPlaybackService");
89f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        // Set extra information
90f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mExtras = new Bundle();
91f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mExtras.putInt(REPEAT_MODE, mRepeatMode.ordinal());
92f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mSession.setExtras(mExtras);
93f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        // Enable callbacks from MediaButtons and TransportControls
94f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
95f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
96f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        // Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player
97f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        PlaybackState.Builder stateBuilder = new PlaybackState.Builder().setActions(
98f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PLAY_PAUSE);
99f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mSession.setPlaybackState(stateBuilder.build());
100f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        // MediaSessionCallback() has methods that handle callbacks from a media controller
101f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mSession.setCallback(new MediaSessionCallback());
102f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        // Set the session's token so that client activities can communicate with it.
103f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        setSessionToken(mSession.getSessionToken());
104f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
105f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mPlayback = new Playback(this, mMusicProvider);
106f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mPlayback.setState(PlaybackState.STATE_NONE);
107f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mPlayback.setCallback(this);
108f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mPlayback.start();
109f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
110f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        Context context = getApplicationContext();
111f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        Intent intent = new Intent(context, MusicBrowserActivity.class);
112f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        PendingIntent pi = PendingIntent.getActivity(
113f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                context, 99 /*request code*/, intent, PendingIntent.FLAG_UPDATE_CURRENT);
114f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mSession.setSessionActivity(pi);
115f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
116f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        updatePlaybackState(null);
117f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
118f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mMediaNotificationManager = new MediaNotificationManager(this);
119792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project    }
120792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
121792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project    @Override
122f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    public int onStartCommand(Intent startIntent, int flags, int startId) {
123f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        if (startIntent != null) {
124f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            String action = startIntent.getAction();
125f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            String command = startIntent.getStringExtra(CMD_NAME);
126f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            if (ACTION_CMD.equals(action)) {
127f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                if (CMD_PAUSE.equals(command)) {
128f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    if (mPlayback != null && mPlayback.isPlaying()) {
129f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        handlePauseRequest();
130f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    }
131f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                }
132f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            }
133792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project        }
134f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        return START_STICKY;
135f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    }
1362b0b91376a7101fafdf8b007ae660d8b2a039530Marco Nelissen
137f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    @Override
138f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    public void onDestroy() {
139f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        Log.d(TAG, "onDestroy");
140f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        // Service is being killed, so make sure we release our resources
141f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        handleStopRequest(null);
142792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
143f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mDelayedStopHandler.removeCallbacksAndMessages(null);
144f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        // Always release the MediaSession to clean up resources
145f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        // and notify associated MediaController(s).
146f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mSession.release();
147792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project    }
148b0fba8b212a649f41c7cad4c9e7c33e94ca29bb0Jack He
149f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    @Override
150f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
151f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        Log.d(TAG,
152f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                "OnGetRoot: clientPackageName=" + clientPackageName + "; clientUid=" + clientUid
153f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        + " ; rootHints=" + rootHints);
154f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        // Allow everyone to browse
155f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        return new BrowserRoot(MEDIA_ID_ROOT, null);
156f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    }
157792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
158f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    @Override
159f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) {
160f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        Log.d(TAG, "OnLoadChildren: parentMediaId=" + parentMediaId);
161f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        //  Browsing not allowed
162f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        if (parentMediaId == null) {
163f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            result.sendResult(null);
164792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project            return;
165792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project        }
166f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        if (!mMusicProvider.isInitialized()) {
167f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            // Use result.detach to allow calling result.sendResult from another thread:
168f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            result.detach();
1698d08ec235831d71fdd7f7b6f7757c2bc19528faeMarco Nelissen
170f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            mMusicProvider.retrieveMediaAsync(new MusicProvider.MusicProviderCallback() {
171f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                @Override
172f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                public void onMusicCatalogReady(boolean success) {
173f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    Log.d(TAG, "Received catalog result, success:  " + String.valueOf(success));
174f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    if (success) {
175f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        onLoadChildren(parentMediaId, result);
17663dbbbb163b537f8447f5abdb44fe2966a525464Marco Nelissen                    } else {
177f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        result.sendResult(Collections.emptyList());
17863dbbbb163b537f8447f5abdb44fe2966a525464Marco Nelissen                    }
17963dbbbb163b537f8447f5abdb44fe2966a525464Marco Nelissen                }
180f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            });
181b0fba8b212a649f41c7cad4c9e7c33e94ca29bb0Jack He
182f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        } else {
183f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            // If our music catalog is already loaded/cached, load them into result immediately
184f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            List<MediaItem> mediaItems = new ArrayList<>();
185f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
186f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            switch (parentMediaId) {
187f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                case MEDIA_ID_ROOT:
188f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    Log.d(TAG, "OnLoadChildren.ROOT");
189f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    mediaItems.add(new MediaItem(new MediaDescription.Builder()
190f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                                         .setMediaId(MEDIA_ID_MUSICS_BY_ARTIST)
191f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                                         .setTitle("Artists")
192f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                                         .build(),
193f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                            MediaItem.FLAG_BROWSABLE));
194f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    mediaItems.add(new MediaItem(new MediaDescription.Builder()
195f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                                         .setMediaId(MEDIA_ID_MUSICS_BY_ALBUM)
196f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                                         .setTitle("Albums")
197f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                                         .build(),
198f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                            MediaItem.FLAG_BROWSABLE));
199f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    mediaItems.add(new MediaItem(new MediaDescription.Builder()
200f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                                         .setMediaId(MEDIA_ID_MUSICS_BY_SONG)
201f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                                         .setTitle("Songs")
202f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                                         .build(),
203f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                            MediaItem.FLAG_BROWSABLE));
204f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    mediaItems.add(new MediaItem(new MediaDescription.Builder()
205f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                                         .setMediaId(MEDIA_ID_MUSICS_BY_PLAYLIST)
206f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                                         .setTitle("Playlists")
207f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                                         .build(),
208f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                            MediaItem.FLAG_BROWSABLE));
209f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    break;
210f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                case MEDIA_ID_MUSICS_BY_ARTIST:
211f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    Log.d(TAG, "OnLoadChildren.ARTIST");
212f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    for (String artist : mMusicProvider.getArtists()) {
213f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        MediaItem item = new MediaItem(
214f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                new MediaDescription.Builder()
215f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                        .setMediaId(MediaIDHelper.createBrowseCategoryMediaID(
216f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                                MEDIA_ID_MUSICS_BY_ARTIST, artist))
217f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                        .setTitle(artist)
218f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                        .build(),
219f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                MediaItem.FLAG_BROWSABLE);
220f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        mediaItems.add(item);
221f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    }
222f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    break;
223f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                case MEDIA_ID_MUSICS_BY_PLAYLIST:
224f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    LogHelper.d(TAG, "OnLoadChildren.PLAYLIST");
225f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    for (String playlist : mMusicProvider.getPlaylists()) {
226f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        MediaItem item = new MediaItem(
227f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                new MediaDescription.Builder()
228f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                        .setMediaId(MediaIDHelper.createBrowseCategoryMediaID(
229f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                                MEDIA_ID_MUSICS_BY_PLAYLIST, playlist))
230f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                        .setTitle(playlist)
231f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                        .build(),
232f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                MediaItem.FLAG_BROWSABLE);
233f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        mediaItems.add(item);
234f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    }
235f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    break;
236f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                case MEDIA_ID_MUSICS_BY_ALBUM:
237f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    Log.d(TAG, "OnLoadChildren.ALBUM");
238f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    loadAlbum(mMusicProvider.getAlbums(), mediaItems);
239f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    break;
240f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                case MEDIA_ID_MUSICS_BY_SONG:
241f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    Log.d(TAG, "OnLoadChildren.SONG");
242f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    String hierarchyAwareMediaID = MediaIDHelper.createBrowseCategoryMediaID(
243f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                            parentMediaId, MEDIA_ID_MUSICS_BY_SONG);
244f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    loadSong(mMusicProvider.getMusicList(), mediaItems, hierarchyAwareMediaID);
245f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    break;
246f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                default:
247f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    if (parentMediaId.startsWith(MEDIA_ID_MUSICS_BY_ARTIST)) {
248f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        String artist = MediaIDHelper.getHierarchy(parentMediaId)[1];
249f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        Log.d(TAG, "OnLoadChildren.SONGS_BY_ARTIST  artist=" + artist);
250f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        loadAlbum(mMusicProvider.getAlbumByArtist(artist), mediaItems);
251f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    } else if (parentMediaId.startsWith(MEDIA_ID_MUSICS_BY_ALBUM)) {
252f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        String album = MediaIDHelper.getHierarchy(parentMediaId)[1];
253f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        Log.d(TAG, "OnLoadChildren.SONGS_BY_ALBUM  album=" + album);
254f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        loadSong(mMusicProvider.getMusicsByAlbum(album), mediaItems, parentMediaId);
255f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    } else if (parentMediaId.startsWith(MEDIA_ID_MUSICS_BY_PLAYLIST)) {
256f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        String playlist = MediaIDHelper.getHierarchy(parentMediaId)[1];
257f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        LogHelper.d(TAG, "OnLoadChildren.SONGS_BY_PLAYLIST playlist=", playlist);
258f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        if (playlist.equals(MEDIA_ID_NOW_PLAYING) && mPlayingQueue != null
259f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                && mPlayingQueue.size() > 0) {
260f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                            loadPlayingQueue(mediaItems, parentMediaId);
261f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        } else {
262f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                            loadSong(mMusicProvider.getMusicsByPlaylist(playlist), mediaItems,
263f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                    parentMediaId);
264f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        }
265bf0ea148eefcffd43e6f5023a6f8365d74fb829fMarco Nelissen                    } else {
266f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        Log.w(TAG, "Skipping unmatched parentMediaId: " + parentMediaId);
267bf0ea148eefcffd43e6f5023a6f8365d74fb829fMarco Nelissen                    }
268f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    break;
269792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project            }
270f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            Log.d(TAG,
271f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    "OnLoadChildren sending " + mediaItems.size() + " results for "
272f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                            + parentMediaId);
273f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            result.sendResult(mediaItems);
274f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        }
275f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    }
276f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
277f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private void loadPlayingQueue(List<MediaItem> mediaItems, String parentId) {
278f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        for (MediaSession.QueueItem queueItem : mPlayingQueue) {
279f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            MediaItem mediaItem =
280f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    new MediaItem(queueItem.getDescription(), MediaItem.FLAG_PLAYABLE);
281f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            mediaItems.add(mediaItem);
282f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        }
283f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    }
284f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
285f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private void loadSong(
286f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            Iterable<MediaMetadata> songList, List<MediaItem> mediaItems, String parentId) {
287f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        for (MediaMetadata metadata : songList) {
288f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            String hierarchyAwareMediaID =
289f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    MediaIDHelper.createMediaID(metadata.getDescription().getMediaId(), parentId);
290f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            Bundle songExtra = new Bundle();
291f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            songExtra.putLong(MediaMetadata.METADATA_KEY_DURATION,
292f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    metadata.getLong(MediaMetadata.METADATA_KEY_DURATION));
293f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            String title = metadata.getString(MediaMetadata.METADATA_KEY_TITLE);
294f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            String artistName = metadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
295f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            MediaItem item = new MediaItem(new MediaDescription.Builder()
296f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                                   .setMediaId(hierarchyAwareMediaID)
297f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                                   .setTitle(title)
298f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                                   .setSubtitle(artistName)
299f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                                   .setExtras(songExtra)
300f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                                   .build(),
301f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    MediaItem.FLAG_PLAYABLE);
302f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            mediaItems.add(item);
303f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        }
304f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    }
305f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
306f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private void loadAlbum(Iterable<MediaMetadata> albumList, List<MediaItem> mediaItems) {
307f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        for (MediaMetadata albumMetadata : albumList) {
308f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            String albumName = albumMetadata.getString(MediaMetadata.METADATA_KEY_ALBUM);
309f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            String artistName = albumMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
310f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            Bundle albumExtra = new Bundle();
311f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            albumExtra.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS,
312f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    albumMetadata.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
313f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            MediaItem item = new MediaItem(
314f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    new MediaDescription.Builder()
315f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                            .setMediaId(MediaIDHelper.createBrowseCategoryMediaID(
316f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                    MEDIA_ID_MUSICS_BY_ALBUM, albumName))
317f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                            .setTitle(albumName)
318f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                            .setSubtitle(artistName)
319f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                            .setIconBitmap(
320f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                    albumMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART))
321f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                            .setExtras(albumExtra)
322f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                            .build(),
323f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    MediaItem.FLAG_BROWSABLE);
324f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            mediaItems.add(item);
325f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        }
326f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    }
327f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
328f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private final class MediaSessionCallback extends MediaSession.Callback {
329f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        @Override
330f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        public void onPlay() {
331f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            Log.d(TAG, "play");
332792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
333f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            if (mPlayingQueue == null || mPlayingQueue.isEmpty()) {
334f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                mPlayingQueue = QueueHelper.getRandomQueue(mMusicProvider);
335f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                mSession.setQueue(mPlayingQueue);
336f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                mSession.setQueueTitle(getString(R.string.random_queue_title));
337f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                // start playing from the beginning of the queue
338f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                mCurrentIndexOnQueue = 0;
339792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project            }
340792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
341f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
342f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                handlePlayRequest();
343792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project            }
344f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        }
345b0fba8b212a649f41c7cad4c9e7c33e94ca29bb0Jack He
346f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        @Override
347f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        public void onSkipToQueueItem(long queueId) {
348f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            LogHelper.d(TAG, "OnSkipToQueueItem:", queueId);
349792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
350f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
351f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                // set the current index on queue from the music Id:
352f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                mCurrentIndexOnQueue = QueueHelper.getMusicIndexOnQueue(mPlayingQueue, queueId);
353f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                // play the music
354f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                handlePlayRequest();
35563dbbbb163b537f8447f5abdb44fe2966a525464Marco Nelissen            }
356792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project        }
357c1017e5fe69da3a4ffd2bf7d0890a1a216265a61Marco Nelissen
358f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        @Override
359f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        public void onSeekTo(long position) {
360f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            Log.d(TAG, "onSeekTo:" + position);
361f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            mPlayback.seekTo((int) position);
362f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        }
363c1017e5fe69da3a4ffd2bf7d0890a1a216265a61Marco Nelissen
364f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        @Override
365f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        public void onPlayFromMediaId(String mediaId, Bundle extras) {
366f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            LogHelper.d(TAG, "playFromMediaId mediaId:", mediaId, "  extras=", extras);
367f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
368f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            // The mediaId used here is not the unique musicId. This one comes from the
369f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            // MediaBrowser, and is actually a "hierarchy-aware mediaID": a concatenation of
370f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            // the hierarchy in MediaBrowser and the actual unique musicID. This is necessary
371f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            // so we can build the correct playing queue, based on where the track was
372f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            // selected from.
373f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            mPlayingQueue = QueueHelper.getPlayingQueue(mediaId, mMusicProvider);
374f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            mSession.setQueue(mPlayingQueue);
375f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            String queueTitle = getString(R.string.browse_musics_by_genre_subtitle,
376f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    MediaIDHelper.extractBrowseCategoryValueFromMediaID(mediaId));
377f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            mSession.setQueueTitle(queueTitle);
378f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
379f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
380f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                // set the current index on queue from the media Id:
381f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                mCurrentIndexOnQueue = QueueHelper.getMusicIndexOnQueue(mPlayingQueue, mediaId);
382f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
383f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                if (mCurrentIndexOnQueue < 0) {
384f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    LogHelper.e(TAG, "playFromMediaId: media ID ", mediaId,
385f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                            " could not be found on queue. Ignoring.");
386c1017e5fe69da3a4ffd2bf7d0890a1a216265a61Marco Nelissen                } else {
387f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    // play the music
388f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    handlePlayRequest();
389c1017e5fe69da3a4ffd2bf7d0890a1a216265a61Marco Nelissen                }
390792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project            }
391792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project        }
392b0fba8b212a649f41c7cad4c9e7c33e94ca29bb0Jack He
393f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        @Override
394f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        public void onPause() {
395f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            LogHelper.d(TAG, "pause. current state=" + mPlayback.getState());
396f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            handlePauseRequest();
397792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project        }
398b0fba8b212a649f41c7cad4c9e7c33e94ca29bb0Jack He
399f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        @Override
400f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        public void onStop() {
401f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            LogHelper.d(TAG, "stop. current state=" + mPlayback.getState());
402f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            handleStopRequest(null);
403792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project        }
404b0fba8b212a649f41c7cad4c9e7c33e94ca29bb0Jack He
405792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project        @Override
406f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        public void onSkipToNext() {
407f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            LogHelper.d(TAG, "skipToNext");
408f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            mCurrentIndexOnQueue++;
409f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            if (mPlayingQueue != null && mCurrentIndexOnQueue >= mPlayingQueue.size()) {
410f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                // This sample's behavior: skipping to next when in last song returns to the
411f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                // first song.
412f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                mCurrentIndexOnQueue = 0;
413f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            }
414f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
415f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                handlePlayRequest();
416f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            } else {
417f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                LogHelper.e(TAG,
418f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        "skipToNext: cannot skip to next. next Index=" + mCurrentIndexOnQueue
419f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                + " queue length="
420f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                + (mPlayingQueue == null ? "null" : mPlayingQueue.size()));
421f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                handleStopRequest("Cannot skip");
422792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project            }
423792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project        }
424792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
425f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        @Override
426f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        public void onSkipToPrevious() {
427f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            LogHelper.d(TAG, "skipToPrevious");
428f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            mCurrentIndexOnQueue--;
429f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            if (mPlayingQueue != null && mCurrentIndexOnQueue < 0) {
430f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                // This sample's behavior: skipping to previous when in first song restarts the
431f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                // first song.
432f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                mCurrentIndexOnQueue = 0;
433f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            }
434f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
435f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                handlePlayRequest();
436f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            } else {
437f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                LogHelper.e(TAG,
438f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        "skipToPrevious: cannot skip to previous. previous Index="
439f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                + mCurrentIndexOnQueue + " queue length="
440f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                + (mPlayingQueue == null ? "null" : mPlayingQueue.size()));
441f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                handleStopRequest("Cannot skip");
442f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            }
443792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project        }
444792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
445f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        @Override
446f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        public void onPlayFromSearch(String query, Bundle extras) {
447f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            LogHelper.d(TAG, "playFromSearch  query=", query);
448f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
449f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            if (TextUtils.isEmpty(query)) {
450f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                // A generic search like "Play music" sends an empty query
451f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                // and it's expected that we start playing something. What will be played depends
452f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                // on the app: favorite playlist, "I'm feeling lucky", most recent, etc.
453f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                mPlayingQueue = QueueHelper.getRandomQueue(mMusicProvider);
454f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            } else {
455f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                mPlayingQueue = QueueHelper.getPlayingQueueFromSearch(query, mMusicProvider);
4568ef367969a0fd1f678b1f119b67d0ee409ea0389Mike Lockwood            }
4578717d3490e179e9591eac98a91a14995d98291adMarco Nelissen
458f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            LogHelper.d(TAG, "playFromSearch  playqueue.length=" + mPlayingQueue.size());
459f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            mSession.setQueue(mPlayingQueue);
460b0fba8b212a649f41c7cad4c9e7c33e94ca29bb0Jack He
461f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
462f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                // immediately start playing from the beginning of the search results
463f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                mCurrentIndexOnQueue = 0;
464792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
465f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                handlePlayRequest();
466f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            } else {
467f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                // if nothing was found, we need to warn the user and stop playing
468f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                handleStopRequest(getString(R.string.no_search_results));
469792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project            }
470792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project        }
471b0fba8b212a649f41c7cad4c9e7c33e94ca29bb0Jack He
472f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        @Override
473f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        public void onCustomAction(String action, Bundle extras) {
474f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            LogHelper.d(TAG, "onCustomAction action=", action, ", extras=", extras);
475f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            switch (action) {
476f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                case CMD_REPEAT:
477f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    mRepeatMode = RepeatMode.values()[extras.getInt(REPEAT_MODE)];
478f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    mExtras.putInt(REPEAT_MODE, mRepeatMode.ordinal());
479f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    mSession.setExtras(mExtras);
480f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    LogHelper.d(TAG, "modified repeatMode=", mRepeatMode);
481f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    break;
482f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                default:
483f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    LogHelper.d(TAG, "Unkown action=", action);
484f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    break;
485f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            }
4863aa9ad029bc85da2e07bba019cec928ad5d74043Marco Nelissen        }
487792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project    }
488b0fba8b212a649f41c7cad4c9e7c33e94ca29bb0Jack He
489792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project    /**
490f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He     * Handle a request to play music
491792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project     */
492f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private void handlePlayRequest() {
493f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        LogHelper.d(TAG, "handlePlayRequest: mState=" + mPlayback.getState());
494f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
495f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mDelayedStopHandler.removeCallbacksAndMessages(null);
496f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        if (!mServiceStarted) {
497f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            LogHelper.v(TAG, "Starting service");
498f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            // The MusicService needs to keep running even after the calling MediaBrowser
499f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            // is disconnected. Call startService(Intent) and then stopSelf(..) when we no longer
500f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            // need to play media.
501f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            startService(new Intent(getApplicationContext(), MediaPlaybackService.class));
502f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            mServiceStarted = true;
503792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project        }
504792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
505f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        if (!mSession.isActive()) {
506f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            mSession.setActive(true);
507f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        }
508792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
509f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
510f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            updateMetadata();
511f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            mPlayback.play(mPlayingQueue.get(mCurrentIndexOnQueue));
512792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project        }
513792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project    }
514b0fba8b212a649f41c7cad4c9e7c33e94ca29bb0Jack He
515792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project    /**
516f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He     * Handle a request to pause music
517792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project     */
518f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private void handlePauseRequest() {
519f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        LogHelper.d(TAG, "handlePauseRequest: mState=" + mPlayback.getState());
520f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mPlayback.pause();
521f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        // reset the delayed stop handler.
522f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mDelayedStopHandler.removeCallbacksAndMessages(null);
523f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mDelayedStopHandler.sendEmptyMessageDelayed(0, STOP_DELAY);
524792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project    }
525792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
526792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project    /**
527f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He     * Handle a request to stop music
528792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project     */
529f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private void handleStopRequest(String withError) {
530f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        LogHelper.d(
531f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                TAG, "handleStopRequest: mState=" + mPlayback.getState() + " error=", withError);
532f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mPlayback.stop(true);
533f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        // reset the delayed stop handler.
534f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mDelayedStopHandler.removeCallbacksAndMessages(null);
535f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mDelayedStopHandler.sendEmptyMessageDelayed(0, STOP_DELAY);
536792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
537f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        updatePlaybackState(withError);
538e41bd18d3351b494306b7d7f2e1f562fa3fa8f8cMarco Nelissen
539f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        // service is no longer necessary. Will be started again if needed.
540f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        stopSelf();
541f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mServiceStarted = false;
542e41bd18d3351b494306b7d7f2e1f562fa3fa8f8cMarco Nelissen    }
543e41bd18d3351b494306b7d7f2e1f562fa3fa8f8cMarco Nelissen
544f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private void updateMetadata() {
545f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        if (!QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
546f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            LogHelper.e(TAG, "Can't retrieve current metadata.");
547f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            updatePlaybackState(getResources().getString(R.string.error_no_metadata));
548f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            return;
549f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        }
550f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        MediaSession.QueueItem queueItem = mPlayingQueue.get(mCurrentIndexOnQueue);
551f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        String musicId =
552f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                MediaIDHelper.extractMusicIDFromMediaID(queueItem.getDescription().getMediaId());
553f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        MediaMetadata track = mMusicProvider.getMusicByMediaId(musicId).getMetadata();
554f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        final String trackId = track.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
555f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        if (!musicId.equals(trackId)) {
556f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            IllegalStateException e = new IllegalStateException("track ID should match musicId.");
557f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            LogHelper.e(TAG, "track ID should match musicId.", " musicId=", musicId,
558f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    " trackId=", trackId,
559f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    " mediaId from queueItem=", queueItem.getDescription().getMediaId(),
560f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    " title from queueItem=", queueItem.getDescription().getTitle(),
561f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    " mediaId from track=", track.getDescription().getMediaId(),
562f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    " title from track=", track.getDescription().getTitle(),
563f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    " source.hashcode from track=",
564f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    track.getString(MusicProvider.CUSTOM_METADATA_TRACK_SOURCE).hashCode(), e);
565f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            throw e;
566f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        }
567f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        LogHelper.d(TAG, "Updating metadata for MusicID= " + musicId);
568f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mSession.setMetadata(track);
569f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
570f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        // Set the proper album artwork on the media session, so it can be shown in the
571f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        // locked screen and in other places.
572f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        if (track.getDescription().getIconBitmap() == null
573f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                && track.getDescription().getIconUri() != null) {
574f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            String albumUri = track.getDescription().getIconUri().toString();
575f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            AlbumArtCache.getInstance().fetch(albumUri, new AlbumArtCache.FetchListener() {
576f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                @Override
577f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                public void onFetched(String artUrl, Bitmap bitmap, Bitmap icon) {
578f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    MediaSession.QueueItem queueItem = mPlayingQueue.get(mCurrentIndexOnQueue);
579f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    MediaMetadata track = mMusicProvider.getMusicByMediaId(trackId).getMetadata();
580f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    track = new MediaMetadata
581f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                    .Builder(track)
582f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
583f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                    // set high resolution bitmap in METADATA_KEY_ALBUM_ART. This is
584f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                    // used, for
585f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                    // example, on the lockscreen background when the media session
586f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                    // is active.
587f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                    .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, bitmap)
588f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
589f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                    // set small version of the album art in the DISPLAY_ICON. This
590f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                    // is used on
591f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                    // the MediaDescription and thus it should be small to be
592f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                    // serialized if
593f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                    // necessary..
594f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                    .putBitmap(MediaMetadata.METADATA_KEY_DISPLAY_ICON, icon)
595f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
596f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                    .build();
597f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
598f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    mMusicProvider.updateMusic(trackId, track);
599f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He
600f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    // If we are still playing the same music
601f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    String currentPlayingId = MediaIDHelper.extractMusicIDFromMediaID(
602f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                            queueItem.getDescription().getMediaId());
603f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    if (trackId.equals(currentPlayingId)) {
604f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        mSession.setMetadata(track);
605c37b200d0198deaf488d0bfe1b114d1fd9037382Marco Nelissen                    }
606e41bd18d3351b494306b7d7f2e1f562fa3fa8f8cMarco Nelissen                }
607f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            });
608e41bd18d3351b494306b7d7f2e1f562fa3fa8f8cMarco Nelissen        }
609e41bd18d3351b494306b7d7f2e1f562fa3fa8f8cMarco Nelissen    }
610e41bd18d3351b494306b7d7f2e1f562fa3fa8f8cMarco Nelissen
611792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project    /**
612f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He     * Update the current media player state, optionally showing an error message.
613792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project     *
614f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He     * @param error if not null, error message to present to the user.
615792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project     */
616f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private void updatePlaybackState(String error) {
617f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        LogHelper.d(TAG, "updatePlaybackState, playback state=" + mPlayback.getState());
618f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        long position = PlaybackState.PLAYBACK_POSITION_UNKNOWN;
619f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        if (mPlayback != null && mPlayback.isConnected()) {
620f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            position = mPlayback.getCurrentStreamPosition();
621792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project        }
622792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
623f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        PlaybackState.Builder stateBuilder =
624f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                new PlaybackState.Builder().setActions(getAvailableActions());
625272eb78d8836f969f7480454167bdffc6a6f6dd7Thomas Tuttle
626f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        int state = mPlayback.getState();
627792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
628f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        // If there is an error message, send it to the playback state:
629f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        if (error != null) {
630f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            // Error states are really only supposed to be used for errors that cause playback to
631f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            // stop unexpectedly and persist until the user takes action to fix it.
632f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            stateBuilder.setErrorMessage(error);
633f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            state = PlaybackState.STATE_ERROR;
634f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        }
635f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        stateBuilder.setState(state, position, 1.0f, SystemClock.elapsedRealtime());
636347fe57e4c8a860dfbad6f0ec1991171bfa69d77Mike Cleron
637f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        // Set the activeQueueItemId if the current index is valid.
638f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
639f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            MediaSession.QueueItem item = mPlayingQueue.get(mCurrentIndexOnQueue);
640f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            stateBuilder.setActiveQueueItemId(item.getQueueId());
641792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project        }
642e41bd18d3351b494306b7d7f2e1f562fa3fa8f8cMarco Nelissen
643f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        mSession.setPlaybackState(stateBuilder.build());
644e41bd18d3351b494306b7d7f2e1f562fa3fa8f8cMarco Nelissen
645f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        if (state == PlaybackState.STATE_PLAYING || state == PlaybackState.STATE_PAUSED) {
646f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            mMediaNotificationManager.startNotification();
647e41bd18d3351b494306b7d7f2e1f562fa3fa8f8cMarco Nelissen        }
648e41bd18d3351b494306b7d7f2e1f562fa3fa8f8cMarco Nelissen    }
649e41bd18d3351b494306b7d7f2e1f562fa3fa8f8cMarco Nelissen
650f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private long getAvailableActions() {
651f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        long actions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PLAY_FROM_MEDIA_ID
652f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                | PlaybackState.ACTION_PLAY_FROM_SEARCH;
653f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        if (mPlayingQueue == null || mPlayingQueue.isEmpty()) {
654f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            return actions;
655792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project        }
656f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        if (mPlayback.isPlaying()) {
657f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            actions |= PlaybackState.ACTION_PAUSE;
658792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project        }
659f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        if (mCurrentIndexOnQueue > 0) {
660f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            actions |= PlaybackState.ACTION_SKIP_TO_PREVIOUS;
661792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project        }
662f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        if (mCurrentIndexOnQueue < mPlayingQueue.size() - 1) {
663f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            actions |= PlaybackState.ACTION_SKIP_TO_NEXT;
664792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project        }
665f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        return actions;
666792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project    }
667792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
668f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private MediaMetadata getCurrentPlayingMusic() {
669f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
670f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            MediaSession.QueueItem item = mPlayingQueue.get(mCurrentIndexOnQueue);
671f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            if (item != null) {
672f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                LogHelper.d(TAG,
673f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        "getCurrentPlayingMusic for musicId=", item.getDescription().getMediaId());
674f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                return mMusicProvider
675f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        .getMusicByMediaId(MediaIDHelper.extractMusicIDFromMediaID(
676f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                                item.getDescription().getMediaId()))
677f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        .getMetadata();
678f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            }
679f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        }
680f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        return null;
681792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project    }
682792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
683792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project    /**
684f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He     * Implementation of the Playback.Callback interface
685792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project     */
686f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    @Override
687f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    public void onCompletion() {
688f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        // The media player finished playing the current song, so we go ahead
689f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        // and start the next.
690f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
691f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            switch (mRepeatMode) {
692f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                case REPEAT_ALL:
693f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    // Increase the index
694f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    mCurrentIndexOnQueue++;
695f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    // Restart queue when reaching the end
696f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    if (mCurrentIndexOnQueue >= mPlayingQueue.size()) {
697f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        mCurrentIndexOnQueue = 0;
698f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    }
699f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    break;
700f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                case REPEAT_CURRENT:
701f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    // Do not change the index
702f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    break;
703f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                case REPEAT_NONE:
704f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                default:
705f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    // Increase the index
706f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    mCurrentIndexOnQueue++;
707f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    // Stop the queue when reaching the end
708f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    if (mCurrentIndexOnQueue >= mPlayingQueue.size()) {
709f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        handleStopRequest(null);
710f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                        return;
711f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    }
712f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    break;
713407cf91b661ceeb3ced16dc20af8243b9a0eb632Marco Nelissen            }
714f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            handlePlayRequest();
715f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        } else {
716f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            // If there is nothing to play, we stop and release the resources:
717f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            handleStopRequest(null);
718792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project        }
719792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project    }
720792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
721f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    @Override
722f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    public void onPlaybackStatusChanged(int state) {
723f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        updatePlaybackState(null);
724792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project    }
725792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project
726f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    @Override
727f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    public void onError(String error) {
728f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        updatePlaybackState(error);
729f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    }
730b0fba8b212a649f41c7cad4c9e7c33e94ca29bb0Jack He
731792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project    /**
732f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He     * A simple handler that stops the service if playback is not active (playing)
733792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project     */
734f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He    private static class DelayedStopHandler extends Handler {
735f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        private final WeakReference<MediaPlaybackService> mWeakReference;
736b0fba8b212a649f41c7cad4c9e7c33e94ca29bb0Jack He
737f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        private DelayedStopHandler(MediaPlaybackService service) {
738f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            mWeakReference = new WeakReference<>(service);
739792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project        }
740b0fba8b212a649f41c7cad4c9e7c33e94ca29bb0Jack He
741f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        @Override
742f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He        public void handleMessage(Message msg) {
743f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            MediaPlaybackService service = mWeakReference.get();
744f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He            if (service != null && service.mPlayback != null) {
745f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                if (service.mPlayback.isPlaying()) {
746f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                    Log.d(TAG, "Ignoring delayed stop since the media player is in use.");
747792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project                    return;
748792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project                }
749f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                Log.d(TAG, "Stopping service with delay handler.");
750f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                service.stopSelf();
751f02d3c605f5d1d091ca7e27170010ad23cd22134Jack He                service.mServiceStarted = false;
752308670290db0a553de41a21f1c07ca7d53b3fbf6Marco Nelissen            }
753308670290db0a553de41a21f1c07ca7d53b3fbf6Marco Nelissen        }
7542b0b91376a7101fafdf8b007ae660d8b2a039530Marco Nelissen    }
755792a2206a4f05f6bd13fce902d3663892d2947afThe Android Open Source Project}
756