/**
   * Actual implementation of onLoadChildren that assumes that MusicProvider is already initialized.
   */
  private void loadChildrenImpl(
      final String parentMediaId, final Result<List<MediaBrowser.MediaItem>> result) {
    LogHelper.d(TAG, "OnLoadChildren: parentMediaId=", parentMediaId);

    List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>();

    if (MEDIA_ID_ROOT.equals(parentMediaId)) {
      LogHelper.d(TAG, "OnLoadChildren.ROOT");
      mediaItems.add(
          new MediaBrowser.MediaItem(
              new MediaDescription.Builder()
                  .setMediaId(MEDIA_ID_MUSICS_BY_GENRE)
                  .setTitle(getString(R.string.browse_genres))
                  .setIconUri(
                      Uri.parse(
                          "android.resource://" + "com.example.android.uamp/drawable/ic_by_genre"))
                  .setSubtitle(getString(R.string.browse_genre_subtitle))
                  .build(),
              MediaBrowser.MediaItem.FLAG_BROWSABLE));

    } else if (MEDIA_ID_MUSICS_BY_GENRE.equals(parentMediaId)) {
      LogHelper.d(TAG, "OnLoadChildren.GENRES");
      for (String genre : mMusicProvider.getGenres()) {
        MediaBrowser.MediaItem item =
            new MediaBrowser.MediaItem(
                new MediaDescription.Builder()
                    .setMediaId(createBrowseCategoryMediaID(MEDIA_ID_MUSICS_BY_GENRE, genre))
                    .setTitle(genre)
                    .setSubtitle(getString(R.string.browse_musics_by_genre_subtitle, genre))
                    .build(),
                MediaBrowser.MediaItem.FLAG_BROWSABLE);
        mediaItems.add(item);
      }

    } else if (parentMediaId.startsWith(MEDIA_ID_MUSICS_BY_GENRE)) {
      String genre = MediaIDHelper.getHierarchy(parentMediaId)[1];
      LogHelper.d(TAG, "OnLoadChildren.SONGS_BY_GENRE  genre=", genre);
      for (MediaMetadata track : mMusicProvider.getMusicsByGenre(genre)) {
        // Since mediaMetadata fields are immutable, we need to create a copy, so we
        // can set a hierarchy-aware mediaID. We will need to know the media hierarchy
        // when we get a onPlayFromMusicID call, so we can create the proper queue based
        // on where the music was selected from (by artist, by genre, random, etc)
        String hierarchyAwareMediaID =
            MediaIDHelper.createMediaID(
                track.getDescription().getMediaId(), MEDIA_ID_MUSICS_BY_GENRE, genre);
        MediaMetadata trackCopy =
            new MediaMetadata.Builder(track)
                .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, hierarchyAwareMediaID)
                .build();
        MediaBrowser.MediaItem bItem =
            new MediaBrowser.MediaItem(trackCopy.getDescription(), MediaItem.FLAG_PLAYABLE);
        mediaItems.add(bItem);
      }
    } else {
      LogHelper.w(TAG, "Skipping unmatched parentMediaId: ", parentMediaId);
    }
    LogHelper.d(TAG, "OnLoadChildren sending ", mediaItems.size(), " results for ", parentMediaId);
    result.sendResult(mediaItems);
  }
 @Override
 public void handleMessage(Message msg) {
   MusicService service = mWeakReference.get();
   if (service != null && service.mPlayback != null) {
     if (service.mPlayback.isPlaying()) {
       LogHelper.d(TAG, "Ignoring delayed stop since the media player is in use.");
       return;
     }
     LogHelper.d(TAG, "Stopping service with delay handler.");
     service.stopSelf();
     service.mServiceStarted = false;
   }
 }
    @Override
    public void onPlayFromSearch(String query, Bundle extras) {
      LogHelper.d(TAG, "playFromSearch  query=", query);

      mPlayingQueue = QueueHelper.getPlayingQueueFromSearch(query, mMusicProvider);
      LogHelper.d(TAG, "playFromSearch  playqueue.length=" + mPlayingQueue.size());
      mSession.setQueue(mPlayingQueue);

      if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
        // start playing from the beginning of the queue
        mCurrentIndexOnQueue = 0;

        handlePlayRequest();
      }
    }
    @Override
    public void onPlayFromSearch(final String query, final Bundle extras) {
      LogHelper.d(TAG, "playFromSearch  query=", query, " extras=", extras);

      mPlayback.setState(PlaybackStateCompat.STATE_CONNECTING);

      // Voice searches may occur before the media catalog has been
      // prepared. We only handle the search after the musicProvider is ready.
      mMusicProvider.retrieveMediaAsync(
          new MusicProvider.Callback() {
            @Override
            public void onMusicCatalogReady(boolean success) {
              mPlayingQueue = QueueHelper.getPlayingQueueFromSearch(query, extras, mMusicProvider);

              LogHelper.d(TAG, "playFromSearch  playqueue.length=" + mPlayingQueue.size());
              mSession.setQueue(mPlayingQueue);

              if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
                // immediately start playing from the beginning of the search results
                mCurrentIndexOnQueue = 0;

                handlePlayRequest();
              } else {
                // if nothing was found, we need to warn the user and stop playing
                handleStopRequest(getString(R.string.no_search_results));
              }
            }
          });
    }
 @Override
 public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) {
   LogHelper.d(
       TAG,
       "OnGetRoot: clientPackageName=" + clientPackageName,
       "; clientUid=" + clientUid + " ; rootHints=",
       rootHints);
   // To ensure you are not allowing any arbitrary app to browse your app's contents, you
   // need to check the origin:
   if (!mPackageValidator.isCallerAllowed(this, clientPackageName, clientUid)) {
     // If the request comes from an untrusted package, return null. No further calls will
     // be made to other media browsing methods.
     LogHelper.w(TAG, "OnGetRoot: IGNORING request from untrusted package " + clientPackageName);
     return null;
   }
   //noinspection StatementWithEmptyBody
   if (CarHelper.isValidCarPackage(clientPackageName)) {
     // Optional: if your app needs to adapt the music library to show a different subset
     // when connected to the car, this is where you should handle it.
     // If you want to adapt other runtime behaviors, like tweak ads or change some behavior
     // that should be different on cars, you should instead use the boolean flag
     // set by the BroadcastReceiver mCarConnectionReceiver (mIsConnectedToCar).
   }
   //noinspection StatementWithEmptyBody
   if (WearHelper.isValidWearCompanionPackage(clientPackageName)) {
     // Optional: if your app needs to adapt the music library for when browsing from a
     // Wear device, you should return a different MEDIA ROOT here, and then,
     // on onLoadChildren, handle it accordingly.
   }
   return new BrowserRoot(MEDIA_ID_ROOT, null);
 }
    @Override
    public void onPlayFromMediaId(String mediaId, Bundle extras) {
      LogHelper.d(TAG, "playFromMediaId mediaId:", mediaId, "  extras=", extras);

      // The mediaId used here is not the unique musicId. This one comes from the
      // MediaBrowser, and is actually a "hierarchy-aware mediaID": a concatenation of
      // the hierarchy in MediaBrowser and the actual unique musicID. This is necessary
      // so we can build the correct playing queue, based on where the track was
      // selected from.
      mPlayingQueue = QueueHelper.getPlayingQueue(mediaId, mMusicProvider);
      mSession.setQueue(mPlayingQueue);
      String queueTitle =
          getString(
              R.string.browse_musics_by_genre_subtitle,
              MediaIDHelper.extractBrowseCategoryValueFromMediaID(mediaId));
      mSession.setQueueTitle(queueTitle);

      if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
        // set the current index on queue from the media Id:
        mCurrentIndexOnQueue = QueueHelper.getMusicIndexOnQueue(mPlayingQueue, mediaId);

        if (mCurrentIndexOnQueue < 0) {
          LogHelper.e(
              TAG,
              "playFromMediaId: media ID ",
              mediaId,
              " could not be found on queue. Ignoring.");
        } else {
          // play the music
          handlePlayRequest();
        }
      }
    }
 /** Handle a request to pause music */
 private void handlePauseRequest() {
   LogHelper.d(TAG, "handlePauseRequest: mState=" + mPlayback.getState());
   mPlayback.pause();
   // reset the delayed stop handler.
   mDelayedStopHandler.removeCallbacksAndMessages(null);
   mDelayedStopHandler.sendEmptyMessageDelayed(0, STOP_DELAY);
 }
  /**
   * Update the current media player state, optionally showing an error message.
   *
   * @param error if not null, error message to present to the user.
   */
  private void updatePlaybackState(String error) {
    LogHelper.d(TAG, "updatePlaybackState, playback state=" + mPlayback.getState());
    long position = PlaybackState.PLAYBACK_POSITION_UNKNOWN;
    if (mPlayback != null && mPlayback.isConnected()) {
      position = mPlayback.getCurrentStreamPosition();
    }

    PlaybackState.Builder stateBuilder =
        new PlaybackState.Builder().setActions(getAvailableActions());

    setCustomAction(stateBuilder);
    int state = mPlayback.getState();

    // If there is an error message, send it to the playback state:
    if (error != null) {
      // Error states are really only supposed to be used for errors that cause playback to
      // stop unexpectedly and persist until the user takes action to fix it.
      stateBuilder.setErrorMessage(error);
      state = PlaybackState.STATE_ERROR;
    }
    stateBuilder.setState(state, position, 1.0f, SystemClock.elapsedRealtime());

    // Set the activeQueueItemId if the current index is valid.
    if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
      MediaSession.QueueItem item = mPlayingQueue.get(mCurrentIndexOnQueue);
      stateBuilder.setActiveQueueItemId(item.getQueueId());
    }

    mSession.setPlaybackState(stateBuilder.build());

    if (state == PlaybackState.STATE_PLAYING || state == PlaybackState.STATE_PAUSED) {
      mMediaNotificationManager.startNotification();
    }
  }
 @Override
 public void onDisconnected() {
   LogHelper.d(TAG, "onDisconnected");
   mSessionExtras.remove(EXTRA_CONNECTED_CAST);
   mSession.setExtras(mSessionExtras);
   Playback playback = new LocalPlayback(MusicService.this, mMusicProvider);
   mMediaRouter.setMediaSession(null);
   switchToPlayer(playback, false);
 }
 @Override
 public void onStop() {
   super.onStop();
   LogHelper.d(TAG, "fragment.onStop");
   MediaControllerCompat controller =
       ((FragmentActivity) getActivity()).getSupportMediaController();
   if (controller != null) {
     controller.unregisterCallback(mCallback);
   }
 }
 public void onConnected() {
   MediaControllerCompat controller =
       ((FragmentActivity) getActivity()).getSupportMediaController();
   LogHelper.d(TAG, "onConnected, mediaController==null? ", controller == null);
   if (controller != null) {
     onMetadataChanged(controller.getMetadata());
     onPlaybackStateChanged(controller.getPlaybackState());
     controller.registerCallback(mCallback);
   }
 }
 @Override
 public void onStart() {
   super.onStart();
   LogHelper.d(TAG, "fragment.onStart");
   MediaControllerCompat controller =
       ((FragmentActivity) getActivity()).getSupportMediaController();
   if (controller != null) {
     onConnected();
   }
 }
    @Override
    public void onSkipToQueueItem(long queueId) {
      LogHelper.d(TAG, "OnSkipToQueueItem:" + queueId);

      if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
        // set the current index on queue from the music Id:
        mCurrentIndexOnQueue = QueueHelper.getMusicIndexOnQueue(mPlayingQueue, queueId);
        // play the music
        handlePlayRequest();
      }
    }
 private MediaMetadata getCurrentPlayingMusic() {
   if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
     MediaSession.QueueItem item = mPlayingQueue.get(mCurrentIndexOnQueue);
     if (item != null) {
       LogHelper.d(TAG, "getCurrentPlayingMusic for musicId=", item.getDescription().getMediaId());
       return mMusicProvider.getMusic(
           MediaIDHelper.extractMusicIDFromMediaID(item.getDescription().getMediaId()));
     }
   }
   return null;
 }
 @Override
 public void onMetadataChanged(String mediaId) {
   LogHelper.d(TAG, "onMetadataChanged", mediaId);
   List<MediaSession.QueueItem> queue = QueueHelper.getPlayingQueue(mediaId, mMusicProvider);
   int index = QueueHelper.getMusicIndexOnQueue(queue, mediaId);
   if (index > -1) {
     mCurrentIndexOnQueue = index;
     mPlayingQueue = queue;
     updateMetadata();
   }
 }
  /** Handle a request to stop music */
  private void handleStopRequest(String withError) {
    LogHelper.d(TAG, "handleStopRequest: mState=" + mPlayback.getState() + " error=", withError);
    mPlayback.stop(true);
    // reset the delayed stop handler.
    mDelayedStopHandler.removeCallbacksAndMessages(null);
    mDelayedStopHandler.sendEmptyMessageDelayed(0, STOP_DELAY);

    updatePlaybackState(withError);

    // service is no longer necessary. Will be started again if needed.
    stopSelf();
    mServiceStarted = false;
  }
 @Override
 public void onMetadataChanged(MediaMetadataCompat metadata) {
   if (metadata == null) {
     return;
   }
   LogHelper.d(
       TAG,
       "Received metadata state change to mediaId=",
       metadata.getDescription().getMediaId(),
       " song=",
       metadata.getDescription().getTitle());
   PlaybackControlsFragment.this.onMetadataChanged(metadata);
 }
 @Override
 public void onClick(View v) {
   MediaControllerCompat controller =
       ((FragmentActivity) getActivity()).getSupportMediaController();
   PlaybackStateCompat stateObj = controller.getPlaybackState();
   final int state = stateObj == null ? PlaybackStateCompat.STATE_NONE : stateObj.getState();
   LogHelper.d(TAG, "Button pressed, in state " + state);
   switch (v.getId()) {
     case R.id.play_pause:
       LogHelper.d(TAG, "Play button pressed, in state " + state);
       if (state == PlaybackStateCompat.STATE_PAUSED
           || state == PlaybackStateCompat.STATE_STOPPED
           || state == PlaybackStateCompat.STATE_NONE) {
         playMedia();
       } else if (state == PlaybackStateCompat.STATE_PLAYING
           || state == PlaybackStateCompat.STATE_BUFFERING
           || state == PlaybackStateCompat.STATE_CONNECTING) {
         pauseMedia();
       }
       break;
   }
 }
  /**
   * (non-Javadoc)
   *
   * @see android.app.Service#onDestroy()
   */
  @Override
  public void onDestroy() {
    LogHelper.d(TAG, "onDestroy");
    // Service is being killed, so make sure we release our resources
    handleStopRequest(null);

    mCastManager = ((UAMPApplication) getApplication()).getCastManager(getApplicationContext());
    mCastManager.removeVideoCastConsumer(mCastConsumer);

    mDelayedStopHandler.removeCallbacksAndMessages(null);
    // Always release the MediaSession to clean up resources
    // and notify associated MediaController(s).
    mSession.release();
  }
 /**
  * Helper to switch to a different Playback instance
  *
  * @param playback switch to this playback
  */
 private void switchToPlayer(Playback playback, boolean resumePlaying) {
   if (playback == null) {
     throw new IllegalArgumentException("Playback cannot be null");
   }
   // suspend the current one.
   int oldState = mPlayback.getState();
   int pos = mPlayback.getCurrentStreamPosition();
   String currentMediaId = mPlayback.getCurrentMediaId();
   LogHelper.d(TAG, "Current position from " + playback + " is ", pos);
   mPlayback.stop(false);
   playback.setCallback(this);
   playback.setCurrentStreamPosition(pos < 0 ? 0 : pos);
   playback.setCurrentMediaId(currentMediaId);
   playback.start();
   // finally swap the instance
   mPlayback = playback;
   switch (oldState) {
     case PlaybackState.STATE_BUFFERING:
     case PlaybackState.STATE_CONNECTING:
     case PlaybackState.STATE_PAUSED:
       mPlayback.pause();
       break;
     case PlaybackState.STATE_PLAYING:
       if (resumePlaying && QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
         mPlayback.play(mPlayingQueue.get(mCurrentIndexOnQueue));
       } else if (!resumePlaying) {
         mPlayback.pause();
       } else {
         mPlayback.stop(true);
       }
       break;
     case PlaybackState.STATE_NONE:
       break;
     default:
       LogHelper.d(TAG, "Default called. Old state is ", oldState);
   }
 }
  /**
   * (non-Javadoc)
   *
   * @see android.app.Service#onDestroy()
   */
  @Override
  public void onDestroy() {
    LogHelper.d(TAG, "onDestroy");
    unregisterReceiver(mCarConnectionReceiver);
    // Service is being killed, so make sure we release our resources
    handleStopRequest(null);

    mCastManager = VideoCastManager.getInstance();
    mCastManager.removeVideoCastConsumer(mCastConsumer);

    mDelayedStopHandler.removeCallbacksAndMessages(null);
    // Always release the MediaSessionCompat to clean up resources
    // and notify associated MediaControllerCompat(s).
    mSession.release();
  }
    @Override
    public void onPlay() {
      LogHelper.d(TAG, "play");

      if (mPlayingQueue == null || mPlayingQueue.isEmpty()) {
        mPlayingQueue = QueueHelper.getRandomQueue(mMusicProvider);
        mSession.setQueue(mPlayingQueue);
        mSession.setQueueTitle(getString(R.string.random_queue_title));
        // start playing from the beginning of the queue
        mCurrentIndexOnQueue = 0;
      }

      if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) {
        handlePlayRequest();
      }
    }
  private void onMetadataChanged(MediaMetadataCompat metadata) {
    LogHelper.d(TAG, "onMetadataChanged ", metadata);
    if (getActivity() == null) {
      LogHelper.w(
          TAG,
          "onMetadataChanged called when getActivity null,"
              + "this should not happen if the callback was properly unregistered. Ignoring.");
      return;
    }
    if (metadata == null) {
      return;
    }

    mTitle.setText(metadata.getDescription().getTitle());
    mSubtitle.setText(metadata.getDescription().getSubtitle());
    String artUrl = null;
    if (metadata.getDescription().getIconUri() != null) {
      artUrl = metadata.getDescription().getIconUri().toString();
    }
    if (!TextUtils.equals(artUrl, mArtUrl)) {
      mArtUrl = artUrl;
      Bitmap art = metadata.getDescription().getIconBitmap();
      AlbumArtCache cache = AlbumArtCache.getInstance();
      if (art == null) {
        art = cache.getIconImage(mArtUrl);
      }
      if (art != null) {
        mAlbumArt.setImageBitmap(art);
      } else {
        cache.fetch(
            artUrl,
            new AlbumArtCache.FetchListener() {
              @Override
              public void onFetched(String artUrl, Bitmap bitmap, Bitmap icon) {
                if (icon != null) {
                  LogHelper.d(
                      TAG, "album art icon of w=", icon.getWidth(), " h=", icon.getHeight());
                  if (isAdded()) {
                    mAlbumArt.setImageBitmap(icon);
                  }
                }
              }
            });
      }
    }
  }
 private void setCustomAction(PlaybackState.Builder stateBuilder) {
   MediaMetadata currentMusic = getCurrentPlayingMusic();
   if (currentMusic != null) {
     // Set appropriate "Favorite" icon on Custom action:
     String musicId = currentMusic.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
     int favoriteIcon = R.drawable.ic_star_off;
     if (mMusicProvider.isFavorite(musicId)) {
       favoriteIcon = R.drawable.ic_star_on;
     }
     LogHelper.d(
         TAG,
         "updatePlaybackState, setting Favorite custom action of music ",
         musicId,
         " current favorite=",
         mMusicProvider.isFavorite(musicId));
     stateBuilder.addCustomAction(
         CUSTOM_ACTION_THUMBS_UP, getString(R.string.favorite), favoriteIcon);
   }
 }
  private void onPlaybackStateChanged(PlaybackStateCompat state) {
    LogHelper.d(TAG, "onPlaybackStateChanged ", state);
    if (getActivity() == null) {
      LogHelper.w(
          TAG,
          "onPlaybackStateChanged called when getActivity null,"
              + "this should not happen if the callback was properly unregistered. Ignoring.");
      return;
    }
    if (state == null) {
      return;
    }
    boolean enablePlay = false;
    switch (state.getState()) {
      case PlaybackStateCompat.STATE_PAUSED:
      case PlaybackStateCompat.STATE_STOPPED:
        enablePlay = true;
        break;
      case PlaybackStateCompat.STATE_ERROR:
        LogHelper.e(TAG, "error playbackstate: ", state.getErrorMessage());
        Toast.makeText(getActivity(), state.getErrorMessage(), Toast.LENGTH_LONG).show();
        break;
    }

    if (enablePlay) {
      mPlayPause.setImageDrawable(
          ContextCompat.getDrawable(getActivity(), R.drawable.ic_play_arrow_black_36dp));
    } else {
      mPlayPause.setImageDrawable(
          ContextCompat.getDrawable(getActivity(), R.drawable.ic_pause_black_36dp));
    }

    MediaControllerCompat controller =
        ((FragmentActivity) getActivity()).getSupportMediaController();
    String extraInfo = null;
    if (controller != null && controller.getExtras() != null) {
      String castName = controller.getExtras().getString(MusicService.EXTRA_CONNECTED_CAST);
      if (castName != null) {
        extraInfo = getResources().getString(R.string.casting_to_device, castName);
      }
    }
    setExtraInfo(extraInfo);
  }
  private MediaMetadata buildFromJSON(JSONObject json, String basePath) throws JSONException {
    String title = json.getString(JSON_TITLE);
    String album = json.getString(JSON_ALBUM);
    String artist = json.getString(JSON_ARTIST);
    String genre = json.getString(JSON_GENRE);
    String source = json.getString(JSON_SOURCE);
    String iconUrl = json.getString(JSON_IMAGE);
    int trackNumber = json.getInt(JSON_TRACK_NUMBER);
    int totalTrackCount = json.getInt(JSON_TOTAL_TRACK_COUNT);
    int duration = json.getInt(JSON_DURATION) * 1000; // ms

    LogHelper.d(TAG, "Found music track: ", json);

    // Media is stored relative to JSON file
    if (!source.startsWith("http")) {
      source = basePath + source;
    }
    if (!iconUrl.startsWith("http")) {
      iconUrl = basePath + iconUrl;
    }
    // Since we don't have a unique ID in the server, we fake one using the hashcode of
    // the music source. In a real world app, this could come from the server.
    String id = String.valueOf(source.hashCode());

    // Adding the music source to the MediaMetadata (and consequently using it in the
    // mediaSession.setMetadata) is not a good idea for a real world music app, because
    // the session metadata can be accessed by notification listeners. This is done in this
    // sample for convenience only.
    return new MediaMetadata.Builder()
        .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, id)
        .putString(CUSTOM_METADATA_TRACK_SOURCE, source)
        .putString(MediaMetadata.METADATA_KEY_ALBUM, album)
        .putString(MediaMetadata.METADATA_KEY_ARTIST, artist)
        .putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
        .putString(MediaMetadata.METADATA_KEY_GENRE, genre)
        .putString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI, iconUrl)
        .putString(MediaMetadata.METADATA_KEY_TITLE, title)
        .putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, trackNumber)
        .putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, totalTrackCount)
        .build();
  }
 @Override
 public void onSkipToNext() {
   LogHelper.d(TAG, "skipToNext");
   mCurrentIndexOnQueue++;
   if (mPlayingQueue != null && mCurrentIndexOnQueue >= mPlayingQueue.size()) {
     // This sample's behavior: skipping to next when in last song returns to the
     // first song.
     mCurrentIndexOnQueue = 0;
   }
   if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
     handlePlayRequest();
   } else {
     LogHelper.e(
         TAG,
         "skipToNext: cannot skip to next. next Index="
             + mCurrentIndexOnQueue
             + " queue length="
             + (mPlayingQueue == null ? "null" : mPlayingQueue.size()));
     handleStopRequest("Cannot skip");
   }
 }
 @Override
 public void onSkipToPrevious() {
   LogHelper.d(TAG, "skipToPrevious");
   mCurrentIndexOnQueue--;
   if (mPlayingQueue != null && mCurrentIndexOnQueue < 0) {
     // This sample's behavior: skipping to previous when in first song restarts the
     // first song.
     mCurrentIndexOnQueue = 0;
   }
   if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
     handlePlayRequest();
   } else {
     LogHelper.e(
         TAG,
         "skipToPrevious: cannot skip to previous. previous Index="
             + mCurrentIndexOnQueue
             + " queue length="
             + (mPlayingQueue == null ? "null" : mPlayingQueue.size()));
     handleStopRequest("Cannot skip");
   }
 }
  /*
   * (non-Javadoc)
   * @see android.app.Service#onCreate()
   */
  @Override
  public void onCreate() {
    super.onCreate();
    LogHelper.d(TAG, "onCreate");

    mPlayingQueue = new ArrayList<>();
    mMusicProvider = new MusicProvider();
    mPackageValidator = new PackageValidator(this);

    // Start a new MediaSession
    mSession = new MediaSession(this, "MusicService");
    setSessionToken(mSession.getSessionToken());
    mSession.setCallback(new MediaSessionCallback());
    mSession.setFlags(
        MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);

    mPlayback = new LocalPlayback(this, mMusicProvider);
    mPlayback.setState(PlaybackState.STATE_NONE);
    mPlayback.setCallback(this);
    mPlayback.start();

    Context context = getApplicationContext();
    Intent intent = new Intent(context, NowPlayingActivity.class);
    PendingIntent pi =
        PendingIntent.getActivity(
            context, 99 /*request code*/, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    mSession.setSessionActivity(pi);

    mSessionExtras = new Bundle();
    CarHelper.setSlotReservationFlags(mSessionExtras, true, true, true);
    mSession.setExtras(mSessionExtras);

    updatePlaybackState(null);

    mMediaNotificationManager = new MediaNotificationManager(this);
    mCastManager = ((UAMPApplication) getApplication()).getCastManager(getApplicationContext());

    mCastManager.addVideoCastConsumer(mCastConsumer);
    mMediaRouter = MediaRouter.getInstance(getApplicationContext());
  }
  /** Handle a request to play music */
  private void handlePlayRequest() {
    LogHelper.d(TAG, "handlePlayRequest: mState=" + mPlayback.getState());

    mDelayedStopHandler.removeCallbacksAndMessages(null);
    if (!mServiceStarted) {
      LogHelper.v(TAG, "Starting service");
      // The MusicService needs to keep running even after the calling MediaBrowser
      // is disconnected. Call startService(Intent) and then stopSelf(..) when we no longer
      // need to play media.
      startService(new Intent(getApplicationContext(), MusicService.class));
      mServiceStarted = true;
    }

    if (!mSession.isActive()) {
      mSession.setActive(true);
    }

    if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
      updateMetadata();
      mPlayback.play(mPlayingQueue.get(mCurrentIndexOnQueue));
    }
  }