Пример #1
0
public class CarHelper {
  private static final String TAG = LogHelper.makeLogTag(CarHelper.class);

  private static final String AUTO_APP_PACKAGE_NAME = "com.google.android.projection.gearhead";

  // Use these extras to reserve space for the corresponding actions, even when they are disabled
  // in the playbackstate, so the custom actions don't reflow.
  private static final String SLOT_RESERVATION_SKIP_TO_NEXT =
      "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT";
  private static final String SLOT_RESERVATION_SKIP_TO_PREV =
      "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS";
  private static final String SLOT_RESERVATION_QUEUE =
      "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_QUEUE";

  /**
   * Action for an intent broadcast by Android Auto when a media app is connected or disconnected. A
   * "connected" media app is the one currently attached to the "media" facet on Android Auto. So,
   * this intent is sent by AA on:
   *
   * <p>- connection: when the phone is projecting and at the moment the app is selected from the
   * list of media apps - disconnection: when another media app is selected from the list of media
   * apps or when the phone stops projecting (when the user unplugs it, for example)
   *
   * <p>The actual event (connected or disconnected) will come as an Intent extra, with the key
   * MEDIA_CONNECTION_STATUS (see below).
   */
  public static final String ACTION_MEDIA_STATUS = "com.google.android.gms.car.media.STATUS";

  /**
   * Key in Intent extras that contains the media connection event type (connected or disconnected)
   */
  public static final String MEDIA_CONNECTION_STATUS = "media_connection_status";

  /**
   * Value of the key MEDIA_CONNECTION_STATUS in Intent extras used when the current media app is
   * connected.
   */
  public static final String MEDIA_CONNECTED = "media_connected";

  /**
   * Value of the key MEDIA_CONNECTION_STATUS in Intent extras used when the current media app is
   * disconnected.
   */
  public static final String MEDIA_DISCONNECTED = "media_disconnected";

  public static boolean isValidCarPackage(String packageName) {
    return AUTO_APP_PACKAGE_NAME.equals(packageName);
  }

  public static void setSlotReservationFlags(
      Bundle extras,
      boolean reservePlayingQueueSlot,
      boolean reserveSkipToNextSlot,
      boolean reserveSkipToPrevSlot) {
    if (reservePlayingQueueSlot) {
      extras.putBoolean(SLOT_RESERVATION_QUEUE, true);
    } else {
      extras.remove(SLOT_RESERVATION_QUEUE);
    }
    if (reserveSkipToPrevSlot) {
      extras.putBoolean(SLOT_RESERVATION_SKIP_TO_PREV, true);
    } else {
      extras.remove(SLOT_RESERVATION_SKIP_TO_PREV);
    }
    if (reserveSkipToNextSlot) {
      extras.putBoolean(SLOT_RESERVATION_SKIP_TO_NEXT, true);
    } else {
      extras.remove(SLOT_RESERVATION_SKIP_TO_NEXT);
    }
  }

  /**
   * Returns true when running Android Auto or a car dock.
   *
   * <p>A preferable way of detecting if your app is running in the context of an Android Auto
   * compatible car is by registering a BroadcastReceiver for the action {@link
   * CarHelper#ACTION_MEDIA_STATUS}. See a sample implementation in {@link MusicService#onCreate()}.
   *
   * @param c Context to detect UI Mode.
   * @return true when device is running in car mode, false otherwise.
   */
  public static boolean isCarUiMode(Context c) {
    UiModeManager uiModeManager = (UiModeManager) c.getSystemService(Context.UI_MODE_SERVICE);
    if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) {
      LogHelper.d(TAG, "Running in Car mode");
      return true;
    } else {
      LogHelper.d(TAG, "Running on a non-Car mode");
      return false;
    }
  }
}
/** Utility class to help on queue related tasks. */
public class QueueHelper {

  private static final String TAG = LogHelper.makeLogTag(QueueHelper.class);

  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]);
  }

  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);
  }

  public static int getMusicIndexOnQueue(Iterable<MediaSession.QueueItem> queue, String mediaId) {
    int index = 0;
    for (MediaSession.QueueItem item : queue) {
      if (mediaId.equals(item.getDescription().getMediaId())) {
        return index;
      }
      index++;
    }
    return -1;
  }

  public static int getMusicIndexOnQueue(Iterable<MediaSession.QueueItem> queue, long queueId) {
    int index = 0;
    for (MediaSession.QueueItem item : queue) {
      if (queueId == item.getQueueId()) {
        return index;
      }
      index++;
    }
    return -1;
  }

  private static List<MediaSession.QueueItem> convertToQueue(
      Iterable<MediaMetadata> tracks, String... categories) {
    List<MediaSession.QueueItem> queue = new ArrayList<>();
    int count = 0;
    for (MediaMetadata track : tracks) {

      // We create a hierarchy-aware mediaID, so we know what the queue is about by looking
      // at the QueueItem media IDs.
      String hierarchyAwareMediaID =
          MediaIDHelper.createMediaID(track.getDescription().getMediaId(), categories);

      MediaMetadata trackCopy =
          new MediaMetadata.Builder(track)
              .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, hierarchyAwareMediaID)
              .build();

      // We don't expect queues to change after created, so we use the item index as the
      // queueId. Any other number unique in the queue would work.
      MediaSession.QueueItem item = new MediaSession.QueueItem(trackCopy.getDescription(), count++);
      queue.add(item);
    }
    return queue;
  }

  /**
   * 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");
  }

  public static boolean isIndexPlayable(int index, List<MediaSession.QueueItem> queue) {
    return (queue != null && index >= 0 && index < queue.size());
  }
}