blob: 27c13ed2b505db56c7a957062fa684f0ae756b39 [file]
/*
* Copyright (C) 2025 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.media.AudioManager;
import android.telecom.Log;
import android.util.IndentingPrintWriter;
import android.util.LocalLog;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
/**
* Tracks the current audio mode from {@link AudioManager} and dispatches changes to
* multiple listeners within the Telecom framework. This class centralizes the listening logic
* so that individual components do not need to register their own listeners with AudioManager.
* <p>
* Note: Initially only used for {@link LocalVoicemailController} but I'm envisioning we can use
* this to also track audio mode changes made by VoIP calls not using Telecom to improve the
* efficacy of {@link CallAudioWatchdog}.
*/
public class AudioModeTracker implements AudioManager.OnModeChangedListener {
private final LocalLog mLocalLog = new LocalLog(10);
/**
* Interface for components that need to be notified of audio mode changes.
*/
public interface AudioModeListener {
/**
* Called when the audio mode has changed.
*
* @param mode The new audio mode. See {@link AudioManager.AudioMode}.
*/
void onAudioModeChanged(int mode);
}
private final TelecomSystem.SyncRoot mLock;
private final List<AudioModeListener> mListeners = new CopyOnWriteArrayList<>();
private int mCurrentAudioMode;
/**
* Constructs a new AudioModeTracker.
*
* @param audioManager The AudioManager instance to listen to.
* @param executor The executor on which to receive listener callbacks.
* @param lock The Telecom sync-root lock.
*/
public AudioModeTracker(AudioManager audioManager, Executor executor,
TelecomSystem.SyncRoot lock) {
mLock = lock;
// Cache the initial audio mode.
mCurrentAudioMode = audioManager.getMode();
// Register this class as the single listener for audio mode changes.
audioManager.addOnModeChangedListener(executor, this);
}
/**
* The callback received from {@link AudioManager} when the audio mode changes.
* This method updates the cached state and notifies all registered internal listeners.
*
* @param mode The new audio mode.
*/
@Override
public void onModeChanged(int mode) {
Log.startSession("AMT.oMC");
try {
synchronized (mLock) {
if (mCurrentAudioMode == mode) {
return; // No change.
}
final int oldMode = mCurrentAudioMode;
mCurrentAudioMode = mode;
String modeChangeLog = String.format("%s -> %s", audioModeToString(oldMode),
audioModeToString(mode));
Log.i(this, "onModeChanged: " + modeChangeLog);
mLocalLog.log(modeChangeLog);
for (AudioModeListener listener : mListeners) {
listener.onAudioModeChanged(mode);
}
}
} finally {
Log.endSession();
}
}
/**
* @return The most recently cached audio mode.
*/
public int getAudioMode() {
synchronized (mLock) {
return mCurrentAudioMode;
}
}
/**
* Registers a listener to receive audio mode change notifications.
*
* @param listener The listener to add.
*/
public void addListener(AudioModeListener listener) {
if (listener != null) {
mListeners.add(listener);
}
}
/**
* Unregisters a listener that was previously added.
*
* @param listener The listener to remove.
*/
public void removeListener(AudioModeListener listener) {
if (listener != null) {
mListeners.remove(listener);
}
}
/**
* Translates an audio mode to a human readable string.
*
* @param mode The audio mode to translate.
* @return The string representation of the audio mode.
*/
public static String audioModeToString(int mode) {
switch (mode) {
case AudioManager.MODE_INVALID:
return "MODE_INVALID";
case AudioManager.MODE_CURRENT:
return "MODE_CURRENT";
case AudioManager.MODE_NORMAL:
return "MODE_NORMAL";
case AudioManager.MODE_RINGTONE:
return "MODE_RINGTONE";
case AudioManager.MODE_IN_CALL:
return "MODE_IN_CALL";
case AudioManager.MODE_IN_COMMUNICATION:
return "MODE_IN_COMMUNICATION";
case AudioManager.MODE_CALL_SCREENING:
return "MODE_CALL_SCREENING";
case AudioManager.MODE_CALL_REDIRECT:
return "MODE_CALL_REDIRECT";
case AudioManager.MODE_COMMUNICATION_REDIRECT:
return "MODE_COMMUNICATION_REDIRECT";
default:
return "MODE_UNKNOWN_" + mode;
}
}
/**
* Dumps the state of the {@link AudioModeTracker}.
*
* @param pw The {@code IndentingPrintWriter} to write the state to.
*/
public void dump(IndentingPrintWriter pw) {
pw.println("Audio Mode History:");
pw.increaseIndent();
mLocalLog.dump(pw);
pw.decreaseIndent();
}
}