@Override
  public void onLoadChildren(
      @NonNull final String parentMediaId, @NonNull final Result<List<MediaItem>> result) {
    if (!mMusicProvider.isInitialized()) {
      // Use result.detach to allow calling result.sendResult from another thread:
      result.detach();

      mMusicProvider.retrieveMediaAsync(
          new MusicProvider.Callback() {
            @Override
            public void onMusicCatalogReady(boolean success) {
              if (success) {
                loadChildrenImpl(parentMediaId, result);
              } else {
                updatePlaybackState(getString(R.string.error_no_metadata));
                result.sendResult(Collections.<MediaItem>emptyList());
              }
            }
          });

    } else {
      // If our music catalog is already loaded/cached, load them into result immediately
      loadChildrenImpl(parentMediaId, result);
    }
  }
  public static List<MediaSession.QueueItem> getPlayingQueue(
      String mediaId, MusicProvider musicProvider) {

    // extract the browsing hierarchy from the media ID:
    String[] hierarchy = MediaIDHelper.getHierarchy(mediaId);

    if (hierarchy.length != 2) {
      LogHelper.e(TAG, "Could not build a playing queue for this mediaId: ", mediaId);
      return null;
    }

    String categoryType = hierarchy[0];
    String categoryValue = hierarchy[1];
    LogHelper.d(TAG, "Creating playing queue for ", categoryType, ",  ", categoryValue);

    Iterable<MediaMetadata> tracks = null;
    // This sample only supports genre and by_search category types.
    if (categoryType.equals(MEDIA_ID_MUSICS_BY_GENRE)) {
      tracks = musicProvider.getMusicsByGenre(categoryValue);
    } else if (categoryType.equals(MEDIA_ID_MUSICS_BY_SEARCH)) {
      tracks = musicProvider.searchMusicBySongTitle(categoryValue);
    }

    if (tracks == null) {
      LogHelper.e(TAG, "Unrecognized category type: ", categoryType, " for media ", mediaId);
      return null;
    }

    return convertToQueue(tracks, hierarchy[0], hierarchy[1]);
  }
  /**
   * 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);
  }
  /**
   * Create a random queue.
   *
   * @param musicProvider the provider used for fetching music.
   * @return list containing {@link MediaSession.QueueItem}'s
   */
  public static List<MediaSession.QueueItem> getRandomQueue(MusicProvider musicProvider) {
    List<MediaMetadata> result = new ArrayList<>();

    for (String genre : musicProvider.getGenres()) {
      Iterable<MediaMetadata> tracks = musicProvider.getMusicsByGenre(genre);
      for (MediaMetadata track : tracks) {
        if (ThreadLocalRandom.current().nextBoolean()) {
          result.add(track);
        }
      }
    }
    LogHelper.d(TAG, "getRandomQueue: result.size=", result.size());

    Collections.shuffle(result);

    return convertToQueue(result, MEDIA_ID_MUSICS_BY_SEARCH, "random");
  }
 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 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;
 }
  public static List<MediaSession.QueueItem> getPlayingQueueFromSearch(
      String query, Bundle queryParams, MusicProvider musicProvider) {

    LogHelper.d(
        TAG, "Creating playing queue for musics from search: ", query, " params=", queryParams);

    VoiceSearchParams params = new VoiceSearchParams(query, queryParams);

    LogHelper.d(TAG, "VoiceSearchParams: ", params);

    if (params.isAny) {
      // If isAny is true, we will play anything. This is app-dependent, and can be,
      // for example, favorite playlists, "I'm feeling lucky", most recent, etc.
      return getRandomQueue(musicProvider);
    }

    Iterable<MediaMetadata> result = null;
    if (params.isAlbumFocus) {
      result = musicProvider.searchMusicByAlbum(params.album);
    } else if (params.isGenreFocus) {
      result = musicProvider.getMusicsByGenre(params.genre);
    } else if (params.isArtistFocus) {
      result = musicProvider.searchMusicByArtist(params.artist);
    } else if (params.isSongFocus) {
      result = musicProvider.searchMusicBySongTitle(params.song);
    }

    // If there was no results using media focus parameter, we do an unstructured query.
    // This is useful when the user is searching for something that looks like an artist
    // to Google, for example, but is not. For example, a user searching for Madonna on
    // a PodCast application wouldn't get results if we only looked at the
    // Artist (podcast author). Then, we can instead do an unstructured search.
    if (params.isUnstructured || result == null || !result.iterator().hasNext()) {
      // To keep it simple for this example, we do unstructured searches on the
      // song title only. A real world application could search on other fields as well.
      result = musicProvider.searchMusicBySongTitle(query);
    }

    return convertToQueue(result, MEDIA_ID_MUSICS_BY_SEARCH, query);
  }
 private void setCustomAction(PlaybackStateCompat.Builder stateBuilder) {
   MediaMetadataCompat currentMusic = getCurrentPlayingMusic();
   if (currentMusic != null) {
     // Set appropriate "Favorite" icon on Custom action:
     String musicId = currentMusic.getString(MediaMetadataCompat.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));
     Bundle customActionExtras = new Bundle();
     WearHelper.setShowCustomActionOnWear(customActionExtras, true);
     stateBuilder.addCustomAction(
         new PlaybackStateCompat.CustomAction.Builder(
                 CUSTOM_ACTION_THUMBS_UP, getString(R.string.favorite), favoriteIcon)
             .setExtras(customActionExtras)
             .build());
   }
 }
  private void updateMetadata() {
    if (!QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) {
      LogHelper.e(TAG, "Can't retrieve current metadata.");
      updatePlaybackState(getResources().getString(R.string.error_no_metadata));
      return;
    }
    MediaSession.QueueItem queueItem = mPlayingQueue.get(mCurrentIndexOnQueue);
    String musicId =
        MediaIDHelper.extractMusicIDFromMediaID(queueItem.getDescription().getMediaId());
    MediaMetadata track = mMusicProvider.getMusic(musicId);
    final String trackId = track.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
    if (!musicId.equals(trackId)) {
      IllegalStateException e = new IllegalStateException("track ID should match musicId.");
      LogHelper.e(
          TAG,
          "track ID should match musicId.",
          " musicId=",
          musicId,
          " trackId=",
          trackId,
          " mediaId from queueItem=",
          queueItem.getDescription().getMediaId(),
          " title from queueItem=",
          queueItem.getDescription().getTitle(),
          " mediaId from track=",
          track.getDescription().getMediaId(),
          " title from track=",
          track.getDescription().getTitle(),
          " source.hashcode from track=",
          track.getString(MusicProvider.CUSTOM_METADATA_TRACK_SOURCE).hashCode(),
          e);
      throw e;
    }
    LogHelper.d(TAG, "Updating metadata for MusicID= " + musicId);
    mSession.setMetadata(track);

    // Set the proper album artwork on the media session, so it can be shown in the
    // locked screen and in other places.
    if (track.getDescription().getIconBitmap() == null
        && track.getDescription().getIconUri() != null) {
      String albumUri = track.getDescription().getIconUri().toString();
      AlbumArtCache.getInstance()
          .fetch(
              albumUri,
              new AlbumArtCache.FetchListener() {
                @Override
                public void onFetched(String artUrl, Bitmap bitmap, Bitmap icon) {
                  MediaSession.QueueItem queueItem = mPlayingQueue.get(mCurrentIndexOnQueue);
                  MediaMetadata track = mMusicProvider.getMusic(trackId);
                  track =
                      new MediaMetadata.Builder(track)

                          // set high resolution bitmap in METADATA_KEY_ALBUM_ART. This is used, for
                          // example, on the lockscreen background when the media session is active.
                          .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, bitmap)

                          // set small version of the album art in the DISPLAY_ICON. This is used on
                          // the MediaDescription and thus it should be small to be serialized if
                          // necessary..
                          .putBitmap(MediaMetadata.METADATA_KEY_DISPLAY_ICON, icon)
                          .build();

                  MediaDescription md;
                  mMusicProvider.updateMusic(trackId, track);

                  // If we are still playing the same music
                  String currentPlayingId =
                      MediaIDHelper.extractMusicIDFromMediaID(
                          queueItem.getDescription().getMediaId());
                  if (trackId.equals(currentPlayingId)) {
                    mSession.setMetadata(track);
                  }
                }
              });
    }
  }