content: /*
   * Copyright (C) 2015 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package android.hardware.radio;
  
  import android.annotation.NonNull;
  import android.annotation.SystemApi;
  import android.graphics.Bitmap;
  import android.graphics.BitmapFactory;
  import android.os.Bundle;
  import android.os.Parcel;
  import android.os.Parcelable;
  import android.util.ArrayMap;
  import android.util.Log;
  import android.util.SparseArray;
  
  import java.util.Set;
  
  /**
   * Contains meta data about a radio program such as station name, song title, artist etc...
   * @hide
   */
  @SystemApi
  public final class RadioMetadata implements Parcelable {
      private static final String TAG = "BroadcastRadio.metadata";
  
      /**
       * The RDS Program Information.
       */
      public static final String METADATA_KEY_RDS_PI = "android.hardware.radio.metadata.RDS_PI";
  
      /**
       * The RDS Program Service.
       */
      public static final String METADATA_KEY_RDS_PS = "android.hardware.radio.metadata.RDS_PS";
  
      /**
       * The RDS PTY.
       */
      public static final String METADATA_KEY_RDS_PTY = "android.hardware.radio.metadata.RDS_PTY";
  
      /**
       * The RBDS PTY.
       */
      public static final String METADATA_KEY_RBDS_PTY = "android.hardware.radio.metadata.RBDS_PTY";
  
      /**
       * The RBDS Radio Text.
       */
      public static final String METADATA_KEY_RDS_RT = "android.hardware.radio.metadata.RDS_RT";
  
      /**
       * The song title.
       */
      public static final String METADATA_KEY_TITLE = "android.hardware.radio.metadata.TITLE";
  
      /**
       * The artist name.
       */
      public static final String METADATA_KEY_ARTIST = "android.hardware.radio.metadata.ARTIST";
  
      /**
       * The album name.
       */
      public static final String METADATA_KEY_ALBUM = "android.hardware.radio.metadata.ALBUM";
  
      /**
       * The music genre.
       */
      public static final String METADATA_KEY_GENRE = "android.hardware.radio.metadata.GENRE";
  
      /**
       * The radio station icon {@link Bitmap}.
       */
      public static final String METADATA_KEY_ICON = "android.hardware.radio.metadata.ICON";
  
      /**
       * The artwork for the song/album {@link Bitmap}.
       */
      public static final String METADATA_KEY_ART = "android.hardware.radio.metadata.ART";
  
      /**
       * The clock.
       */
      public static final String METADATA_KEY_CLOCK = "android.hardware.radio.metadata.CLOCK";
  
      /**
       * Technology-independent program name (station name).
       */
      public static final String METADATA_KEY_PROGRAM_NAME =
              "android.hardware.radio.metadata.PROGRAM_NAME";
  
      /**
       * DAB ensemble name.
       */
      public static final String METADATA_KEY_DAB_ENSEMBLE_NAME =
              "android.hardware.radio.metadata.DAB_ENSEMBLE_NAME";
  
      /**
       * DAB ensemble name - short version (up to 8 characters).
       */
      public static final String METADATA_KEY_DAB_ENSEMBLE_NAME_SHORT =
              "android.hardware.radio.metadata.DAB_ENSEMBLE_NAME_SHORT";
  
      /**
       * DAB service name.
       */
      public static final String METADATA_KEY_DAB_SERVICE_NAME =
              "android.hardware.radio.metadata.DAB_SERVICE_NAME";
  
      /**
       * DAB service name - short version (up to 8 characters).
       */
      public static final String METADATA_KEY_DAB_SERVICE_NAME_SHORT =
              "android.hardware.radio.metadata.DAB_SERVICE_NAME_SHORT";
  
      /**
       * DAB component name.
       */
      public static final String METADATA_KEY_DAB_COMPONENT_NAME =
              "android.hardware.radio.metadata.DAB_COMPONENT_NAME";
  
      /**
       * DAB component name.
       */
      public static final String METADATA_KEY_DAB_COMPONENT_NAME_SHORT =
              "android.hardware.radio.metadata.DAB_COMPONENT_NAME_SHORT";
  
  
      private static final int METADATA_TYPE_INVALID = -1;
      private static final int METADATA_TYPE_INT = 0;
      private static final int METADATA_TYPE_TEXT = 1;
      private static final int METADATA_TYPE_BITMAP = 2;
      private static final int METADATA_TYPE_CLOCK = 3;
  
      private static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
  
      static {
          METADATA_KEYS_TYPE = new ArrayMap<String, Integer>();
          METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PI, METADATA_TYPE_INT);
          METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PS, METADATA_TYPE_TEXT);
          METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PTY, METADATA_TYPE_INT);
          METADATA_KEYS_TYPE.put(METADATA_KEY_RBDS_PTY, METADATA_TYPE_INT);
          METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_RT, METADATA_TYPE_TEXT);
          METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT);
          METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT);
          METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT);
          METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT);
          METADATA_KEYS_TYPE.put(METADATA_KEY_ICON, METADATA_TYPE_BITMAP);
          METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
          METADATA_KEYS_TYPE.put(METADATA_KEY_CLOCK, METADATA_TYPE_CLOCK);
          METADATA_KEYS_TYPE.put(METADATA_KEY_PROGRAM_NAME, METADATA_TYPE_TEXT);
          METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_ENSEMBLE_NAME, METADATA_TYPE_TEXT);
          METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_ENSEMBLE_NAME_SHORT, METADATA_TYPE_TEXT);
          METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_SERVICE_NAME, METADATA_TYPE_TEXT);
          METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_SERVICE_NAME_SHORT, METADATA_TYPE_TEXT);
          METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_COMPONENT_NAME, METADATA_TYPE_TEXT);
          METADATA_KEYS_TYPE.put(METADATA_KEY_DAB_COMPONENT_NAME_SHORT, METADATA_TYPE_TEXT);
      }
  
      // keep in sync with: system/media/radio/include/system/radio_metadata.h
      private static final int NATIVE_KEY_INVALID     = -1;
      private static final int NATIVE_KEY_RDS_PI      = 0;
      private static final int NATIVE_KEY_RDS_PS      = 1;
      private static final int NATIVE_KEY_RDS_PTY     = 2;
      private static final int NATIVE_KEY_RBDS_PTY    = 3;
      private static final int NATIVE_KEY_RDS_RT      = 4;
      private static final int NATIVE_KEY_TITLE       = 5;
      private static final int NATIVE_KEY_ARTIST      = 6;
      private static final int NATIVE_KEY_ALBUM       = 7;
      private static final int NATIVE_KEY_GENRE       = 8;
      private static final int NATIVE_KEY_ICON        = 9;
      private static final int NATIVE_KEY_ART         = 10;
      private static final int NATIVE_KEY_CLOCK       = 11;
  
      private static final SparseArray<String> NATIVE_KEY_MAPPING;
  
      static {
          NATIVE_KEY_MAPPING = new SparseArray<String>();
          NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PI, METADATA_KEY_RDS_PI);
          NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PS, METADATA_KEY_RDS_PS);
          NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PTY, METADATA_KEY_RDS_PTY);
          NATIVE_KEY_MAPPING.put(NATIVE_KEY_RBDS_PTY, METADATA_KEY_RBDS_PTY);
          NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_RT, METADATA_KEY_RDS_RT);
          NATIVE_KEY_MAPPING.put(NATIVE_KEY_TITLE, METADATA_KEY_TITLE);
          NATIVE_KEY_MAPPING.put(NATIVE_KEY_ARTIST, METADATA_KEY_ARTIST);
          NATIVE_KEY_MAPPING.put(NATIVE_KEY_ALBUM, METADATA_KEY_ALBUM);
          NATIVE_KEY_MAPPING.put(NATIVE_KEY_GENRE, METADATA_KEY_GENRE);
          NATIVE_KEY_MAPPING.put(NATIVE_KEY_ICON, METADATA_KEY_ICON);
          NATIVE_KEY_MAPPING.put(NATIVE_KEY_ART, METADATA_KEY_ART);
          NATIVE_KEY_MAPPING.put(NATIVE_KEY_CLOCK, METADATA_KEY_CLOCK);
      }
  
      /**
       * Provides a Clock that can be used to describe time as provided by the Radio.
       *
       * The clock is defined by the seconds since epoch at the UTC + 0 timezone
       * and timezone offset from UTC + 0 represented in number of minutes.
       *
       * @hide
       */
      @SystemApi
      public static final class Clock implements Parcelable {
          private final long mUtcEpochSeconds;
          private final int mTimezoneOffsetMinutes;
  
          public int describeContents() {
              return 0;
          }
  
          public void writeToParcel(Parcel out, int flags) {
              out.writeLong(mUtcEpochSeconds);
              out.writeInt(mTimezoneOffsetMinutes);
          }
  
          public static final @android.annotation.NonNull Parcelable.Creator<Clock> CREATOR
                  = new Parcelable.Creator<Clock>() {
              public Clock createFromParcel(Parcel in) {
                  return new Clock(in);
              }
  
              public Clock[] newArray(int size) {
                  return new Clock[size];
              }
          };
  
          public Clock(long utcEpochSeconds, int timezoneOffsetMinutes) {
              mUtcEpochSeconds = utcEpochSeconds;
              mTimezoneOffsetMinutes = timezoneOffsetMinutes;
          }
  
          private Clock(Parcel in) {
              mUtcEpochSeconds = in.readLong();
              mTimezoneOffsetMinutes = in.readInt();
          }
  
          public long getUtcEpochSeconds() {
              return mUtcEpochSeconds;
          }
  
          public int getTimezoneOffsetMinutes() {
              return mTimezoneOffsetMinutes;
          }
      }
  
      private final Bundle mBundle;
  
      RadioMetadata() {
          mBundle = new Bundle();
      }
  
      private RadioMetadata(Bundle bundle) {
          mBundle = new Bundle(bundle);
      }
  
      private RadioMetadata(Parcel in) {
          mBundle = in.readBundle();
      }
  
      @Override
      public String toString() {
          StringBuilder sb = new StringBuilder("RadioMetadata[");
  
          final String removePrefix = "android.hardware.radio.metadata";
  
          boolean first = true;
          for (String key : mBundle.keySet()) {
              if (first) first = false;
              else sb.append(", ");
  
              String keyDisp = key;
              if (key.startsWith(removePrefix)) keyDisp = key.substring(removePrefix.length());
  
              sb.append(keyDisp);
              sb.append('=');
              sb.append(mBundle.get(key));
          }
  
          sb.append("]");
          return sb.toString();
      }
  
      /**
       * Returns {@code true} if the given key is contained in the meta data
       *
       * @param key a String key
       * @return {@code true} if the key exists in this meta data, {@code false} otherwise
       */
      public boolean containsKey(String key) {
          return mBundle.containsKey(key);
      }
  
      /**
       * Returns the text value associated with the given key as a String, or null
       * if the key is not found in the meta data.
       *
       * @param key The key the value is stored under
       * @return a String value, or null
       */
      public String getString(String key) {
          return mBundle.getString(key);
      }
  
      private static void putInt(Bundle bundle, String key, int value) {
          int type = METADATA_KEYS_TYPE.getOrDefault(key, METADATA_TYPE_INVALID);
          if (type != METADATA_TYPE_INT && type != METADATA_TYPE_BITMAP) {
              throw new IllegalArgumentException("The " + key + " key cannot be used to put an int");
          }
          bundle.putInt(key, value);
      }
  
      /**
       * Returns the value associated with the given key,
       * or 0 if the key is not found in the meta data.
       *
       * @param key The key the value is stored under
       * @return an int value
       */
      public int getInt(String key) {
          return mBundle.getInt(key, 0);
      }
  
      /**
       * Returns a {@link Bitmap} for the given key or null if the key is not found in the meta data.
       *
       * @param key The key the value is stored under
       * @return a {@link Bitmap} or null
       * @deprecated Use getBitmapId(String) instead
       */
      @Deprecated
      public Bitmap getBitmap(String key) {
          Bitmap bmp = null;
          try {
              bmp = mBundle.getParcelable(key);
          } catch (Exception e) {
              // ignore, value was not a bitmap
              Log.w(TAG, "Failed to retrieve a key as Bitmap.", e);
          }
          return bmp;
      }
  
      /**
       * Retrieves an identifier for a bitmap.
       *
       * The format of an identifier is opaque to the application,
       * with a special case of value 0 being invalid.
       * An identifier for a given image-tuner pair is unique, so an application
       * may cache images and determine if there is a necessity to fetch them
       * again - if identifier changes, it means the image has changed.
       *
       * Only bitmap keys may be used with this method:
       * <ul>
       * <li>{@link #METADATA_KEY_ICON}</li>
       * <li>{@link #METADATA_KEY_ART}</li>
       * </ul>
       *
       * @param key The key the value is stored under.
       * @return a bitmap identifier or 0 if it's missing.
       * @hide This API is not thoroughly elaborated yet
       */
      public int getBitmapId(@NonNull String key) {
          if (!METADATA_KEY_ICON.equals(key) && !METADATA_KEY_ART.equals(key)) return 0;
          return getInt(key);
      }
  
      public Clock getClock(String key) {
          Clock clock = null;
          try {
              clock = mBundle.getParcelable(key);
          } catch (Exception e) {
              // ignore, value was not a clock.
              Log.w(TAG, "Failed to retrieve a key as Clock.", e);
          }
          return clock;
      }
  
      @Override
      public int describeContents() {
          return 0;
      }
  
      @Override
      public void writeToParcel(Parcel dest, int flags) {
          dest.writeBundle(mBundle);
      }
  
      /**
       * Returns the number of fields in this meta data.
       *
       * @return the number of fields in the meta data.
       */
      public int size() {
          return mBundle.size();
      }
  
      /**
       * Returns a Set containing the Strings used as keys in this meta data.
       *
       * @return a Set of String keys
       */
      public Set<String> keySet() {
          return mBundle.keySet();
      }
  
      /**
       * Helper for getting the String key used by {@link RadioMetadata} from the
       * corrsponding native integer key.
       *
       * @param editorKey The key used by the editor
       * @return the key used by this class or null if no mapping exists
       * @hide
       */
      public static String getKeyFromNativeKey(int nativeKey) {
          return NATIVE_KEY_MAPPING.get(nativeKey, null);
      }
  
      public static final @android.annotation.NonNull Parcelable.Creator<RadioMetadata> CREATOR =
              new Parcelable.Creator<RadioMetadata>() {
                  @Override
                  public RadioMetadata createFromParcel(Parcel in) {
                      return new RadioMetadata(in);
                  }
  
                  @Override
                  public RadioMetadata[] newArray(int size) {
                      return new RadioMetadata[size];
                  }
              };
  
      /**
       * Use to build RadioMetadata objects.
       */
      public static final class Builder {
          private final Bundle mBundle;
  
          /**
           * Create an empty Builder. Any field that should be included in the
           * {@link RadioMetadata} must be added.
           */
          public Builder() {
              mBundle = new Bundle();
          }
  
          /**
           * Create a Builder using a {@link RadioMetadata} instance to set the
           * initial values. All fields in the source meta data will be included in
           * the new meta data. Fields can be overwritten by adding the same key.
           *
           * @param source
           */
          public Builder(RadioMetadata source) {
              mBundle = new Bundle(source.mBundle);
          }
  
          /**
           * Create a Builder using a {@link RadioMetadata} instance to set
           * initial values, but replace bitmaps with a scaled down copy if they
           * are larger than maxBitmapSize.
           *
           * @param source The original meta data to copy.
           * @param maxBitmapSize The maximum height/width for bitmaps contained
           *            in the meta data.
           * @hide
           */
          public Builder(RadioMetadata source, int maxBitmapSize) {
              this(source);
              for (String key : mBundle.keySet()) {
                  Object value = mBundle.get(key);
                  if (value != null && value instanceof Bitmap) {
                      Bitmap bmp = (Bitmap) value;
                      if (bmp.getHeight() > maxBitmapSize || bmp.getWidth() > maxBitmapSize) {
                          putBitmap(key, scaleBitmap(bmp, maxBitmapSize));
                      }
                  }
              }
          }
  
          /**
           * Put a String value into the meta data. Custom keys may be used, but if
           * the METADATA_KEYs defined in this class are used they may only be one
           * of the following:
           * <ul>
           * <li>{@link #METADATA_KEY_RDS_PS}</li>
           * <li>{@link #METADATA_KEY_RDS_RT}</li>
           * <li>{@link #METADATA_KEY_TITLE}</li>
           * <li>{@link #METADATA_KEY_ARTIST}</li>
           * <li>{@link #METADATA_KEY_ALBUM}</li>
           * <li>{@link #METADATA_KEY_GENRE}</li>
           * </ul>
           *
           * @param key The key for referencing this value
           * @param value The String value to store
           * @return the same Builder instance
           */
          public Builder putString(String key, String value) {
              if (!METADATA_KEYS_TYPE.containsKey(key) ||
                      METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
                  throw new IllegalArgumentException("The " + key
                          + " key cannot be used to put a String");
              }
              mBundle.putString(key, value);
              return this;
          }
  
          /**
           * Put an int value into the meta data. Custom keys may be used, but if
           * the METADATA_KEYs defined in this class are used they may only be one
           * of the following:
           * <ul>
           * <li>{@link #METADATA_KEY_RDS_PI}</li>
           * <li>{@link #METADATA_KEY_RDS_PTY}</li>
           * <li>{@link #METADATA_KEY_RBDS_PTY}</li>
           * </ul>
           * or any bitmap represented by its identifier.
           *
           * @param key The key for referencing this value
           * @param value The int value to store
           * @return the same Builder instance
           */
          public Builder putInt(String key, int value) {
              RadioMetadata.putInt(mBundle, key, value);
              return this;
          }
  
          /**
           * Put a {@link Bitmap} into the meta data. Custom keys may be used, but
           * if the METADATA_KEYs defined in this class are used they may only be
           * one of the following:
           * <ul>
           * <li>{@link #METADATA_KEY_ICON}</li>
           * <li>{@link #METADATA_KEY_ART}</li>
           * </ul>
           * <p>
           *
           * @param key The key for referencing this value
           * @param value The Bitmap to store
           * @return the same Builder instance
           */
          public Builder putBitmap(String key, Bitmap value) {
              if (!METADATA_KEYS_TYPE.containsKey(key) ||
                      METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
                  throw new IllegalArgumentException("The " + key
                          + " key cannot be used to put a Bitmap");
              }
              mBundle.putParcelable(key, value);
              return this;
          }
  
          /**
           * Put a {@link RadioMetadata.Clock} into the meta data. Custom keys may be used, but if the
           * METADATA_KEYs defined in this class are used they may only be one of the following:
           * <ul>
           * <li>{@link #MEADATA_KEY_CLOCK}</li>
           * </ul>
           *
           * @param utcSecondsSinceEpoch Number of seconds since epoch for UTC + 0 timezone.
           * @param timezoneOffsetInMinutes Offset of timezone from UTC + 0 in minutes.
           * @return the same Builder instance.
           */
          public Builder putClock(String key, long utcSecondsSinceEpoch, int timezoneOffsetMinutes) {
              if (!METADATA_KEYS_TYPE.containsKey(key) ||
                      METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_CLOCK) {
                  throw new IllegalArgumentException("The " + key
                      + " key cannot be used to put a RadioMetadata.Clock.");
              }
              mBundle.putParcelable(key, new Clock(utcSecondsSinceEpoch, timezoneOffsetMinutes));
              return this;
          }
  
          /**
           * Creates a {@link RadioMetadata} instance with the specified fields.
           *
           * @return a new {@link RadioMetadata} object
           */
          public RadioMetadata build() {
              return new RadioMetadata(mBundle);
          }
  
          private Bitmap scaleBitmap(Bitmap bmp, int maxSize) {
              float maxSizeF = maxSize;
              float widthScale = maxSizeF / bmp.getWidth();
              float heightScale = maxSizeF / bmp.getHeight();
              float scale = Math.min(widthScale, heightScale);
              int height = (int) (bmp.getHeight() * scale);
              int width = (int) (bmp.getWidth() * scale);
              return Bitmap.createScaledBitmap(bmp, width, height, true);
          }
      }
  
      int putIntFromNative(int nativeKey, int value) {
          String key = getKeyFromNativeKey(nativeKey);
          try {
              putInt(mBundle, key, value);
              return 0;
          } catch (IllegalArgumentException ex) {
              return -1;
          }
      }
  
      int putStringFromNative(int nativeKey, String value) {
          String key = getKeyFromNativeKey(nativeKey);
          if (!METADATA_KEYS_TYPE.containsKey(key) ||
                  METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
              return -1;
          }
          mBundle.putString(key, value);
          return 0;
      }
  
      int putBitmapFromNative(int nativeKey, byte[] value) {
          String key = getKeyFromNativeKey(nativeKey);
          if (!METADATA_KEYS_TYPE.containsKey(key) ||
                  METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
              return -1;
          }
          Bitmap bmp = null;
          try {
              bmp = BitmapFactory.decodeByteArray(value, 0, value.length);
              if (bmp != null) {
                  mBundle.putParcelable(key, bmp);
                  return 0;
              }
          } catch (Exception e) {
          }
          return -1;
      }
  
      int putClockFromNative(int nativeKey, long utcEpochSeconds, int timezoneOffsetInMinutes) {
          String key = getKeyFromNativeKey(nativeKey);
          if (!METADATA_KEYS_TYPE.containsKey(key) ||
                  METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_CLOCK) {
                return -1;
          }
          mBundle.putParcelable(key, new RadioMetadata.Clock(
              utcEpochSeconds, timezoneOffsetInMinutes));
          return 0;
      }
  }
  