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 mWifiInjector.getWifiMetrics().setVoipMode(mCurrentMode); 185 } 186 mLocalLog.log(log); 187 if (mVerboseLoggingEnabled) { 188 Log.d(TAG, log); 189 } 190 } 191 } 192 startMonitoring()193 private void startMonitoring() { 194 if (mIsMonitoring) { 195 return; 196 } 197 mIsMonitoring = true; 198 if (mAudioManager == null) { 199 mAudioManager = mContext.getSystemService(AudioManager.class); 200 } 201 if (mTelephonyManager == null) { 202 mTelephonyManager = mWifiInjector.makeTelephonyManager(); 203 } 204 if (mWifiCallingStateListener == null) { 205 mWifiCallingStateListener = new WifiCallingStateListener(); 206 } 207 if (mAudioModeListener == null) { 208 mAudioModeListener = new AudioModeListener(); 209 } 210 if (mTelephonyManager != null) { 211 mIsVoWifiOn = mWifiCarrierInfoManager.isWifiCallingAvailable(); 212 mTelephonyManager.registerTelephonyCallback( 213 mHandlerExecutor, mWifiCallingStateListener); 214 } 215 if (mAudioManager != null) { 216 int audioMode = mAudioManager.getMode(); 217 mIsOTTCallOn = audioMode == MODE_IN_COMMUNICATION 218 || audioMode == MODE_COMMUNICATION_REDIRECT; 219 mAudioManager.addOnModeChangedListener(mHandlerExecutor, mAudioModeListener); 220 } 221 executeWifiVoIPOptimization(); 222 } 223 stopMonitoring()224 private void stopMonitoring() { 225 if (!mIsMonitoring) { 226 return; 227 } 228 mIsMonitoring = false; 229 if (mAudioModeListener != null) { 230 mAudioManager.removeOnModeChangedListener(mAudioModeListener); 231 } 232 if (mWifiCallingStateListener != null) { 233 mTelephonyManager.unregisterTelephonyCallback(mWifiCallingStateListener); 234 mWifiCallingStateListener = null; 235 } 236 mIsOTTCallOn = false; 237 mIsVoWifiOn = false; 238 mIsWifiConnected = false; 239 executeWifiVoIPOptimization(); 240 } 241 242 /** 243 * Dump output for debugging. 244 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)245 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 246 pw.println("Dump of WifiVoipDetector:"); 247 mLocalLog.dump(fd, pw, args); 248 pw.println("mIsMonitoring = " + mIsMonitoring); 249 pw.println("mIsOTTCallOn = " + mIsOTTCallOn); 250 pw.println("mIsVoWifiOn = " + mIsVoWifiOn); 251 pw.println("mIsWifiConnected = " + mIsWifiConnected); 252 pw.println("mCurrentMode = " + mCurrentMode); 253 } 254 } 255