@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();
        }
      }
    }
 @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();
      }
    }
 /**
  * Download a JSON file from a server, parse the content and return the JSON object.
  *
  * @return result JSONObject containing the parsed representation.
  */
 private JSONObject fetchJSONFromUrl(String urlString) {
   InputStream is = null;
   try {
     URL url = new URL(urlString);
     URLConnection urlConnection = url.openConnection();
     is = new BufferedInputStream(urlConnection.getInputStream());
     BufferedReader reader =
         new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "iso-8859-1"));
     StringBuilder sb = new StringBuilder();
     String line;
     while ((line = reader.readLine()) != null) {
       sb.append(line);
     }
     return new JSONObject(sb.toString());
   } catch (Exception e) {
     LogHelper.e(TAG, "Failed to parse the json for media list", e);
     return null;
   } finally {
     if (is != null) {
       try {
         is.close();
       } catch (IOException e) {
         // ignore
       }
     }
   }
 }
 @Override
 public void onCustomAction(String action, Bundle extras) {
   if (CUSTOM_ACTION_THUMBS_UP.equals(action)) {
     LogHelper.i(TAG, "onCustomAction: favorite for current track");
     MediaMetadata track = getCurrentPlayingMusic();
     if (track != null) {
       String musicId = track.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
       mMusicProvider.setFavorite(musicId, !mMusicProvider.isFavorite(musicId));
     }
     // playback state needs to be updated because the "Favorite" icon on the
     // custom action will change to reflect the new favorite state.
     updatePlaybackState(null);
   } else {
     LogHelper.e(TAG, "Unsupported action: ", action);
   }
 }
    @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));
              }
            }
          });
    }
  /**
   * 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();
    }
  }
  private synchronized void retrieveMedia() {
    try {
      if (mCurrentState == State.NON_INITIALIZED) {
        mCurrentState = State.INITIALIZING;

        int slashPos = CATALOG_URL.lastIndexOf('/');
        String path = CATALOG_URL.substring(0, slashPos + 1);
        JSONObject jsonObj = fetchJSONFromUrl(CATALOG_URL);
        if (jsonObj == null) {
          return;
        }
        JSONArray tracks = jsonObj.getJSONArray(JSON_MUSIC);
        if (tracks != null) {
          for (int j = 0; j < tracks.length(); j++) {
            MediaMetadata item = buildFromJSON(tracks.getJSONObject(j), path);
            String musicId = item.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
            mMusicListById.put(musicId, new MutableMediaMetadata(musicId, item));
          }
          buildListsByGenre();
        }
        mCurrentState = State.INITIALIZED;
      }
    } catch (JSONException e) {
      LogHelper.e(TAG, e, "Could not retrieve music list");
    } finally {
      if (mCurrentState != State.INITIALIZED) {
        // Something bad happened, so we reset state to NON_INITIALIZED to allow
        // retries (eg if the network connection is temporary unavailable)
        mCurrentState = State.NON_INITIALIZED;
      }
    }
  }
 /** 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);
 }
  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);
                  }
                }
              }
            });
      }
    }
  }
 @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);
   }
 }
  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);
  }
 @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();
   }
 }
 @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");
   }
 }
  /**
   * 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 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;
   }
 }
 @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);
 }
  /** 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;
  }
  /** 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));
    }
  }
  /**
   * (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();
  }
 @Override
 public BrowserRoot onGetRoot(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 ads, music library or anything else that
     // needs to run differently when connected to the car, this is where you should handle
     // it.
   }
   return new BrowserRoot(MEDIA_ID_ROOT, null);
 }
 /**
  * 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();
  }