OSDN Git Service

Merge "docs: Add documentation for equals() method" into qt-dev
[android-x86/frameworks-base.git] / services / core / java / com / android / server / audio / AudioDeviceInventory.java
1 /*
2  * Copyright 2019 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.android.server.audio;
17
18 import android.annotation.NonNull;
19 import android.app.ActivityManager;
20 import android.bluetooth.BluetoothA2dp;
21 import android.bluetooth.BluetoothAdapter;
22 import android.bluetooth.BluetoothDevice;
23 import android.bluetooth.BluetoothHearingAid;
24 import android.bluetooth.BluetoothProfile;
25 import android.content.Intent;
26 import android.media.AudioDevicePort;
27 import android.media.AudioFormat;
28 import android.media.AudioManager;
29 import android.media.AudioPort;
30 import android.media.AudioRoutesInfo;
31 import android.media.AudioSystem;
32 import android.media.IAudioRoutesObserver;
33 import android.os.Binder;
34 import android.os.RemoteCallbackList;
35 import android.os.RemoteException;
36 import android.os.UserHandle;
37 import android.text.TextUtils;
38 import android.util.ArrayMap;
39 import android.util.ArraySet;
40 import android.util.Log;
41 import android.util.Slog;
42
43 import com.android.internal.annotations.GuardedBy;
44
45 import java.util.ArrayList;
46
47 /**
48  * Class to manage the inventory of all connected devices.
49  * This class is thread-safe.
50  */
51 public final class AudioDeviceInventory {
52
53     private static final String TAG = "AS.AudioDeviceInventory";
54
55     // Actual list of connected devices
56     // Key for map created from DeviceInfo.makeDeviceListKey()
57     private final ArrayMap<String, DeviceInfo> mConnectedDevices = new ArrayMap<>();
58
59     private final @NonNull AudioDeviceBroker mDeviceBroker;
60
61     AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
62         mDeviceBroker = broker;
63     }
64
65     // cache of the address of the last dock the device was connected to
66     private String mDockAddress;
67
68     // Monitoring of audio routes.  Protected by mAudioRoutes.
69     final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo();
70     final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers =
71             new RemoteCallbackList<IAudioRoutesObserver>();
72
73     //------------------------------------------------------------
74     /**
75      * Class to store info about connected devices.
76      * Use makeDeviceListKey() to make a unique key for this list.
77      */
78     private static class DeviceInfo {
79         final int mDeviceType;
80         final String mDeviceName;
81         final String mDeviceAddress;
82         int mDeviceCodecFormat;
83
84         DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) {
85             mDeviceType = deviceType;
86             mDeviceName = deviceName;
87             mDeviceAddress = deviceAddress;
88             mDeviceCodecFormat = deviceCodecFormat;
89         }
90
91         @Override
92         public String toString() {
93             return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
94                     + " name:" + mDeviceName
95                     + " addr:" + mDeviceAddress
96                     + " codec: " + Integer.toHexString(mDeviceCodecFormat) + "]";
97         }
98
99         /**
100          * Generate a unique key for the mConnectedDevices List by composing the device "type"
101          * and the "address" associated with a specific instance of that device type
102          */
103         private static String makeDeviceListKey(int device, String deviceAddress) {
104             return "0x" + Integer.toHexString(device) + ":" + deviceAddress;
105         }
106     }
107
108     /**
109      * A class just for packaging up a set of connection parameters.
110      */
111     /*package*/ class WiredDeviceConnectionState {
112         public final int mType;
113         public final @AudioService.ConnectionState int mState;
114         public final String mAddress;
115         public final String mName;
116         public final String mCaller;
117
118         /*package*/ WiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
119                                                String address, String name, String caller) {
120             mType = type;
121             mState = state;
122             mAddress = address;
123             mName = name;
124             mCaller = caller;
125         }
126     }
127
128     //------------------------------------------------------------
129     // Message handling from AudioDeviceBroker
130
131     /**
132      * Restore previously connected devices. Use in case of audio server crash
133      * (see AudioService.onAudioServerDied() method)
134      */
135     /*package*/ void onRestoreDevices() {
136         synchronized (mConnectedDevices) {
137             for (int i = 0; i < mConnectedDevices.size(); i++) {
138                 DeviceInfo di = mConnectedDevices.valueAt(i);
139                 AudioSystem.setDeviceConnectionState(
140                         di.mDeviceType,
141                         AudioSystem.DEVICE_STATE_AVAILABLE,
142                         di.mDeviceAddress,
143                         di.mDeviceName,
144                         di.mDeviceCodecFormat);
145             }
146         }
147     }
148
149     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
150     /*package*/ void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo,
151             @AudioService.BtProfileConnectionState int state) {
152         final BluetoothDevice btDevice = btInfo.getBtDevice();
153         int a2dpVolume = btInfo.getVolume();
154         if (AudioService.DEBUG_DEVICES) {
155             Log.d(TAG, "onSetA2dpSinkConnectionState btDevice=" + btDevice + " state="
156                     + state + " is dock=" + btDevice.isBluetoothDock() + " vol=" + a2dpVolume);
157         }
158         String address = btDevice.getAddress();
159         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
160             address = "";
161         }
162         AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
163                 "A2DP sink connected: device addr=" + address + " state=" + state
164                         + " vol=" + a2dpVolume));
165
166         final int a2dpCodec = btInfo.getCodec();
167
168         synchronized (mConnectedDevices) {
169             final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
170                     btDevice.getAddress());
171             final DeviceInfo di = mConnectedDevices.get(key);
172             boolean isConnected = di != null;
173
174             if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
175                 if (btDevice.isBluetoothDock()) {
176                     if (state == BluetoothProfile.STATE_DISCONNECTED) {
177                         // introduction of a delay for transient disconnections of docks when
178                         // power is rapidly turned off/on, this message will be canceled if
179                         // we reconnect the dock under a preset delay
180                         makeA2dpDeviceUnavailableLater(address,
181                                 AudioDeviceBroker.BTA2DP_DOCK_TIMEOUT_MS);
182                         // the next time isConnected is evaluated, it will be false for the dock
183                     }
184                 } else {
185                     makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
186                 }
187             } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
188                 if (btDevice.isBluetoothDock()) {
189                     // this could be a reconnection after a transient disconnection
190                     mDeviceBroker.cancelA2dpDockTimeout();
191                     mDockAddress = address;
192                 } else {
193                     // this could be a connection of another A2DP device before the timeout of
194                     // a dock: cancel the dock timeout, and make the dock unavailable now
195                     if (mDeviceBroker.hasScheduledA2dpDockTimeout() && mDockAddress != null) {
196                         mDeviceBroker.cancelA2dpDockTimeout();
197                         makeA2dpDeviceUnavailableNow(mDockAddress,
198                                 AudioSystem.AUDIO_FORMAT_DEFAULT);
199                     }
200                 }
201                 if (a2dpVolume != -1) {
202                     mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC,
203                             // convert index to internal representation in VolumeStreamState
204                             a2dpVolume * 10,
205                             AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "onSetA2dpSinkConnectionState");
206                 }
207                 makeA2dpDeviceAvailable(address, BtHelper.getName(btDevice),
208                         "onSetA2dpSinkConnectionState", a2dpCodec);
209             }
210         }
211     }
212
213     /*package*/ void onSetA2dpSourceConnectionState(
214             @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int state) {
215         final BluetoothDevice btDevice = btInfo.getBtDevice();
216         if (AudioService.DEBUG_DEVICES) {
217             Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state="
218                     + state);
219         }
220         String address = btDevice.getAddress();
221         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
222             address = "";
223         }
224
225         synchronized (mConnectedDevices) {
226             final String key = DeviceInfo.makeDeviceListKey(
227                     AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
228             final DeviceInfo di = mConnectedDevices.get(key);
229             boolean isConnected = di != null;
230
231             if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
232                 makeA2dpSrcUnavailable(address);
233             } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
234                 makeA2dpSrcAvailable(address);
235             }
236         }
237     }
238
239     /*package*/ void onSetHearingAidConnectionState(BluetoothDevice btDevice,
240                 @AudioService.BtProfileConnectionState int state, int streamType) {
241         String address = btDevice.getAddress();
242         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
243             address = "";
244         }
245         AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
246                 "onSetHearingAidConnectionState addr=" + address));
247
248         synchronized (mConnectedDevices) {
249             final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID,
250                     btDevice.getAddress());
251             final DeviceInfo di = mConnectedDevices.get(key);
252             boolean isConnected = di != null;
253
254             if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
255                 makeHearingAidDeviceUnavailable(address);
256             } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
257                 makeHearingAidDeviceAvailable(address, BtHelper.getName(btDevice), streamType,
258                         "onSetHearingAidConnectionState");
259             }
260         }
261     }
262
263     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
264     /*package*/ void onBluetoothA2dpActiveDeviceChange(
265             @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int event) {
266         final BluetoothDevice btDevice = btInfo.getBtDevice();
267         if (btDevice == null) {
268             return;
269         }
270         if (AudioService.DEBUG_DEVICES) {
271             Log.d(TAG, "onBluetoothA2dpActiveDeviceChange btDevice=" + btDevice);
272         }
273         int a2dpVolume = btInfo.getVolume();
274         final int a2dpCodec = btInfo.getCodec();
275
276         String address = btDevice.getAddress();
277         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
278             address = "";
279         }
280         AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
281                 "onBluetoothA2dpActiveDeviceChange addr=" + address
282                     + " event=" + BtHelper.a2dpDeviceEventToString(event)));
283
284         synchronized (mConnectedDevices) {
285             //TODO original CL is not consistent between BluetoothDevice and BluetoothA2dpDeviceInfo
286             // for this type of message
287             if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) {
288                 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
289                         "A2dp config change ignored"));
290                 return;
291             }
292             final String key = DeviceInfo.makeDeviceListKey(
293                     AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
294             final DeviceInfo di = mConnectedDevices.get(key);
295             if (di == null) {
296                 Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpActiveDeviceChange");
297                 return;
298             }
299
300             if (event == BtHelper.EVENT_ACTIVE_DEVICE_CHANGE) {
301                 // Device is connected
302                 if (a2dpVolume != -1) {
303                     mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC,
304                             // convert index to internal representation in VolumeStreamState
305                             a2dpVolume * 10,
306                             AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
307                             "onBluetoothA2dpActiveDeviceChange");
308                 }
309             } else if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
310                 if (di.mDeviceCodecFormat != a2dpCodec) {
311                     di.mDeviceCodecFormat = a2dpCodec;
312                     mConnectedDevices.replace(key, di);
313                 }
314             }
315             if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
316                     BtHelper.getName(btDevice), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) {
317                 int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
318                 // force A2DP device disconnection in case of error so that AudioService state is
319                 // consistent with audio policy manager state
320                 setBluetoothA2dpDeviceConnectionState(
321                         btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
322                         false /* suppressNoisyIntent */, musicDevice,
323                         -1 /* a2dpVolume */);
324             }
325         }
326     }
327
328     /*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
329         synchronized (mConnectedDevices) {
330             makeA2dpDeviceUnavailableNow(address, a2dpCodec);
331         }
332     }
333
334     /*package*/ void onReportNewRoutes() {
335         int n = mRoutesObservers.beginBroadcast();
336         if (n > 0) {
337             AudioRoutesInfo routes;
338             synchronized (mCurAudioRoutes) {
339                 routes = new AudioRoutesInfo(mCurAudioRoutes);
340             }
341             while (n > 0) {
342                 n--;
343                 IAudioRoutesObserver obs = mRoutesObservers.getBroadcastItem(n);
344                 try {
345                     obs.dispatchAudioRoutesChanged(routes);
346                 } catch (RemoteException e) { }
347             }
348         }
349         mRoutesObservers.finishBroadcast();
350         mDeviceBroker.postObserveDevicesForAllStreams();
351     }
352
353     private static final int DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG =
354             AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
355                     | AudioSystem.DEVICE_OUT_LINE | AudioSystem.DEVICE_OUT_ALL_USB;
356
357     /*package*/ void onSetWiredDeviceConnectionState(
358                             AudioDeviceInventory.WiredDeviceConnectionState wdcs) {
359         AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs));
360
361         synchronized (mConnectedDevices) {
362             if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED)
363                     && ((wdcs.mType & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0)) {
364                 mDeviceBroker.setBluetoothA2dpOnInt(true,
365                         "onSetWiredDeviceConnectionState state DISCONNECTED");
366             }
367
368             if (!handleDeviceConnection(wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED,
369                     wdcs.mType, wdcs.mAddress, wdcs.mName)) {
370                 // change of connection state failed, bailout
371                 return;
372             }
373             if (wdcs.mState != AudioService.CONNECTION_STATE_DISCONNECTED) {
374                 if ((wdcs.mType & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0) {
375                     mDeviceBroker.setBluetoothA2dpOnInt(false,
376                             "onSetWiredDeviceConnectionState state not DISCONNECTED");
377                 }
378                 mDeviceBroker.checkMusicActive(wdcs.mType, wdcs.mCaller);
379             }
380             if (wdcs.mType == AudioSystem.DEVICE_OUT_HDMI) {
381                 mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller);
382             }
383             sendDeviceConnectionIntent(wdcs.mType, wdcs.mState, wdcs.mAddress, wdcs.mName);
384             updateAudioRoutes(wdcs.mType, wdcs.mState);
385         }
386     }
387
388     /*package*/ void onToggleHdmi() {
389         synchronized (mConnectedDevices) {
390             // Is HDMI connected?
391             final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, "");
392             final DeviceInfo di = mConnectedDevices.get(key);
393             if (di == null) {
394                 Log.e(TAG, "invalid null DeviceInfo in onToggleHdmi");
395                 return;
396             }
397             // Toggle HDMI to retrigger broadcast with proper formats.
398             setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
399                     AudioSystem.DEVICE_STATE_UNAVAILABLE, "", "",
400                     "android"); // disconnect
401             setWiredDeviceConnectionState(AudioSystem.DEVICE_OUT_HDMI,
402                     AudioSystem.DEVICE_STATE_AVAILABLE, "", "",
403                     "android"); // reconnect
404         }
405     }
406     //------------------------------------------------------------
407     //
408
409     /**
410      * Implements the communication with AudioSystem to (dis)connect a device in the native layers
411      * @param connect true if connection
412      * @param device the device type
413      * @param address the address of the device
414      * @param deviceName human-readable name of device
415      * @return false if an error was reported by AudioSystem
416      */
417     /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
418             String deviceName) {
419         if (AudioService.DEBUG_DEVICES) {
420             Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:"
421                     + Integer.toHexString(device) + " address:" + address
422                     + " name:" + deviceName + ")");
423         }
424         synchronized (mConnectedDevices) {
425             final String deviceKey = DeviceInfo.makeDeviceListKey(device, address);
426             if (AudioService.DEBUG_DEVICES) {
427                 Slog.i(TAG, "deviceKey:" + deviceKey);
428             }
429             DeviceInfo di = mConnectedDevices.get(deviceKey);
430             boolean isConnected = di != null;
431             if (AudioService.DEBUG_DEVICES) {
432                 Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected);
433             }
434             if (connect && !isConnected) {
435                 final int res = AudioSystem.setDeviceConnectionState(device,
436                         AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
437                         AudioSystem.AUDIO_FORMAT_DEFAULT);
438                 if (res != AudioSystem.AUDIO_STATUS_OK) {
439                     Slog.e(TAG, "not connecting device 0x" + Integer.toHexString(device)
440                             + " due to command error " + res);
441                     return false;
442                 }
443                 mConnectedDevices.put(deviceKey, new DeviceInfo(
444                         device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
445                 mDeviceBroker.postAccessoryPlugMediaUnmute(device);
446                 return true;
447             } else if (!connect && isConnected) {
448                 AudioSystem.setDeviceConnectionState(device,
449                         AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName,
450                         AudioSystem.AUDIO_FORMAT_DEFAULT);
451                 // always remove even if disconnection failed
452                 mConnectedDevices.remove(deviceKey);
453                 return true;
454             }
455             Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey
456                     + ", deviceSpec=" + di + ", connect=" + connect);
457         }
458         return false;
459     }
460
461
462     /*package*/ void disconnectA2dp() {
463         synchronized (mConnectedDevices) {
464             final ArraySet<String> toRemove = new ArraySet<>();
465             // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
466             mConnectedDevices.values().forEach(deviceInfo -> {
467                 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP) {
468                     toRemove.add(deviceInfo.mDeviceAddress);
469                 }
470             });
471             if (toRemove.size() > 0) {
472                 final int delay = checkSendBecomingNoisyIntentInt(
473                         AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
474                         AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE);
475                 toRemove.stream().forEach(deviceAddress ->
476                         makeA2dpDeviceUnavailableLater(deviceAddress, delay)
477                 );
478             }
479         }
480     }
481
482     /*package*/ void disconnectA2dpSink() {
483         synchronized (mConnectedDevices) {
484             final ArraySet<String> toRemove = new ArraySet<>();
485             // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices
486             mConnectedDevices.values().forEach(deviceInfo -> {
487                 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_IN_BLUETOOTH_A2DP) {
488                     toRemove.add(deviceInfo.mDeviceAddress);
489                 }
490             });
491             toRemove.stream().forEach(deviceAddress -> makeA2dpSrcUnavailable(deviceAddress));
492         }
493     }
494
495     /*package*/ void disconnectHearingAid() {
496         synchronized (mConnectedDevices) {
497             final ArraySet<String> toRemove = new ArraySet<>();
498             // Disconnect ALL DEVICE_OUT_HEARING_AID devices
499             mConnectedDevices.values().forEach(deviceInfo -> {
500                 if (deviceInfo.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
501                     toRemove.add(deviceInfo.mDeviceAddress);
502                 }
503             });
504             if (toRemove.size() > 0) {
505                 final int delay = checkSendBecomingNoisyIntentInt(
506                         AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE);
507                 toRemove.stream().forEach(deviceAddress ->
508                         // TODO delay not used?
509                         makeHearingAidDeviceUnavailable(deviceAddress /*, delay*/)
510                 );
511             }
512         }
513     }
514
515     // must be called before removing the device from mConnectedDevices
516     // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
517     // from AudioSystem
518     /*package*/ int checkSendBecomingNoisyIntent(int device,
519             @AudioService.ConnectionState int state, int musicDevice) {
520         synchronized (mConnectedDevices) {
521             return checkSendBecomingNoisyIntentInt(device, state, musicDevice);
522         }
523     }
524
525     /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
526         synchronized (mCurAudioRoutes) {
527             AudioRoutesInfo routes = new AudioRoutesInfo(mCurAudioRoutes);
528             mRoutesObservers.register(observer);
529             return routes;
530         }
531     }
532
533     /*package*/ AudioRoutesInfo getCurAudioRoutes() {
534         return mCurAudioRoutes;
535     }
536
537     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
538     /*package*/ void setBluetoothA2dpDeviceConnectionState(
539             @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
540             int profile, boolean suppressNoisyIntent, int musicDevice, int a2dpVolume) {
541         int delay;
542         if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
543             throw new IllegalArgumentException("invalid profile " + profile);
544         }
545         synchronized (mConnectedDevices) {
546             if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) {
547                 int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
548                 delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
549                         intState, musicDevice);
550             } else {
551                 delay = 0;
552             }
553
554             final int a2dpCodec = mDeviceBroker.getA2dpCodec(device);
555
556             if (AudioService.DEBUG_DEVICES) {
557                 Log.i(TAG, "setBluetoothA2dpDeviceConnectionState device: " + device
558                         + " state: " + state + " delay(ms): " + delay + "codec:" + a2dpCodec
559                         + " suppressNoisyIntent: " + suppressNoisyIntent);
560             }
561
562             final BtHelper.BluetoothA2dpDeviceInfo a2dpDeviceInfo =
563                     new BtHelper.BluetoothA2dpDeviceInfo(device, a2dpVolume, a2dpCodec);
564             if (profile == BluetoothProfile.A2DP) {
565                 if (delay == 0) {
566                     onSetA2dpSinkConnectionState(a2dpDeviceInfo, state);
567                 } else {
568                     mDeviceBroker.postA2dpSinkConnection(state,
569                             a2dpDeviceInfo,
570                             delay);
571                 }
572             } else { //profile == BluetoothProfile.A2DP_SINK
573                 mDeviceBroker.postA2dpSourceConnection(state,
574                         a2dpDeviceInfo,
575                         delay);
576             }
577         }
578     }
579
580     /*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
581                                                   String address, String name, String caller) {
582         synchronized (mConnectedDevices) {
583             int delay = checkSendBecomingNoisyIntentInt(type, state, AudioSystem.DEVICE_NONE);
584             mDeviceBroker.postSetWiredDeviceConnectionState(
585                     new WiredDeviceConnectionState(type, state, address, name, caller),
586                     delay);
587             return delay;
588         }
589     }
590
591     /*package*/ int  setBluetoothHearingAidDeviceConnectionState(
592             @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
593             boolean suppressNoisyIntent, int musicDevice) {
594         int delay;
595         synchronized (mConnectedDevices) {
596             if (!suppressNoisyIntent) {
597                 int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0;
598                 delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_HEARING_AID,
599                         intState, musicDevice);
600             } else {
601                 delay = 0;
602             }
603             mDeviceBroker.postSetHearingAidConnectionState(state, device, delay);
604             return delay;
605         }
606     }
607
608
609     //-------------------------------------------------------------------
610     // Internal utilities
611
612     @GuardedBy("mConnectedDevices")
613     private void makeA2dpDeviceAvailable(String address, String name, String eventSource,
614             int a2dpCodec) {
615         // enable A2DP before notifying A2DP connection to avoid unnecessary processing in
616         // audio policy manager
617         mDeviceBroker.setBluetoothA2dpOnInt(true, eventSource);
618         AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
619                 AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec);
620         // Reset A2DP suspend state each time a new sink is connected
621         AudioSystem.setParameters("A2dpSuspended=false");
622         mConnectedDevices.put(
623                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address),
624                 new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
625                         address, a2dpCodec));
626         mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
627         setCurrentAudioRouteNameIfPossible(name);
628     }
629
630     @GuardedBy("mConnectedDevices")
631     private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
632         if (address == null) {
633             return;
634         }
635         mDeviceBroker.setAvrcpAbsoluteVolumeSupported(false);
636         AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
637                 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec);
638         mConnectedDevices.remove(
639                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
640         // Remove A2DP routes as well
641         setCurrentAudioRouteNameIfPossible(null);
642         if (mDockAddress == address) {
643             mDockAddress = null;
644         }
645     }
646
647     @GuardedBy("mConnectedDevices")
648     private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
649         // prevent any activity on the A2DP audio output to avoid unwanted
650         // reconnection of the sink.
651         AudioSystem.setParameters("A2dpSuspended=true");
652         // retrieve DeviceInfo before removing device
653         final String deviceKey =
654                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
655         final DeviceInfo deviceInfo = mConnectedDevices.get(deviceKey);
656         final int a2dpCodec = deviceInfo != null ? deviceInfo.mDeviceCodecFormat :
657                 AudioSystem.AUDIO_FORMAT_DEFAULT;
658         // the device will be made unavailable later, so consider it disconnected right away
659         mConnectedDevices.remove(deviceKey);
660         // send the delayed message to make the device unavailable later
661         mDeviceBroker.setA2dpDockTimeout(address, a2dpCodec, delayMs);
662     }
663
664
665     @GuardedBy("mConnectedDevices")
666     private void makeA2dpSrcAvailable(String address) {
667         AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
668                 AudioSystem.DEVICE_STATE_AVAILABLE, address, "",
669                 AudioSystem.AUDIO_FORMAT_DEFAULT);
670         mConnectedDevices.put(
671                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
672                 new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "",
673                         address, AudioSystem.AUDIO_FORMAT_DEFAULT));
674     }
675
676     @GuardedBy("mConnectedDevices")
677     private void makeA2dpSrcUnavailable(String address) {
678         AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
679                 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
680                 AudioSystem.AUDIO_FORMAT_DEFAULT);
681         mConnectedDevices.remove(
682                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address));
683     }
684
685     @GuardedBy("mConnectedDevices")
686     private void makeHearingAidDeviceAvailable(
687             String address, String name, int streamType, String eventSource) {
688         final int hearingAidVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType,
689                 AudioSystem.DEVICE_OUT_HEARING_AID);
690         mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType);
691
692         AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
693                 AudioSystem.DEVICE_STATE_AVAILABLE, address, name,
694                 AudioSystem.AUDIO_FORMAT_DEFAULT);
695         mConnectedDevices.put(
696                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address),
697                 new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name,
698                         address, AudioSystem.AUDIO_FORMAT_DEFAULT));
699         mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID);
700         mDeviceBroker.postApplyVolumeOnDevice(streamType,
701                 AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
702         setCurrentAudioRouteNameIfPossible(name);
703     }
704
705     @GuardedBy("mConnectedDevices")
706     private void makeHearingAidDeviceUnavailable(String address) {
707         AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
708                 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
709                 AudioSystem.AUDIO_FORMAT_DEFAULT);
710         mConnectedDevices.remove(
711                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address));
712         // Remove Hearing Aid routes as well
713         setCurrentAudioRouteNameIfPossible(null);
714     }
715
716     @GuardedBy("mConnectedDevices")
717     private void setCurrentAudioRouteNameIfPossible(String name) {
718         synchronized (mCurAudioRoutes) {
719             if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) {
720                 return;
721             }
722             if (name != null || !isCurrentDeviceConnected()) {
723                 mCurAudioRoutes.bluetoothName = name;
724                 mDeviceBroker.postReportNewRoutes();
725             }
726         }
727     }
728
729     @GuardedBy("mConnectedDevices")
730     private boolean isCurrentDeviceConnected() {
731         return mConnectedDevices.values().stream().anyMatch(deviceInfo ->
732             TextUtils.equals(deviceInfo.mDeviceName, mCurAudioRoutes.bluetoothName));
733     }
734
735     // Devices which removal triggers intent ACTION_AUDIO_BECOMING_NOISY. The intent is only
736     // sent if:
737     // - none of these devices are connected anymore after one is disconnected AND
738     // - the device being disconnected is actually used for music.
739     // Access synchronized on mConnectedDevices
740     private int mBecomingNoisyIntentDevices =
741             AudioSystem.DEVICE_OUT_WIRED_HEADSET | AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
742                     | AudioSystem.DEVICE_OUT_ALL_A2DP | AudioSystem.DEVICE_OUT_HDMI
743                     | AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET
744                     | AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
745                     | AudioSystem.DEVICE_OUT_ALL_USB | AudioSystem.DEVICE_OUT_LINE
746                     | AudioSystem.DEVICE_OUT_HEARING_AID;
747
748     // must be called before removing the device from mConnectedDevices
749     // musicDevice argument is used when not AudioSystem.DEVICE_NONE instead of querying
750     // from AudioSystem
751     @GuardedBy("mConnectedDevices")
752     private int checkSendBecomingNoisyIntentInt(int device,
753             @AudioService.ConnectionState int state, int musicDevice) {
754         if (state != AudioService.CONNECTION_STATE_DISCONNECTED) {
755             return 0;
756         }
757         if ((device & mBecomingNoisyIntentDevices) == 0) {
758             return 0;
759         }
760         int delay = 0;
761         int devices = 0;
762         for (int i = 0; i < mConnectedDevices.size(); i++) {
763             int dev = mConnectedDevices.valueAt(i).mDeviceType;
764             if (((dev & AudioSystem.DEVICE_BIT_IN) == 0)
765                     && ((dev & mBecomingNoisyIntentDevices) != 0)) {
766                 devices |= dev;
767             }
768         }
769         if (musicDevice == AudioSystem.DEVICE_NONE) {
770             musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
771         }
772
773         // always ignore condition on device being actually used for music when in communication
774         // because music routing is altered in this case.
775         // also checks whether media routing if affected by a dynamic policy or mirroring
776         if (((device == musicDevice) || mDeviceBroker.isInCommunication())
777                 && (device == devices) && !mDeviceBroker.hasMediaDynamicPolicy()
778                         && ((musicDevice & AudioSystem.DEVICE_OUT_REMOTE_SUBMIX) == 0)) {
779             if (!AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0 /*not looking in past*/)
780                     && !mDeviceBroker.hasAudioFocusUsers()) {
781                 // no media playback, not a "becoming noisy" situation, otherwise it could cause
782                 // the pausing of some apps that are playing remotely
783                 AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
784                         "dropping ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
785                 return 0;
786             }
787             mDeviceBroker.postBroadcastBecomingNoisy();
788             delay = 1000;
789         }
790
791         return delay;
792     }
793
794     // Intent "extra" data keys.
795     private static final String CONNECT_INTENT_KEY_PORT_NAME = "portName";
796     private static final String CONNECT_INTENT_KEY_STATE = "state";
797     private static final String CONNECT_INTENT_KEY_ADDRESS = "address";
798     private static final String CONNECT_INTENT_KEY_HAS_PLAYBACK = "hasPlayback";
799     private static final String CONNECT_INTENT_KEY_HAS_CAPTURE = "hasCapture";
800     private static final String CONNECT_INTENT_KEY_HAS_MIDI = "hasMIDI";
801     private static final String CONNECT_INTENT_KEY_DEVICE_CLASS = "class";
802
803     private void sendDeviceConnectionIntent(int device, int state, String address,
804                                             String deviceName) {
805         if (AudioService.DEBUG_DEVICES) {
806             Slog.i(TAG, "sendDeviceConnectionIntent(dev:0x" + Integer.toHexString(device)
807                     + " state:0x" + Integer.toHexString(state) + " address:" + address
808                     + " name:" + deviceName + ");");
809         }
810         Intent intent = new Intent();
811
812         switch(device) {
813             case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
814                 intent.setAction(Intent.ACTION_HEADSET_PLUG);
815                 intent.putExtra("microphone", 1);
816                 break;
817             case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
818             case AudioSystem.DEVICE_OUT_LINE:
819                 intent.setAction(Intent.ACTION_HEADSET_PLUG);
820                 intent.putExtra("microphone", 0);
821                 break;
822             case AudioSystem.DEVICE_OUT_USB_HEADSET:
823                 intent.setAction(Intent.ACTION_HEADSET_PLUG);
824                 intent.putExtra("microphone",
825                         AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_IN_USB_HEADSET, "")
826                                 == AudioSystem.DEVICE_STATE_AVAILABLE ? 1 : 0);
827                 break;
828             case AudioSystem.DEVICE_IN_USB_HEADSET:
829                 if (AudioSystem.getDeviceConnectionState(AudioSystem.DEVICE_OUT_USB_HEADSET, "")
830                         == AudioSystem.DEVICE_STATE_AVAILABLE) {
831                     intent.setAction(Intent.ACTION_HEADSET_PLUG);
832                     intent.putExtra("microphone", 1);
833                 } else {
834                     // do not send ACTION_HEADSET_PLUG when only the input side is seen as changing
835                     return;
836                 }
837                 break;
838             case AudioSystem.DEVICE_OUT_HDMI:
839             case AudioSystem.DEVICE_OUT_HDMI_ARC:
840                 configureHdmiPlugIntent(intent, state);
841                 break;
842         }
843
844         if (intent.getAction() == null) {
845             return;
846         }
847
848         intent.putExtra(CONNECT_INTENT_KEY_STATE, state);
849         intent.putExtra(CONNECT_INTENT_KEY_ADDRESS, address);
850         intent.putExtra(CONNECT_INTENT_KEY_PORT_NAME, deviceName);
851
852         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
853
854         final long ident = Binder.clearCallingIdentity();
855         try {
856             ActivityManager.broadcastStickyIntent(intent, UserHandle.USER_CURRENT);
857         } finally {
858             Binder.restoreCallingIdentity(ident);
859         }
860     }
861
862     private void updateAudioRoutes(int device, int state) {
863         int connType = 0;
864
865         switch (device) {
866             case AudioSystem.DEVICE_OUT_WIRED_HEADSET:
867                 connType = AudioRoutesInfo.MAIN_HEADSET;
868                 break;
869             case AudioSystem.DEVICE_OUT_WIRED_HEADPHONE:
870             case AudioSystem.DEVICE_OUT_LINE:
871                 connType = AudioRoutesInfo.MAIN_HEADPHONES;
872                 break;
873             case AudioSystem.DEVICE_OUT_HDMI:
874             case AudioSystem.DEVICE_OUT_HDMI_ARC:
875                 connType = AudioRoutesInfo.MAIN_HDMI;
876                 break;
877             case AudioSystem.DEVICE_OUT_USB_DEVICE:
878             case AudioSystem.DEVICE_OUT_USB_HEADSET:
879                 connType = AudioRoutesInfo.MAIN_USB;
880                 break;
881         }
882
883         synchronized (mCurAudioRoutes) {
884             if (connType == 0) {
885                 return;
886             }
887             int newConn = mCurAudioRoutes.mainType;
888             if (state != 0) {
889                 newConn |= connType;
890             } else {
891                 newConn &= ~connType;
892             }
893             if (newConn != mCurAudioRoutes.mainType) {
894                 mCurAudioRoutes.mainType = newConn;
895                 mDeviceBroker.postReportNewRoutes();
896             }
897         }
898     }
899
900     private void configureHdmiPlugIntent(Intent intent, @AudioService.ConnectionState int state) {
901         intent.setAction(AudioManager.ACTION_HDMI_AUDIO_PLUG);
902         intent.putExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, state);
903         if (state != AudioService.CONNECTION_STATE_CONNECTED) {
904             return;
905         }
906         ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
907         int[] portGeneration = new int[1];
908         int status = AudioSystem.listAudioPorts(ports, portGeneration);
909         if (status != AudioManager.SUCCESS) {
910             Log.e(TAG, "listAudioPorts error " + status + " in configureHdmiPlugIntent");
911             return;
912         }
913         for (AudioPort port : ports) {
914             if (!(port instanceof AudioDevicePort)) {
915                 continue;
916             }
917             final AudioDevicePort devicePort = (AudioDevicePort) port;
918             if (devicePort.type() != AudioManager.DEVICE_OUT_HDMI
919                     && devicePort.type() != AudioManager.DEVICE_OUT_HDMI_ARC) {
920                 continue;
921             }
922             // found an HDMI port: format the list of supported encodings
923             int[] formats = AudioFormat.filterPublicFormats(devicePort.formats());
924             if (formats.length > 0) {
925                 ArrayList<Integer> encodingList = new ArrayList(1);
926                 for (int format : formats) {
927                     // a format in the list can be 0, skip it
928                     if (format != AudioFormat.ENCODING_INVALID) {
929                         encodingList.add(format);
930                     }
931                 }
932                 final int[] encodingArray = encodingList.stream().mapToInt(i -> i).toArray();
933                 intent.putExtra(AudioManager.EXTRA_ENCODINGS, encodingArray);
934             }
935             // find the maximum supported number of channels
936             int maxChannels = 0;
937             for (int mask : devicePort.channelMasks()) {
938                 int channelCount = AudioFormat.channelCountFromOutChannelMask(mask);
939                 if (channelCount > maxChannels) {
940                     maxChannels = channelCount;
941                 }
942             }
943             intent.putExtra(AudioManager.EXTRA_MAX_CHANNEL_COUNT, maxChannels);
944         }
945     }
946 }