16aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal/* 26aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal * Copyright (C) 2014 The Android Open Source Project 36aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal * 46aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal * Licensed under the Apache License, Version 2.0 (the "License"); 56aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal * you may not use this file except in compliance with the License. 66aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal * You may obtain a copy of the License at 76aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal * 86aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal * http://www.apache.org/licenses/LICENSE-2.0 96aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal * 106aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal * Unless required by applicable law or agreed to in writing, software 116aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal * distributed under the License is distributed on an "AS IS" BASIS, 126aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 136aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal * See the License for the specific language governing permissions and 146aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal * limitations under the License. 156aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal */ 166aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal 177cc70b4f0ad1064a4a0dce6056ad82b205887160Tyler Gunnpackage com.android.server.telecom; 186aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal 196aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepalimport android.content.Context; 20ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liuimport android.media.AudioDeviceCallback; 21ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liuimport android.media.AudioDeviceInfo; 226aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepalimport android.media.AudioManager; 23a3eccfee788c3ac3c831a443b085b141b39bb63dBrad Ebingerimport android.telecom.Log; 246aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal 25f62630a57de0d52be2bdbc92a9bf8f305cc0892dHall Liuimport com.android.internal.annotations.VisibleForTesting; 269787e0e80d8960cf8b0ca74c7cdc4c4aac97187aTyler Gunnimport com.android.internal.util.IndentingPrintWriter; 279787e0e80d8960cf8b0ca74c7cdc4c4aac97187aTyler Gunn 28a82c8f794a0a1a9eaa1329a6361abe28043d139aJay Shraunerimport java.util.Collections; 29a82c8f794a0a1a9eaa1329a6361abe28043d139aJay Shraunerimport java.util.Set; 30a82c8f794a0a1a9eaa1329a6361abe28043d139aJay Shraunerimport java.util.concurrent.ConcurrentHashMap; 31b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal 32b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal/** Listens for and caches headset state. */ 33f62630a57de0d52be2bdbc92a9bf8f305cc0892dHall Liu@VisibleForTesting 34f62630a57de0d52be2bdbc92a9bf8f305cc0892dHall Liupublic class WiredHeadsetManager { 35f62630a57de0d52be2bdbc92a9bf8f305cc0892dHall Liu @VisibleForTesting 36f62630a57de0d52be2bdbc92a9bf8f305cc0892dHall Liu public interface Listener { 37b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn); 38b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal } 39b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal 406aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal /** Receiver for wired headset plugged and unplugged events. */ 41ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liu private class WiredHeadsetCallback extends AudioDeviceCallback { 42ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liu @Override 43ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liu public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { 44ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liu Log.startSession("WHC.oADA"); 45ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liu try { 46ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liu updateHeadsetStatus(); 47ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liu } finally { 48ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liu Log.endSession(); 49ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liu } 50ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liu } 51ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liu 526aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal @Override 53ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liu public void onAudioDevicesRemoved(AudioDeviceInfo[] devices) { 54ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liu Log.startSession("WHC.oADR"); 5558f365891ab631d8c45ad69d7024519384e9b75aBrad Ebinger try { 56ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liu updateHeadsetStatus(); 5758f365891ab631d8c45ad69d7024519384e9b75aBrad Ebinger } finally { 5858f365891ab631d8c45ad69d7024519384e9b75aBrad Ebinger Log.endSession(); 596aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal } 606aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal } 61ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liu 62ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liu private void updateHeadsetStatus() { 6322c2ce9cfe71b1d905b9af3bc28513a8eba180c4David Lin final boolean isPluggedIn = isWiredHeadsetPluggedIn(); 64ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liu 6531ed9becf01386eb721ffc5803d5a8a6c64cd4cfHall Liu Log.i(WiredHeadsetManager.this, "ACTION_HEADSET_PLUG event, plugged in: %b, ", 6631ed9becf01386eb721ffc5803d5a8a6c64cd4cfHall Liu isPluggedIn); 67ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liu onHeadsetPluggedInChanged(isPluggedIn); 68ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liu } 696aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal } 706aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal 71c395e49e7b9aef59e5b4092945a8648f5adba3a0Santos Cordon private final AudioManager mAudioManager; 726aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal private boolean mIsPluggedIn; 73a82c8f794a0a1a9eaa1329a6361abe28043d139aJay Shrauner /** 74a82c8f794a0a1a9eaa1329a6361abe28043d139aJay Shrauner * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is 75a82c8f794a0a1a9eaa1329a6361abe28043d139aJay Shrauner * load factor before resizing, 1 means we only expect a single thread to 76a82c8f794a0a1a9eaa1329a6361abe28043d139aJay Shrauner * access the map so make only a single shard 77a82c8f794a0a1a9eaa1329a6361abe28043d139aJay Shrauner */ 78a82c8f794a0a1a9eaa1329a6361abe28043d139aJay Shrauner private final Set<Listener> mListeners = Collections.newSetFromMap( 79ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liu new ConcurrentHashMap<>(8, 0.9f, 1)); 806aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal 81f62630a57de0d52be2bdbc92a9bf8f305cc0892dHall Liu public WiredHeadsetManager(Context context) { 82c395e49e7b9aef59e5b4092945a8648f5adba3a0Santos Cordon mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 8322c2ce9cfe71b1d905b9af3bc28513a8eba180c4David Lin mIsPluggedIn = isWiredHeadsetPluggedIn(); 846aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal 85ca9988ba4e8aed7d04835eca57b62f397c1a08f4Hall Liu mAudioManager.registerAudioDeviceCallback(new WiredHeadsetCallback(), null); 866aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal } 876aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal 88f62630a57de0d52be2bdbc92a9bf8f305cc0892dHall Liu @VisibleForTesting 89f62630a57de0d52be2bdbc92a9bf8f305cc0892dHall Liu public void addListener(Listener listener) { 90b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal mListeners.add(listener); 91b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal } 92b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal 93b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal void removeListener(Listener listener) { 94a82c8f794a0a1a9eaa1329a6361abe28043d139aJay Shrauner if (listener != null) { 95a82c8f794a0a1a9eaa1329a6361abe28043d139aJay Shrauner mListeners.remove(listener); 96a82c8f794a0a1a9eaa1329a6361abe28043d139aJay Shrauner } 97b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal } 98b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal 99f62630a57de0d52be2bdbc92a9bf8f305cc0892dHall Liu @VisibleForTesting 100f62630a57de0d52be2bdbc92a9bf8f305cc0892dHall Liu public boolean isPluggedIn() { 1016aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal return mIsPluggedIn; 1026aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal } 1036aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal 10422c2ce9cfe71b1d905b9af3bc28513a8eba180c4David Lin private boolean isWiredHeadsetPluggedIn() { 10522c2ce9cfe71b1d905b9af3bc28513a8eba180c4David Lin AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL); 10622c2ce9cfe71b1d905b9af3bc28513a8eba180c4David Lin boolean isPluggedIn = false; 10722c2ce9cfe71b1d905b9af3bc28513a8eba180c4David Lin for (AudioDeviceInfo device : devices) { 10822c2ce9cfe71b1d905b9af3bc28513a8eba180c4David Lin switch (device.getType()) { 10922c2ce9cfe71b1d905b9af3bc28513a8eba180c4David Lin case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: 11022c2ce9cfe71b1d905b9af3bc28513a8eba180c4David Lin case AudioDeviceInfo.TYPE_WIRED_HEADSET: 11122c2ce9cfe71b1d905b9af3bc28513a8eba180c4David Lin case AudioDeviceInfo.TYPE_USB_HEADSET: 11222c2ce9cfe71b1d905b9af3bc28513a8eba180c4David Lin case AudioDeviceInfo.TYPE_USB_DEVICE: 11322c2ce9cfe71b1d905b9af3bc28513a8eba180c4David Lin isPluggedIn = true; 11422c2ce9cfe71b1d905b9af3bc28513a8eba180c4David Lin } 11522c2ce9cfe71b1d905b9af3bc28513a8eba180c4David Lin if (isPluggedIn) { 11622c2ce9cfe71b1d905b9af3bc28513a8eba180c4David Lin break; 11722c2ce9cfe71b1d905b9af3bc28513a8eba180c4David Lin } 11822c2ce9cfe71b1d905b9af3bc28513a8eba180c4David Lin } 11922c2ce9cfe71b1d905b9af3bc28513a8eba180c4David Lin return isPluggedIn; 12022c2ce9cfe71b1d905b9af3bc28513a8eba180c4David Lin } 12122c2ce9cfe71b1d905b9af3bc28513a8eba180c4David Lin 1226aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal private void onHeadsetPluggedInChanged(boolean isPluggedIn) { 1236aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal if (mIsPluggedIn != isPluggedIn) { 1246aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal Log.v(this, "onHeadsetPluggedInChanged, mIsPluggedIn: %b -> %b", mIsPluggedIn, 1256aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal isPluggedIn); 1266aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal boolean oldIsPluggedIn = mIsPluggedIn; 1276aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal mIsPluggedIn = isPluggedIn; 128b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal for (Listener listener : mListeners) { 129b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal listener.onWiredHeadsetPluggedInChanged(oldIsPluggedIn, mIsPluggedIn); 130b88795aadc636d14156791c2fc93af051d7e0d49Sailesh Nepal } 1316aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal } 1326aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal } 1339787e0e80d8960cf8b0ca74c7cdc4c4aac97187aTyler Gunn 1349787e0e80d8960cf8b0ca74c7cdc4c4aac97187aTyler Gunn /** 1359787e0e80d8960cf8b0ca74c7cdc4c4aac97187aTyler Gunn * Dumps the state of the {@link WiredHeadsetManager}. 1369787e0e80d8960cf8b0ca74c7cdc4c4aac97187aTyler Gunn * 1379787e0e80d8960cf8b0ca74c7cdc4c4aac97187aTyler Gunn * @param pw The {@code IndentingPrintWriter} to write the state to. 1389787e0e80d8960cf8b0ca74c7cdc4c4aac97187aTyler Gunn */ 1399787e0e80d8960cf8b0ca74c7cdc4c4aac97187aTyler Gunn public void dump(IndentingPrintWriter pw) { 1409787e0e80d8960cf8b0ca74c7cdc4c4aac97187aTyler Gunn pw.println("mIsPluggedIn: " + mIsPluggedIn); 1419787e0e80d8960cf8b0ca74c7cdc4c4aac97187aTyler Gunn } 1426aca10a0efa2771ccdef5920f4276f0db4a7ee1fSailesh Nepal} 143