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