• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 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.google.android.iwlan;
18 
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.database.ContentObserver;
23 import android.net.Uri;
24 import android.net.wifi.WifiInfo;
25 import android.net.wifi.WifiManager;
26 import android.os.Handler;
27 import android.os.HandlerThread;
28 import android.os.Looper;
29 import android.support.annotation.IntDef;
30 import android.support.annotation.NonNull;
31 import android.telephony.CellInfo;
32 import android.telephony.SubscriptionManager;
33 import android.telephony.TelephonyCallback;
34 import android.telephony.TelephonyManager;
35 import android.telephony.ims.ImsManager;
36 import android.telephony.ims.ImsMmTelManager;
37 import android.util.Log;
38 import android.util.SparseArray;
39 
40 import com.android.internal.annotations.VisibleForTesting;
41 
42 import com.google.android.iwlan.flags.FeatureFlags;
43 import com.google.android.iwlan.flags.FeatureFlagsImpl;
44 
45 import java.util.HashSet;
46 import java.util.List;
47 import java.util.Map;
48 import java.util.Objects;
49 import java.util.Set;
50 import java.util.concurrent.ConcurrentHashMap;
51 
52 public class IwlanEventListener {
53 
54     public static final int UNKNOWN_EVENT = -1;
55 
56     /** On {@link IwlanCarrierConfigChangeListener#onCarrierConfigChanged} is called. */
57     public static final int CARRIER_CONFIG_CHANGED_EVENT = 1;
58 
59     /** Wifi turned off or disabled. */
60     public static final int WIFI_DISABLE_EVENT = 2;
61 
62     /** Airplane mode turned off or disabled. */
63     public static final int APM_DISABLE_EVENT = 3;
64 
65     /** Airplane mode turned on or enabled */
66     public static final int APM_ENABLE_EVENT = 4;
67 
68     /** Wifi AccessPoint changed. */
69     public static final int WIFI_AP_CHANGED_EVENT = 5;
70 
71     /** Wifi calling turned on or enabled */
72     public static final int WIFI_CALLING_ENABLE_EVENT = 6;
73 
74     /** Wifi calling turned off or disabled */
75     public static final int WIFI_CALLING_DISABLE_EVENT = 7;
76 
77     /** Cross sim calling enabled */
78     public static final int CROSS_SIM_CALLING_ENABLE_EVENT = 8;
79 
80     /** Cross sim calling disabled */
81     public static final int CROSS_SIM_CALLING_DISABLE_EVENT = 9;
82 
83     /**
84      * On {@link IwlanCarrierConfigChangeListener#onCarrierConfigChanged} is called with
85      * UNKNOWN_CARRIER_ID.
86      */
87     public static final int CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT = 10;
88 
89     /** On Cellinfo changed */
90     public static final int CELLINFO_CHANGED_EVENT = 11;
91 
92     /** On Call state changed */
93     public static final int CALL_STATE_CHANGED_EVENT = 12;
94 
95     /** On Preferred Network Type changed */
96     public static final int PREFERRED_NETWORK_TYPE_CHANGED_EVENT = 13;
97 
98     public static final int SCREEN_ON_EVENT = 14;
99 
100     /* Events used and handled by IwlanDataService internally */
101     public static final int DATA_SERVICE_INTERNAL_EVENT_BASE = 100;
102 
103     /* Events used and handled by IwlanNetworkService internally */
104     public static final int NETWORK_SERVICE_INTERNAL_EVENT_BASE = 200;
105 
106     @IntDef({
107         CARRIER_CONFIG_CHANGED_EVENT,
108         WIFI_DISABLE_EVENT,
109         APM_DISABLE_EVENT,
110         APM_ENABLE_EVENT,
111         WIFI_AP_CHANGED_EVENT,
112         WIFI_CALLING_ENABLE_EVENT,
113         WIFI_CALLING_DISABLE_EVENT,
114         CROSS_SIM_CALLING_ENABLE_EVENT,
115         CROSS_SIM_CALLING_DISABLE_EVENT,
116         CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT,
117         CELLINFO_CHANGED_EVENT,
118         CALL_STATE_CHANGED_EVENT,
119         PREFERRED_NETWORK_TYPE_CHANGED_EVENT,
120         SCREEN_ON_EVENT,
121     })
122     @interface IwlanEventType {}
123 
124     private static final String LOG_TAG = IwlanEventListener.class.getSimpleName();
125 
126     private final String SUB_TAG;
127 
128     private static Boolean sIsAirplaneModeOn;
129 
130     private static String sWifiSSID = "";
131 
132     private static final Map<Integer, IwlanEventListener> mInstances = new ConcurrentHashMap<>();
133 
134     private final Context mContext;
135     private final int mSlotId;
136     private int mSubId;
137     private Uri mCrossSimCallingUri;
138     private Uri mWfcEnabledUri;
139     private UserSettingContentObserver mUserSettingContentObserver;
140     private RadioInfoTelephonyCallback mTelephonyCallback;
141 
142     SparseArray<Set<Handler>> eventHandlers = new SparseArray<>();
143 
144     private class UserSettingContentObserver extends ContentObserver {
UserSettingContentObserver(Handler h)145         UserSettingContentObserver(Handler h) {
146             super(h);
147         }
148 
149         @Override
onChange(boolean selfChange, Uri uri)150         public void onChange(boolean selfChange, Uri uri) {
151             Objects.requireNonNull(mCrossSimCallingUri, "CrossSimCallingUri must not be null");
152             Objects.requireNonNull(mWfcEnabledUri, "WfcEnabledUri must not be null");
153             if (mCrossSimCallingUri.equals(uri)) {
154                 notifyCurrentSetting(uri);
155             } else if (mWfcEnabledUri.equals(uri)) {
156                 notifyCurrentSetting(uri);
157             }
158         }
159     }
160 
161     private class RadioInfoTelephonyCallback extends TelephonyCallback
162             implements TelephonyCallback.CellInfoListener,
163                     TelephonyCallback.CallStateListener,
164                     TelephonyCallback.AllowedNetworkTypesListener {
165         @Override
onCellInfoChanged(List<CellInfo> arrayCi)166         public void onCellInfoChanged(List<CellInfo> arrayCi) {
167             Log.d(LOG_TAG, "Cellinfo changed");
168 
169             for (Map.Entry<Integer, IwlanEventListener> entry : mInstances.entrySet()) {
170                 IwlanEventListener instance = entry.getValue();
171                 if (instance != null) {
172                     instance.updateHandlers(arrayCi);
173                 }
174             }
175         }
176 
177         @Override
onCallStateChanged(int state)178         public void onCallStateChanged(int state) {
179             Log.d(
180                     LOG_TAG,
181                     "Call state changed to " + callStateToString(state) + " for slot " + mSlotId);
182 
183             IwlanEventListener instance = mInstances.get(mSlotId);
184             if (instance != null) {
185                 instance.updateHandlers(CALL_STATE_CHANGED_EVENT, state);
186             }
187         }
188 
189         @Override
onAllowedNetworkTypesChanged( @elephonyManager.AllowedNetworkTypesReason int reason, @TelephonyManager.NetworkTypeBitMask long allowedNetworkType)190         public void onAllowedNetworkTypesChanged(
191                 @TelephonyManager.AllowedNetworkTypesReason int reason,
192                 @TelephonyManager.NetworkTypeBitMask long allowedNetworkType) {
193             if (reason != TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER) {
194                 return;
195             }
196 
197             IwlanEventListener instance = mInstances.get(mSlotId);
198             if (instance != null) {
199                 instance.updateHandlers(PREFERRED_NETWORK_TYPE_CHANGED_EVENT, allowedNetworkType);
200             }
201         }
202     }
203 
204     /** Returns IwlanEventListener instance */
getInstance(@onNull Context context, int slotId)205     public static IwlanEventListener getInstance(@NonNull Context context, int slotId) {
206         return mInstances.computeIfAbsent(
207                 slotId, k -> new IwlanEventListener(context, slotId, new FeatureFlagsImpl()));
208     }
209 
210     @VisibleForTesting
resetAllInstances()211     public static void resetAllInstances() {
212         mInstances.clear();
213     }
214 
215     /**
216      * Adds handler for the list of events.
217      *
218      * @param events lists of events for which the handler needs to be notified.
219      * @param handler handler to be called when the events happen
220      */
addEventListener(List<Integer> events, Handler handler)221     public synchronized void addEventListener(List<Integer> events, Handler handler) {
222         for (@IwlanEventType int event : events) {
223             if (eventHandlers.contains(event)) {
224                 eventHandlers.get(event).add(handler);
225             } else {
226                 Set<Handler> handlers = new HashSet<>();
227                 handlers.add(handler);
228                 eventHandlers.append(event, handlers);
229             }
230         }
231     }
232 
233     /**
234      * Removes handler for the list of events.
235      *
236      * @param events lists of events for which the handler needs to be removed.
237      * @param handler handler to be removed
238      */
removeEventListener(List<Integer> events, Handler handler)239     public synchronized void removeEventListener(List<Integer> events, Handler handler) {
240         for (int event : events) {
241             if (eventHandlers.contains(event)) {
242                 Set<Handler> handlers = eventHandlers.get(event);
243                 handlers.remove(handler);
244                 if (handlers.isEmpty()) {
245                     eventHandlers.delete(event);
246                 }
247             }
248         }
249         if (eventHandlers.size() == 0) {
250             mInstances.remove(mSlotId, this);
251         }
252     }
253 
254     /**
255      * Removes handler for all events it is registered
256      *
257      * @param handler handler to be removed
258      */
removeEventListener(Handler handler)259     public synchronized void removeEventListener(Handler handler) {
260         for (int i = 0; i < eventHandlers.size(); i++) {
261             Set<Handler> handlers = eventHandlers.valueAt(i);
262             handlers.remove(handler);
263             if (handlers.isEmpty()) {
264                 eventHandlers.delete(eventHandlers.keyAt(i));
265                 i--;
266             }
267         }
268         if (eventHandlers.size() == 0) {
269             mInstances.remove(mSlotId, this);
270         }
271     }
272 
273     /**
274      * Report a Broadcast received. Mainly used by IwlanBroadcastReceiver to report the following
275      * broadcasts: ACTION_AIRPLANE_MODE_CHANGED, WIFI_STATE_CHANGED_ACTION
276      *
277      * @param intent intent
278      */
onBroadcastReceived(Intent intent)279     public static synchronized void onBroadcastReceived(Intent intent) {
280         int event = UNKNOWN_EVENT;
281         switch (intent.getAction()) {
282             case Intent.ACTION_AIRPLANE_MODE_CHANGED:
283                 Boolean isAirplaneModeOn = intent.getBooleanExtra("state", false);
284                 if (sIsAirplaneModeOn != null && sIsAirplaneModeOn.equals(isAirplaneModeOn)) {
285                     // no change in apm state
286                     break;
287                 }
288                 sIsAirplaneModeOn = isAirplaneModeOn;
289                 event = sIsAirplaneModeOn ? APM_ENABLE_EVENT : APM_DISABLE_EVENT;
290                 for (Map.Entry<Integer, IwlanEventListener> entry : mInstances.entrySet()) {
291                     IwlanEventListener instance = entry.getValue();
292                     instance.updateHandlers(event);
293                 }
294                 break;
295             case WifiManager.WIFI_STATE_CHANGED_ACTION:
296                 int wifiState =
297                         intent.getIntExtra(
298                                 WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN);
299                 if (wifiState == WifiManager.WIFI_STATE_DISABLED) {
300                     event = WIFI_DISABLE_EVENT;
301                     for (Map.Entry<Integer, IwlanEventListener> entry : mInstances.entrySet()) {
302                         IwlanEventListener instance = entry.getValue();
303                         instance.updateHandlers(event);
304                     }
305                 }
306                 break;
307             case Intent.ACTION_SCREEN_ON:
308                 mInstances.values().forEach(instance -> instance.updateHandlers(SCREEN_ON_EVENT));
309                 break;
310         }
311     }
312 
313     /**
314      * Broadcast WIFI_AP_CHANGED_EVENT if Wifi SSID changed after Wifi connected.
315      *
316      * @param wifiInfo connected Wifi network's information.
317      */
onWifiConnected(WifiInfo wifiInfo)318     public static void onWifiConnected(WifiInfo wifiInfo) {
319         if (wifiInfo == null) {
320             Log.e(LOG_TAG, "wifiInfo is null");
321             return;
322         }
323         String wifiSSID = wifiInfo.getSSID();
324         if (wifiSSID.equals(WifiManager.UNKNOWN_SSID)) {
325             Log.e(LOG_TAG, "Could not get Wifi SSID");
326             return;
327         }
328 
329         // Check sWifiSSID is greater than 0 to avoid trigger event after device first camps on
330         // Wifi.
331         if (!sWifiSSID.isEmpty() && !sWifiSSID.equals(wifiSSID)) {
332             Log.d(LOG_TAG, "Wifi SSID changed");
333             for (Map.Entry<Integer, IwlanEventListener> entry : mInstances.entrySet()) {
334                 IwlanEventListener instance = entry.getValue();
335                 if (instance != null) {
336                     instance.updateHandlers(WIFI_AP_CHANGED_EVENT);
337                 }
338             }
339         }
340         sWifiSSID = wifiSSID;
341     }
342 
343     /**
344      * Report Carrier Config changed. Mainly used by IwlanCarrierConfigChangeListener.
345      *
346      * @param context context
347      * @param slotId slot id which carrier config is changed
348      * @param subId sub id which carrier config is changed
349      * @param carrierId carrier id
350      */
onCarrierConfigChanged( Context context, int slotId, int subId, int carrierId)351     public static synchronized void onCarrierConfigChanged(
352             Context context, int slotId, int subId, int carrierId) {
353         getInstance(context, slotId).onCarrierConfigChanged(subId, carrierId);
354     }
355 
356     /**
357      * Returns the Event id of the String. String that matches the name of the event
358      *
359      * @param event String form of the event.
360      */
getUnthrottlingEvent(String event)361     public static int getUnthrottlingEvent(String event) {
362         return switch (event) {
363             case "CARRIER_CONFIG_CHANGED_EVENT" -> CARRIER_CONFIG_CHANGED_EVENT;
364             case "WIFI_DISABLE_EVENT" -> WIFI_DISABLE_EVENT;
365             case "APM_DISABLE_EVENT" -> APM_DISABLE_EVENT;
366             case "APM_ENABLE_EVENT" -> APM_ENABLE_EVENT;
367             case "WIFI_AP_CHANGED_EVENT" -> WIFI_AP_CHANGED_EVENT;
368             case "WIFI_CALLING_ENABLE_EVENT" -> WIFI_CALLING_ENABLE_EVENT;
369             case "WIFI_CALLING_DISABLE_EVENT" -> WIFI_CALLING_DISABLE_EVENT;
370             case "CROSS_SIM_CALLING_ENABLE_EVENT" -> CROSS_SIM_CALLING_ENABLE_EVENT;
371             case "CROSS_SIM_CALLING_DISABLE_EVENT" -> CROSS_SIM_CALLING_DISABLE_EVENT;
372             case "CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT" -> CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT;
373             case "CELLINFO_CHANGED_EVENT" -> CELLINFO_CHANGED_EVENT;
374             case "PREFERRED_NETWORK_TYPE_CHANGED_EVENT" -> PREFERRED_NETWORK_TYPE_CHANGED_EVENT;
375             default -> UNKNOWN_EVENT;
376         };
377     }
378 
IwlanEventListener(Context context, int slotId, FeatureFlags featureFlags)379     IwlanEventListener(Context context, int slotId, FeatureFlags featureFlags) {
380         mContext = context;
381         mSlotId = slotId;
382         mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
383         SUB_TAG = IwlanEventListener.class.getSimpleName() + "[" + slotId + "]";
384     }
385 
onCarrierConfigChanged(int subId, int carrierId)386     private void onCarrierConfigChanged(int subId, int carrierId) {
387         Log.d(SUB_TAG, "onCarrierConfigChanged");
388         if (subId != mSubId) {
389             unregisterContentObserver();
390             mSubId = subId;
391             registerContentObserver();
392         }
393         notifyCurrentSetting(mCrossSimCallingUri);
394         notifyCurrentSetting(mWfcEnabledUri);
395 
396         int event;
397         if (carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
398             event = CARRIER_CONFIG_CHANGED_EVENT;
399             registerTelephonyCallback();
400         } else {
401             event = CARRIER_CONFIG_UNKNOWN_CARRIER_EVENT;
402         }
403         updateHandlers(event);
404     }
405 
406     /** Unregister ContentObserver. */
unregisterContentObserver()407     void unregisterContentObserver() {
408         if (mUserSettingContentObserver != null) {
409             mContext.getContentResolver().unregisterContentObserver(mUserSettingContentObserver);
410         }
411         mCrossSimCallingUri = null;
412         mWfcEnabledUri = null;
413     }
414 
415     /** Initiate ContentObserver if it is not created. And, register it with the current sub id. */
registerContentObserver()416     private void registerContentObserver() {
417         if (mUserSettingContentObserver == null) {
418             HandlerThread userSettingHandlerThread =
419                     new HandlerThread(IwlanNetworkService.class.getSimpleName());
420             userSettingHandlerThread.start();
421             Looper looper = userSettingHandlerThread.getLooper();
422             Handler handler = new Handler(looper);
423             mUserSettingContentObserver = new UserSettingContentObserver(handler);
424         }
425 
426         if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
427             return;
428         }
429 
430         ContentResolver resolver = mContext.getContentResolver();
431         // Register for CrossSimCalling setting uri
432         mCrossSimCallingUri =
433                 Uri.withAppendedPath(
434                         SubscriptionManager.CROSS_SIM_ENABLED_CONTENT_URI, String.valueOf(mSubId));
435         resolver.registerContentObserver(mCrossSimCallingUri, true, mUserSettingContentObserver);
436 
437         // Register for WifiCalling setting uri
438         mWfcEnabledUri =
439                 Uri.withAppendedPath(
440                         SubscriptionManager.WFC_ENABLED_CONTENT_URI, String.valueOf(mSubId));
441         resolver.registerContentObserver(mWfcEnabledUri, true, mUserSettingContentObserver);
442     }
443 
444     @VisibleForTesting
notifyCurrentSetting(Uri uri)445     void notifyCurrentSetting(Uri uri) {
446         if (uri == null) {
447             return;
448         }
449         String uriString = uri.getPath();
450         int subIndex = Integer.parseInt(uriString.substring(uriString.lastIndexOf('/') + 1));
451         int slotIndex = SubscriptionManager.getSlotIndex(subIndex);
452 
453         if (slotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
454             Log.e(SUB_TAG, "Invalid slot index: " + slotIndex);
455             return;
456         }
457 
458         if (uri.equals(mCrossSimCallingUri)) {
459             boolean isCstEnabled = IwlanHelper.isCrossSimCallingEnabled(mContext, slotIndex);
460             int event =
461                     (isCstEnabled)
462                             ? CROSS_SIM_CALLING_ENABLE_EVENT
463                             : CROSS_SIM_CALLING_DISABLE_EVENT;
464             getInstance(mContext, slotIndex).updateHandlers(event);
465         } else if (uri.equals(mWfcEnabledUri)) {
466             ImsManager imsManager = mContext.getSystemService(ImsManager.class);
467             if (imsManager == null) {
468                 Log.e(SUB_TAG, "Could not find  ImsManager");
469                 return;
470             }
471             ImsMmTelManager imsMmTelManager = imsManager.getImsMmTelManager(subIndex);
472             if (imsMmTelManager == null) {
473                 Log.e(SUB_TAG, "Could not find  ImsMmTelManager");
474                 return;
475             }
476             boolean wfcEnabled = false;
477             try {
478                 wfcEnabled = imsMmTelManager.isVoWiFiSettingEnabled();
479             } catch (IllegalArgumentException e) {
480                 Log.w(SUB_TAG, e.getMessage());
481             }
482             int event = (wfcEnabled) ? WIFI_CALLING_ENABLE_EVENT : WIFI_CALLING_DISABLE_EVENT;
483             getInstance(mContext, slotIndex).updateHandlers(event);
484         } else {
485             Log.e(SUB_TAG, "Unknown Uri : " + uri);
486         }
487     }
488 
489     @VisibleForTesting
registerTelephonyCallback()490     void registerTelephonyCallback() {
491         Log.d(SUB_TAG, "registerTelephonyCallback");
492         TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
493         telephonyManager =
494                 Objects.requireNonNull(telephonyManager)
495                         .createForSubscriptionId(IwlanHelper.getSubId(mContext, mSlotId));
496         mTelephonyCallback = new RadioInfoTelephonyCallback();
497         telephonyManager.registerTelephonyCallback(Runnable::run, mTelephonyCallback);
498     }
499 
500     @VisibleForTesting
setCrossSimCallingUri(Uri uri)501     void setCrossSimCallingUri(Uri uri) {
502         mCrossSimCallingUri = uri;
503     }
504 
505     @VisibleForTesting
setWfcEnabledUri(Uri uri)506     void setWfcEnabledUri(Uri uri) {
507         mWfcEnabledUri = uri;
508     }
509 
510     @VisibleForTesting
getTelephonyCallback()511     RadioInfoTelephonyCallback getTelephonyCallback() {
512         return mTelephonyCallback;
513     }
514 
updateHandlers(int event)515     private synchronized void updateHandlers(int event) {
516         if (eventHandlers.contains(event)) {
517             Log.d(SUB_TAG, "Updating handlers for the event: " + event);
518             for (Handler handler : eventHandlers.get(event)) {
519                 handler.obtainMessage(event, mSlotId, 0 /* unused */).sendToTarget();
520             }
521         }
522     }
523 
updateHandlers(List<CellInfo> arrayCi)524     private synchronized void updateHandlers(List<CellInfo> arrayCi) {
525         int event = IwlanEventListener.CELLINFO_CHANGED_EVENT;
526         if (eventHandlers.contains(event)) {
527             Log.d(SUB_TAG, "Updating handlers for the event: " + event);
528             for (Handler handler : eventHandlers.get(event)) {
529                 handler.obtainMessage(event, mSlotId, 0 /* unused */, arrayCi).sendToTarget();
530             }
531         }
532     }
533 
updateHandlers(int event, int state)534     private synchronized void updateHandlers(int event, int state) {
535         if (eventHandlers.contains(event)) {
536             Log.d(SUB_TAG, "Updating handlers for the event: " + event);
537             for (Handler handler : eventHandlers.get(event)) {
538                 handler.obtainMessage(event, mSlotId, state).sendToTarget();
539             }
540         }
541     }
542 
updateHandlers(int event, long allowedNetworkType)543     private synchronized void updateHandlers(int event, long allowedNetworkType) {
544         if (eventHandlers.contains(event)) {
545             Log.d(SUB_TAG, "Updating handlers for the event: " + event);
546             for (Handler handler : eventHandlers.get(event)) {
547                 handler.obtainMessage(event, mSlotId, 0 /* unused */, allowedNetworkType)
548                         .sendToTarget();
549             }
550         }
551     }
552 
callStateToString(int state)553     private String callStateToString(int state) {
554         return switch (state) {
555             case TelephonyManager.CALL_STATE_IDLE -> "CALL_STATE_IDLE";
556             case TelephonyManager.CALL_STATE_RINGING -> "CALL_STATE_RINGING";
557             case TelephonyManager.CALL_STATE_OFFHOOK -> "CALL_STATE_OFFHOOK";
558             default -> "Unknown Call State (" + state + ")";
559         };
560     }
561 }
562