1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.android.exoplayer2.audio;
17 
18 import android.annotation.SuppressLint;
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.IntentFilter;
22 import android.media.AudioFormat;
23 import android.media.AudioManager;
24 import android.net.Uri;
25 import android.provider.Settings.Global;
26 import androidx.annotation.Nullable;
27 import com.google.android.exoplayer2.C;
28 import com.google.android.exoplayer2.util.Util;
29 import java.util.Arrays;
30 
31 /** Represents the set of audio formats that a device is capable of playing. */
32 public final class AudioCapabilities {
33 
34   private static final int DEFAULT_MAX_CHANNEL_COUNT = 8;
35 
36   /** The minimum audio capabilities supported by all devices. */
37   public static final AudioCapabilities DEFAULT_AUDIO_CAPABILITIES =
38       new AudioCapabilities(new int[] {AudioFormat.ENCODING_PCM_16BIT}, DEFAULT_MAX_CHANNEL_COUNT);
39 
40   /** Audio capabilities when the device specifies external surround sound. */
41   private static final AudioCapabilities EXTERNAL_SURROUND_SOUND_CAPABILITIES =
42       new AudioCapabilities(
43           new int[] {
44             AudioFormat.ENCODING_PCM_16BIT, AudioFormat.ENCODING_AC3, AudioFormat.ENCODING_E_AC3
45           },
46           DEFAULT_MAX_CHANNEL_COUNT);
47 
48   /** Global settings key for devices that can specify external surround sound. */
49   private static final String EXTERNAL_SURROUND_SOUND_KEY = "external_surround_sound_enabled";
50 
51   /**
52    * Returns the current audio capabilities for the device.
53    *
54    * @param context A context for obtaining the current audio capabilities.
55    * @return The current audio capabilities for the device.
56    */
57   @SuppressWarnings("InlinedApi")
getCapabilities(Context context)58   public static AudioCapabilities getCapabilities(Context context) {
59     Intent intent =
60         context.registerReceiver(
61             /* receiver= */ null, new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG));
62     return getCapabilities(context, intent);
63   }
64 
65   @SuppressLint("InlinedApi")
getCapabilities(Context context, @Nullable Intent intent)66   /* package */ static AudioCapabilities getCapabilities(Context context, @Nullable Intent intent) {
67     if (deviceMaySetExternalSurroundSoundGlobalSetting()
68         && Global.getInt(context.getContentResolver(), EXTERNAL_SURROUND_SOUND_KEY, 0) == 1) {
69       return EXTERNAL_SURROUND_SOUND_CAPABILITIES;
70     }
71     if (intent == null || intent.getIntExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, 0) == 0) {
72       return DEFAULT_AUDIO_CAPABILITIES;
73     }
74     return new AudioCapabilities(
75         intent.getIntArrayExtra(AudioManager.EXTRA_ENCODINGS),
76         intent.getIntExtra(
77             AudioManager.EXTRA_MAX_CHANNEL_COUNT, /* defaultValue= */ DEFAULT_MAX_CHANNEL_COUNT));
78   }
79 
80   /**
81    * Returns the global settings {@link Uri} used by the device to specify external surround sound,
82    * or null if the device does not support this functionality.
83    */
84   @Nullable
getExternalSurroundSoundGlobalSettingUri()85   /* package */ static Uri getExternalSurroundSoundGlobalSettingUri() {
86     return deviceMaySetExternalSurroundSoundGlobalSetting()
87         ? Global.getUriFor(EXTERNAL_SURROUND_SOUND_KEY)
88         : null;
89   }
90 
91   private final int[] supportedEncodings;
92   private final int maxChannelCount;
93 
94   /**
95    * Constructs new audio capabilities based on a set of supported encodings and a maximum channel
96    * count.
97    *
98    * <p>Applications should generally call {@link #getCapabilities(Context)} to obtain an instance
99    * based on the capabilities advertised by the platform, rather than calling this constructor.
100    *
101    * @param supportedEncodings Supported audio encodings from {@link android.media.AudioFormat}'s
102    *     {@code ENCODING_*} constants. Passing {@code null} indicates that no encodings are
103    *     supported.
104    * @param maxChannelCount The maximum number of audio channels that can be played simultaneously.
105    */
AudioCapabilities(@ullable int[] supportedEncodings, int maxChannelCount)106   public AudioCapabilities(@Nullable int[] supportedEncodings, int maxChannelCount) {
107     if (supportedEncodings != null) {
108       this.supportedEncodings = Arrays.copyOf(supportedEncodings, supportedEncodings.length);
109       Arrays.sort(this.supportedEncodings);
110     } else {
111       this.supportedEncodings = new int[0];
112     }
113     this.maxChannelCount = maxChannelCount;
114   }
115 
116   /**
117    * Returns whether this device supports playback of the specified audio {@code encoding}.
118    *
119    * @param encoding One of {@link C.Encoding}'s {@code ENCODING_*} constants.
120    * @return Whether this device supports playback the specified audio {@code encoding}.
121    */
supportsEncoding(@.Encoding int encoding)122   public boolean supportsEncoding(@C.Encoding int encoding) {
123     return Arrays.binarySearch(supportedEncodings, encoding) >= 0;
124   }
125 
126   /**
127    * Returns the maximum number of channels the device can play at the same time.
128    */
getMaxChannelCount()129   public int getMaxChannelCount() {
130     return maxChannelCount;
131   }
132 
133   @Override
equals(@ullable Object other)134   public boolean equals(@Nullable Object other) {
135     if (this == other) {
136       return true;
137     }
138     if (!(other instanceof AudioCapabilities)) {
139       return false;
140     }
141     AudioCapabilities audioCapabilities = (AudioCapabilities) other;
142     return Arrays.equals(supportedEncodings, audioCapabilities.supportedEncodings)
143         && maxChannelCount == audioCapabilities.maxChannelCount;
144   }
145 
146   @Override
hashCode()147   public int hashCode() {
148     return maxChannelCount + 31 * Arrays.hashCode(supportedEncodings);
149   }
150 
151   @Override
toString()152   public String toString() {
153     return "AudioCapabilities[maxChannelCount=" + maxChannelCount
154         + ", supportedEncodings=" + Arrays.toString(supportedEncodings) + "]";
155   }
156 
deviceMaySetExternalSurroundSoundGlobalSetting()157   private static boolean deviceMaySetExternalSurroundSoundGlobalSetting() {
158     return Util.SDK_INT >= 17 && "Amazon".equals(Util.MANUFACTURER);
159   }
160 }
161