1 /* 2 * Copyright (C) 2024 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 17 package com.android.server.wifi; 18 19 import static android.media.AudioManager.MODE_COMMUNICATION_REDIRECT; 20 import static android.media.AudioManager.MODE_IN_COMMUNICATION; 21 22 import android.annotation.NonNull; 23 import android.content.Context; 24 import android.media.AudioManager; 25 import android.os.Build; 26 import android.os.Handler; 27 import android.telephony.CallAttributes; 28 import android.telephony.TelephonyCallback; 29 import android.telephony.TelephonyManager; 30 import android.util.LocalLog; 31 import android.util.Log; 32 33 import androidx.annotation.RequiresApi; 34 35 import com.android.internal.annotations.VisibleForTesting; 36 import com.android.modules.utils.HandlerExecutor; 37 import com.android.server.wifi.hal.WifiChip; 38 39 import java.io.FileDescriptor; 40 import java.io.PrintWriter; 41 import java.util.HashMap; 42 import java.util.Map; 43 44 /** 45 * Class used to detect Wi-Fi VoIP call status 46 */ 47 @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) 48 public class WifiVoipDetector { 49 private static final String TAG = "WifiVoipDetector"; 50 51 private final Context mContext; 52 private final Handler mHandler; 53 private final HandlerExecutor mHandlerExecutor; 54 private final WifiInjector mWifiInjector; 55 private final LocalLog mLocalLog; 56 57 private final WifiCarrierInfoManager mWifiCarrierInfoManager; 58 59 private AudioManager mAudioManager; 60 private TelephonyManager mTelephonyManager; 61 62 private WifiCallingStateListener mWifiCallingStateListener; 63 private AudioModeListener mAudioModeListener; 64 65 private int mCurrentMode = WifiChip.WIFI_VOIP_MODE_OFF; 66 private boolean mIsMonitoring = false; 67 private boolean mIsWifiConnected = false; 68 private boolean mIsOTTCallOn = false; 69 private boolean mIsVoWifiOn = false; 70 private boolean mVerboseLoggingEnabled = false; 71 72 private Map<String, Boolean> mConnectedWifiIfaceMap = new HashMap<>(); 73 WifiVoipDetector(@onNull Context context, @NonNull Handler handler, @NonNull WifiInjector wifiInjector, @NonNull WifiCarrierInfoManager wifiCarrierInfoManager)74 public WifiVoipDetector(@NonNull Context context, @NonNull Handler handler, 75 @NonNull WifiInjector wifiInjector, 76 @NonNull WifiCarrierInfoManager wifiCarrierInfoManager) { 77 mContext = context; 78 mHandler = handler; 79 mHandlerExecutor = new HandlerExecutor(mHandler); 80 mWifiInjector = wifiInjector; 81 mWifiCarrierInfoManager = wifiCarrierInfoManager; 82 mLocalLog = new LocalLog(32); 83 } 84 85 /** 86 * Enable verbose logging for WifiConnectivityManager. 87 */ enableVerboseLogging(boolean verbose)88 public void enableVerboseLogging(boolean verbose) { 89 mVerboseLoggingEnabled = verbose; 90 } 91 92 @VisibleForTesting 93 public class WifiCallingStateListener extends TelephonyCallback 94 implements TelephonyCallback.CallAttributesListener { 95 96 @Override onCallAttributesChanged(@onNull CallAttributes callAttributes)97 public void onCallAttributesChanged(@NonNull CallAttributes callAttributes) { 98 boolean isVoWifion = callAttributes.getNetworkType() 99 == TelephonyManager.NETWORK_TYPE_IWLAN; 100 if (isVoWifion == mIsVoWifiOn) { 101 return; 102 } 103 mIsVoWifiOn = isVoWifion; 104 String log = (mIsVoWifiOn ? "Enter" : "Leave") + " IWLAN Call"; 105 mLocalLog.log(log); 106 if (mVerboseLoggingEnabled) { 107 Log.d(TAG, log); 108 } 109 executeWifiVoIPOptimization(); 110 } 111 } 112 113 @VisibleForTesting 114 public class AudioModeListener implements AudioManager.OnModeChangedListener { 115 @Override onModeChanged(int audioMode)116 public void onModeChanged(int audioMode) { 117 boolean isOTTCallOn = audioMode == MODE_IN_COMMUNICATION 118 || audioMode == MODE_COMMUNICATION_REDIRECT; 119 if (isOTTCallOn == mIsOTTCallOn) { 120 return; 121 } 122 mIsOTTCallOn = isOTTCallOn; 123 String log = "Audio mode (" + (mIsOTTCallOn ? "Enter" : "Leave") 124 + " OTT) onModeChanged to " + audioMode; 125 mLocalLog.log(log); 126 if (mVerboseLoggingEnabled) { 127 Log.d(TAG, log); 128 } 129 executeWifiVoIPOptimization(); 130 } 131 } 132 133 /** 134 * Notify wifi is connected or not to start monitoring the VoIP status. 135 * 136 * @param isConnected whether or not wif is connected. 137 * @param isPrimary the connected client mode is primary or not 138 * @param ifaceName the interface name of connected client momde 139 */ notifyWifiConnected(boolean isConnected, boolean isPrimary, String ifaceName)140 public void notifyWifiConnected(boolean isConnected, boolean isPrimary, String ifaceName) { 141 if (isConnected) { 142 mConnectedWifiIfaceMap.put(ifaceName, isPrimary); 143 if (isPrimary) { 144 mIsWifiConnected = true; 145 } 146 } else { 147 Boolean isPrimaryBefore = mConnectedWifiIfaceMap.remove(ifaceName); 148 if (mConnectedWifiIfaceMap.size() > 0) { 149 if (isPrimaryBefore != null && isPrimaryBefore.booleanValue()) { 150 if (isPrimary) { 151 // Primary client mode is disconnected. 152 mIsWifiConnected = false; 153 } else { 154 // Previous primary was changed to secondary && there is another client mode 155 // which will be primary mode. (MBB use case). 156 return; 157 } 158 } 159 } else { 160 // No any client mode is connected. 161 mIsWifiConnected = false; 162 } 163 } 164 if (mIsWifiConnected) { 165 startMonitoring(); 166 } else { 167 stopMonitoring(); 168 } 169 } 170 isWifiVoipOn()171 private boolean isWifiVoipOn() { 172 return (mIsWifiConnected && mIsOTTCallOn) || mIsVoWifiOn; 173 } 174 executeWifiVoIPOptimization()175 private void executeWifiVoIPOptimization() { 176 final boolean wifiVipOn = isWifiVoipOn(); 177 int newMode = wifiVipOn ? WifiChip.WIFI_VOIP_MODE_VOICE : WifiChip.WIFI_VOIP_MODE_OFF; 178 if (mCurrentMode != newMode) { 179 String log = "Update voip over wifi to new mode: " + newMode; 180 if (!mWifiInjector.getWifiNative().setVoipMode(newMode)) { 181 log = "Failed to set Voip Mode to " + newMode + " (maybe not supported?)"; 182 } else { 183 mCurrentMode = newMode; 184 } 185 mLocalLog.log(log); 186 if (mVerboseLoggingEnabled) { 187 Log.d(TAG, log); 188 } 189 } 190 } 191 startMonitoring()192 private void startMonitoring() { 193 if (mIsMonitoring) { 194 return; 195 } 196 mIsMonitoring = true; 197 if (mAudioManager == null) { 198 mAudioManager = mContext.getSystemService(AudioManager.class); 199 } 200 if (mTelephonyManager == null) { 201 mTelephonyManager = mWifiInjector.makeTelephonyManager(); 202 } 203 if (mWifiCallingStateListener == null) { 204 mWifiCallingStateListener = new WifiCallingStateListener(); 205 } 206 if (mAudioModeListener == null) { 207 mAudioModeListener = new AudioModeListener(); 208 } 209 if (mTelephonyManager != null) { 210 mIsVoWifiOn = mWifiCarrierInfoManager.isWifiCallingAvailable(); 211 mTelephonyManager.registerTelephonyCallback( 212 mHandlerExecutor, mWifiCallingStateListener); 213 } 214 if (mAudioManager != null) { 215 int audioMode = mAudioManager.getMode(); 216 mIsOTTCallOn = audioMode == MODE_IN_COMMUNICATION 217 || audioMode == MODE_COMMUNICATION_REDIRECT; 218 mAudioManager.addOnModeChangedListener(mHandlerExecutor, mAudioModeListener); 219 } 220 executeWifiVoIPOptimization(); 221 } 222 stopMonitoring()223 private void stopMonitoring() { 224 if (!mIsMonitoring) { 225 return; 226 } 227 mIsMonitoring = false; 228 if (mAudioModeListener != null) { 229 mAudioManager.removeOnModeChangedListener(mAudioModeListener); 230 } 231 if (mWifiCallingStateListener != null) { 232 mTelephonyManager.unregisterTelephonyCallback(mWifiCallingStateListener); 233 mWifiCallingStateListener = null; 234 } 235 mIsOTTCallOn = false; 236 mIsVoWifiOn = false; 237 mIsWifiConnected = false; 238 executeWifiVoIPOptimization(); 239 } 240 241 /** 242 * Dump output for debugging. 243 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)244 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 245 pw.println("Dump of WifiVoipDetector:"); 246 mLocalLog.dump(fd, pw, args); 247 pw.println("mIsMonitoring = " + mIsMonitoring); 248 pw.println("mIsOTTCallOn = " + mIsOTTCallOn); 249 pw.println("mIsVoWifiOn = " + mIsVoWifiOn); 250 pw.println("mIsWifiConnected = " + mIsWifiConnected); 251 pw.println("mCurrentMode = " + mCurrentMode); 252 } 253 } 254