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.content.BroadcastReceiver;
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.database.ContentObserver;
24 import android.media.AudioManager;
25 import android.net.Uri;
26 import android.os.Handler;
27 import androidx.annotation.Nullable;
28 import com.google.android.exoplayer2.util.Assertions;
29 import com.google.android.exoplayer2.util.Util;
30 
31 /**
32  * Receives broadcast events indicating changes to the device's audio capabilities, notifying a
33  * {@link Listener} when audio capability changes occur.
34  */
35 public final class AudioCapabilitiesReceiver {
36 
37   /**
38    * Listener notified when audio capabilities change.
39    */
40   public interface Listener {
41 
42     /**
43      * Called when the audio capabilities change.
44      *
45      * @param audioCapabilities The current audio capabilities for the device.
46      */
onAudioCapabilitiesChanged(AudioCapabilities audioCapabilities)47     void onAudioCapabilitiesChanged(AudioCapabilities audioCapabilities);
48 
49   }
50 
51   private final Context context;
52   private final Listener listener;
53   private final Handler handler;
54   @Nullable private final BroadcastReceiver receiver;
55   @Nullable private final ExternalSurroundSoundSettingObserver externalSurroundSoundSettingObserver;
56 
57   @Nullable /* package */ AudioCapabilities audioCapabilities;
58   private boolean registered;
59 
60   /**
61    * @param context A context for registering the receiver.
62    * @param listener The listener to notify when audio capabilities change.
63    */
AudioCapabilitiesReceiver(Context context, Listener listener)64   public AudioCapabilitiesReceiver(Context context, Listener listener) {
65     context = context.getApplicationContext();
66     this.context = context;
67     this.listener = Assertions.checkNotNull(listener);
68     handler = new Handler(Util.getLooper());
69     receiver = Util.SDK_INT >= 21 ? new HdmiAudioPlugBroadcastReceiver() : null;
70     Uri externalSurroundSoundUri = AudioCapabilities.getExternalSurroundSoundGlobalSettingUri();
71     externalSurroundSoundSettingObserver =
72         externalSurroundSoundUri != null
73             ? new ExternalSurroundSoundSettingObserver(
74                 handler, context.getContentResolver(), externalSurroundSoundUri)
75             : null;
76   }
77 
78   /**
79    * Registers the receiver, meaning it will notify the listener when audio capability changes
80    * occur. The current audio capabilities will be returned. It is important to call
81    * {@link #unregister} when the receiver is no longer required.
82    *
83    * @return The current audio capabilities for the device.
84    */
85   @SuppressWarnings("InlinedApi")
register()86   public AudioCapabilities register() {
87     if (registered) {
88       return Assertions.checkNotNull(audioCapabilities);
89     }
90     registered = true;
91     if (externalSurroundSoundSettingObserver != null) {
92       externalSurroundSoundSettingObserver.register();
93     }
94     Intent stickyIntent = null;
95     if (receiver != null) {
96       IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG);
97       stickyIntent =
98           context.registerReceiver(
99               receiver, intentFilter, /* broadcastPermission= */ null, handler);
100     }
101     audioCapabilities = AudioCapabilities.getCapabilities(context, stickyIntent);
102     return audioCapabilities;
103   }
104 
105   /**
106    * Unregisters the receiver, meaning it will no longer notify the listener when audio capability
107    * changes occur.
108    */
unregister()109   public void unregister() {
110     if (!registered) {
111       return;
112     }
113     audioCapabilities = null;
114     if (receiver != null) {
115       context.unregisterReceiver(receiver);
116     }
117     if (externalSurroundSoundSettingObserver != null) {
118       externalSurroundSoundSettingObserver.unregister();
119     }
120     registered = false;
121   }
122 
onNewAudioCapabilities(AudioCapabilities newAudioCapabilities)123   private void onNewAudioCapabilities(AudioCapabilities newAudioCapabilities) {
124     if (registered && !newAudioCapabilities.equals(audioCapabilities)) {
125       audioCapabilities = newAudioCapabilities;
126       listener.onAudioCapabilitiesChanged(newAudioCapabilities);
127     }
128   }
129 
130   private final class HdmiAudioPlugBroadcastReceiver extends BroadcastReceiver {
131 
132     @Override
onReceive(Context context, Intent intent)133     public void onReceive(Context context, Intent intent) {
134       if (!isInitialStickyBroadcast()) {
135         onNewAudioCapabilities(AudioCapabilities.getCapabilities(context, intent));
136       }
137     }
138   }
139 
140   private final class ExternalSurroundSoundSettingObserver extends ContentObserver {
141 
142     private final ContentResolver resolver;
143     private final Uri settingUri;
144 
ExternalSurroundSoundSettingObserver( Handler handler, ContentResolver resolver, Uri settingUri)145     public ExternalSurroundSoundSettingObserver(
146         Handler handler, ContentResolver resolver, Uri settingUri) {
147       super(handler);
148       this.resolver = resolver;
149       this.settingUri = settingUri;
150     }
151 
register()152     public void register() {
153       resolver.registerContentObserver(settingUri, /* notifyForDescendants= */ false, this);
154     }
155 
unregister()156     public void unregister() {
157       resolver.unregisterContentObserver(this);
158     }
159 
160     @Override
onChange(boolean selfChange)161     public void onChange(boolean selfChange) {
162       onNewAudioCapabilities(AudioCapabilities.getCapabilities(context));
163     }
164   }
165 
166 }
167