content: /*
   * Copyright (C) 2014 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.telecom;
  
  import android.annotation.IntDef;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.TestApi;
  import android.bluetooth.BluetoothDevice;
  import android.os.Parcel;
  import android.os.Parcelable;
  
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.List;
  import java.util.Locale;
  import java.util.Objects;
  import java.util.stream.Collectors;
  
  /**
   *  Encapsulates the telecom audio state, including the current audio routing, supported audio
   *  routing and mute.
   */
  public final class CallAudioState implements Parcelable {
      /** @hide */
      @Retention(RetentionPolicy.SOURCE)
      @IntDef(value={ROUTE_EARPIECE, ROUTE_BLUETOOTH, ROUTE_WIRED_HEADSET, ROUTE_SPEAKER},
              flag=true)
      public @interface CallAudioRoute {}
  
      /** Direct the audio stream through the device's earpiece. */
      public static final int ROUTE_EARPIECE      = 0x00000001;
  
      /** Direct the audio stream through Bluetooth. */
      public static final int ROUTE_BLUETOOTH     = 0x00000002;
  
      /** Direct the audio stream through a wired headset. */
      public static final int ROUTE_WIRED_HEADSET = 0x00000004;
  
      /** Direct the audio stream through the device's speakerphone. */
      public static final int ROUTE_SPEAKER       = 0x00000008;
  
      /**
       * Direct the audio stream through the device's earpiece or wired headset if one is
       * connected.
       */
      public static final int ROUTE_WIRED_OR_EARPIECE = ROUTE_EARPIECE | ROUTE_WIRED_HEADSET;
  
      /**
       * Bit mask of all possible audio routes.
       *
       * @hide
       **/
      public static final int ROUTE_ALL = ROUTE_EARPIECE | ROUTE_BLUETOOTH | ROUTE_WIRED_HEADSET |
              ROUTE_SPEAKER;
  
      private final boolean isMuted;
      private final int route;
      private final int supportedRouteMask;
      private final BluetoothDevice activeBluetoothDevice;
      private final Collection<BluetoothDevice> supportedBluetoothDevices;
  
      /**
       * Constructor for a {@link CallAudioState} object.
       *
       * @param muted {@code true} if the call is muted, {@code false} otherwise.
       * @param route The current audio route being used.
       * Allowed values:
       * {@link #ROUTE_EARPIECE}
       * {@link #ROUTE_BLUETOOTH}
       * {@link #ROUTE_WIRED_HEADSET}
       * {@link #ROUTE_SPEAKER}
       * @param supportedRouteMask Bit mask of all routes supported by this call. This should be a
       * bitwise combination of the following values:
       * {@link #ROUTE_EARPIECE}
       * {@link #ROUTE_BLUETOOTH}
       * {@link #ROUTE_WIRED_HEADSET}
       * {@link #ROUTE_SPEAKER}
       */
      public CallAudioState(boolean muted, @CallAudioRoute int route,
              @CallAudioRoute int supportedRouteMask) {
          this(muted, route, supportedRouteMask, null, Collections.emptyList());
      }
  
      /** @hide */
      @TestApi
      public CallAudioState(boolean isMuted, @CallAudioRoute int route,
              @CallAudioRoute int supportedRouteMask,
              @Nullable BluetoothDevice activeBluetoothDevice,
              @NonNull Collection<BluetoothDevice> supportedBluetoothDevices) {
          this.isMuted = isMuted;
          this.route = route;
          this.supportedRouteMask = supportedRouteMask;
          this.activeBluetoothDevice = activeBluetoothDevice;
          this.supportedBluetoothDevices = supportedBluetoothDevices;
      }
  
      /** @hide */
      public CallAudioState(CallAudioState state) {
          isMuted = state.isMuted();
          route = state.getRoute();
          supportedRouteMask = state.getSupportedRouteMask();
          activeBluetoothDevice = state.activeBluetoothDevice;
          supportedBluetoothDevices = state.getSupportedBluetoothDevices();
      }
  
      /** @hide */
      @SuppressWarnings("deprecation")
      public CallAudioState(AudioState state) {
          isMuted = state.isMuted();
          route = state.getRoute();
          supportedRouteMask = state.getSupportedRouteMask();
          activeBluetoothDevice = null;
          supportedBluetoothDevices = Collections.emptyList();
      }
  
      @Override
      public boolean equals(Object obj) {
          if (obj == null) {
              return false;
          }
          if (!(obj instanceof CallAudioState)) {
              return false;
          }
          CallAudioState state = (CallAudioState) obj;
          if (supportedBluetoothDevices.size() != state.supportedBluetoothDevices.size()) {
              return false;
          }
          for (BluetoothDevice device : supportedBluetoothDevices) {
              if (!state.supportedBluetoothDevices.contains(device)) {
                  return false;
              }
          }
          return Objects.equals(activeBluetoothDevice, state.activeBluetoothDevice) && isMuted() ==
                  state.isMuted() && getRoute() == state.getRoute() && getSupportedRouteMask() ==
                  state.getSupportedRouteMask();
      }
  
      @Override
      public String toString() {
          String bluetoothDeviceList = supportedBluetoothDevices.stream()
                  .map(BluetoothDevice::getAddress).collect(Collectors.joining(", "));
  
          return String.format(Locale.US,
                  "[AudioState isMuted: %b, route: %s, supportedRouteMask: %s, " +
                          "activeBluetoothDevice: [%s], supportedBluetoothDevices: [%s]]",
                  isMuted,
                  audioRouteToString(route),
                  audioRouteToString(supportedRouteMask),
                  activeBluetoothDevice,
                  bluetoothDeviceList);
      }
  
      /**
       * @return {@code true} if the call is muted, {@code false} otherwise.
       */
      public boolean isMuted() {
          return isMuted;
      }
  
      /**
       * @return The current audio route being used.
       */
      @CallAudioRoute
      public int getRoute() {
          return route;
      }
  
      /**
       * @return Bit mask of all routes supported by this call.
       */
      @CallAudioRoute
      public int getSupportedRouteMask() {
          return supportedRouteMask;
      }
  
      /**
       * @return The {@link BluetoothDevice} through which audio is being routed.
       *         Will not be {@code null} if {@link #getRoute()} returns {@link #ROUTE_BLUETOOTH}.
       */
      public BluetoothDevice getActiveBluetoothDevice() {
          return activeBluetoothDevice;
      }
  
      /**
       * @return {@link List} of {@link BluetoothDevice}s that can be used for this call.
       */
      public Collection<BluetoothDevice> getSupportedBluetoothDevices() {
          return supportedBluetoothDevices;
      }
  
      /**
       * Converts the provided audio route into a human readable string representation.
       *
       * @param route to convert into a string.
       *
       * @return String representation of the provided audio route.
       */
      public static String audioRouteToString(int route) {
          if (route == 0 || (route & ~ROUTE_ALL) != 0x0) {
              return "UNKNOWN";
          }
  
          StringBuffer buffer = new StringBuffer();
          if ((route & ROUTE_EARPIECE) == ROUTE_EARPIECE) {
              listAppend(buffer, "EARPIECE");
          }
          if ((route & ROUTE_BLUETOOTH) == ROUTE_BLUETOOTH) {
              listAppend(buffer, "BLUETOOTH");
          }
          if ((route & ROUTE_WIRED_HEADSET) == ROUTE_WIRED_HEADSET) {
              listAppend(buffer, "WIRED_HEADSET");
          }
          if ((route & ROUTE_SPEAKER) == ROUTE_SPEAKER) {
              listAppend(buffer, "SPEAKER");
          }
  
          return buffer.toString();
      }
  
      /**
       * Responsible for creating AudioState objects for deserialized Parcels.
       */
      public static final @android.annotation.NonNull Parcelable.Creator<CallAudioState> CREATOR =
              new Parcelable.Creator<CallAudioState> () {
  
          @Override
          public CallAudioState createFromParcel(Parcel source) {
              boolean isMuted = source.readByte() == 0 ? false : true;
              int route = source.readInt();
              int supportedRouteMask = source.readInt();
              BluetoothDevice activeBluetoothDevice = source.readParcelable(
                      ClassLoader.getSystemClassLoader());
              List<BluetoothDevice> supportedBluetoothDevices = new ArrayList<>();
              source.readParcelableList(supportedBluetoothDevices,
                      ClassLoader.getSystemClassLoader());
              return new CallAudioState(isMuted, route,
                      supportedRouteMask, activeBluetoothDevice, supportedBluetoothDevices);
          }
  
          @Override
          public CallAudioState[] newArray(int size) {
              return new CallAudioState[size];
          }
      };
  
      /**
       * {@inheritDoc}
       */
      @Override
      public int describeContents() {
          return 0;
      }
  
      /**
       * Writes AudioState object into a serializeable Parcel.
       */
      @Override
      public void writeToParcel(Parcel destination, int flags) {
          destination.writeByte((byte) (isMuted ? 1 : 0));
          destination.writeInt(route);
          destination.writeInt(supportedRouteMask);
          destination.writeParcelable(activeBluetoothDevice, 0);
          destination.writeParcelableList(new ArrayList<>(supportedBluetoothDevices), 0);
      }
  
      private static void listAppend(StringBuffer buffer, String str) {
          if (buffer.length() > 0) {
              buffer.append(", ");
          }
          buffer.append(str);
      }
  }
  