/* * 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 com.android.server.telecom; import android.content.Context; import android.media.AudioDeviceCallback; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.telecom.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** Listens for and caches headset state. */ @VisibleForTesting public class WiredHeadsetManager { @VisibleForTesting public interface Listener { void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn); } /** Receiver for wired headset plugged and unplugged events. */ private class WiredHeadsetCallback extends AudioDeviceCallback { @Override public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { Log.startSession("WHC.oADA"); try { updateHeadsetStatus(); } finally { Log.endSession(); } } @Override public void onAudioDevicesRemoved(AudioDeviceInfo[] devices) { Log.startSession("WHC.oADR"); try { updateHeadsetStatus(); } finally { Log.endSession(); } } private void updateHeadsetStatus() { final boolean isPluggedIn = isWiredHeadsetPluggedIn(); Log.i(WiredHeadsetManager.this, "ACTION_HEADSET_PLUG event, plugged in: %b, ", isPluggedIn); onHeadsetPluggedInChanged(isPluggedIn); } } private final AudioManager mAudioManager; private boolean mIsPluggedIn; /** * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is * load factor before resizing, 1 means we only expect a single thread to * access the map so make only a single shard */ private final Set mListeners = Collections.newSetFromMap( new ConcurrentHashMap<>(8, 0.9f, 1)); public WiredHeadsetManager(Context context) { mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mIsPluggedIn = isWiredHeadsetPluggedIn(); mAudioManager.registerAudioDeviceCallback(new WiredHeadsetCallback(), null); } @VisibleForTesting public void addListener(Listener listener) { mListeners.add(listener); } void removeListener(Listener listener) { if (listener != null) { mListeners.remove(listener); } } @VisibleForTesting public boolean isPluggedIn() { return mIsPluggedIn; } private boolean isWiredHeadsetPluggedIn() { AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL); boolean isPluggedIn = false; for (AudioDeviceInfo device : devices) { switch (device.getType()) { case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: case AudioDeviceInfo.TYPE_WIRED_HEADSET: case AudioDeviceInfo.TYPE_USB_HEADSET: case AudioDeviceInfo.TYPE_USB_DEVICE: isPluggedIn = true; } if (isPluggedIn) { break; } } return isPluggedIn; } private void onHeadsetPluggedInChanged(boolean isPluggedIn) { if (mIsPluggedIn != isPluggedIn) { Log.v(this, "onHeadsetPluggedInChanged, mIsPluggedIn: %b -> %b", mIsPluggedIn, isPluggedIn); boolean oldIsPluggedIn = mIsPluggedIn; mIsPluggedIn = isPluggedIn; for (Listener listener : mListeners) { listener.onWiredHeadsetPluggedInChanged(oldIsPluggedIn, mIsPluggedIn); } } } /** * Dumps the state of the {@link WiredHeadsetManager}. * * @param pw The {@code IndentingPrintWriter} to write the state to. */ public void dump(IndentingPrintWriter pw) { pw.println("mIsPluggedIn: " + mIsPluggedIn); } }