• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.services.telephony.domainselection;
18 
19 import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
20 
21 import android.annotation.NonNull;
22 import android.content.Context;
23 import android.os.Looper;
24 import android.os.Message;
25 import android.os.PersistableBundle;
26 import android.telecom.TelecomManager;
27 import android.telephony.Annotation.DisconnectCauses;
28 import android.telephony.CarrierConfigManager;
29 import android.telephony.DisconnectCause;
30 import android.telephony.DomainSelectionService.SelectionAttributes;
31 import android.telephony.NetworkRegistrationInfo;
32 import android.telephony.PhoneNumberUtils;
33 import android.telephony.ServiceState;
34 import android.telephony.SubscriptionManager;
35 import android.telephony.TransportSelectorCallback;
36 import android.telephony.ims.ImsReasonInfo;
37 
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.internal.telephony.CallFailCause;
40 
41 /**
42  * Implements domain selector for outgoing non-emergency calls.
43  */
44 public class NormalCallDomainSelector extends DomainSelectorBase implements
45         ImsStateTracker.ImsStateListener, ImsStateTracker.ServiceStateListener {
46 
47     private static final String LOG_TAG = "NCDS";
48 
49     // Wait-time for IMS state change callback.
50     @VisibleForTesting
51     protected static final int WAIT_FOR_IMS_STATE_TIMEOUT_MS = 3000; // 3 seconds
52 
53     @VisibleForTesting
54     protected static final int MSG_WAIT_FOR_IMS_STATE_TIMEOUT = 11;
55 
56     @VisibleForTesting
57     protected enum SelectorState {
58         ACTIVE,
59         INACTIVE,
60         DESTROYED
61     };
62 
63     protected SelectorState mSelectorState = SelectorState.INACTIVE;
64     protected ServiceState mServiceState;
65     private boolean mImsRegStateReceived;
66     private boolean mMmTelCapabilitiesReceived;
67     private boolean mReselectDomain;
68 
NormalCallDomainSelector(Context context, int slotId, int subId, @NonNull Looper looper, @NonNull ImsStateTracker imsStateTracker, @NonNull DestroyListener destroyListener)69     public NormalCallDomainSelector(Context context, int slotId, int subId, @NonNull Looper looper,
70                                     @NonNull ImsStateTracker imsStateTracker,
71                                     @NonNull DestroyListener destroyListener) {
72         super(context, slotId, subId, looper, imsStateTracker, destroyListener, LOG_TAG);
73 
74         if (SubscriptionManager.isValidSubscriptionId(subId)) {
75             logd("Subscribing to state callbacks. Subid:" + subId);
76             mImsStateTracker.addServiceStateListener(this);
77             mImsStateTracker.addImsStateListener(this);
78 
79         } else {
80             loge("Invalid Subscription. Subid:" + subId);
81         }
82     }
83 
84     @Override
handleMessage(Message message)85     public void handleMessage(Message message) {
86         switch (message.what) {
87 
88             case MSG_WAIT_FOR_IMS_STATE_TIMEOUT: {
89                 loge("ImsStateTimeout. ImsState callback not received");
90                 if (mSelectorState != SelectorState.ACTIVE) {
91                     return;
92                 }
93 
94                 if (!mImsRegStateReceived) {
95                     onImsRegistrationStateChanged();
96                 }
97 
98                 if (!mMmTelCapabilitiesReceived) {
99                     onImsMmTelCapabilitiesChanged();
100                 }
101             }
102             break;
103 
104             default: {
105                 super.handleMessage(message);
106             }
107             break;
108         }
109     }
110 
111     @Override
selectDomain(SelectionAttributes attributes, TransportSelectorCallback callback)112     public void selectDomain(SelectionAttributes attributes, TransportSelectorCallback callback) {
113         mSelectionAttributes = attributes;
114         mTransportSelectorCallback = callback;
115         mSelectorState = SelectorState.ACTIVE;
116 
117         if (callback == null) {
118             mSelectorState = SelectorState.INACTIVE;
119             loge("Invalid params: TransportSelectorCallback is null");
120             return;
121         }
122 
123         if (attributes == null) {
124             loge("Invalid params: SelectionAttributes are null");
125             notifySelectionTerminated(DisconnectCause.OUTGOING_FAILURE);
126             return;
127         }
128 
129         int subId = attributes.getSubscriptionId();
130         boolean validSubscriptionId = SubscriptionManager.isValidSubscriptionId(subId);
131         if (attributes.getSelectorType() != SELECTOR_TYPE_CALLING || attributes.isEmergency()
132                 || !validSubscriptionId) {
133             loge("Domain Selection stopped. SelectorType:" + attributes.getSelectorType()
134                     + ", isEmergency:" + attributes.isEmergency()
135                     + ", ValidSubscriptionId:" + validSubscriptionId);
136 
137             notifySelectionTerminated(DisconnectCause.OUTGOING_FAILURE);
138             return;
139         }
140 
141         if (subId == getSubId()) {
142             logd("NormalCallDomainSelection triggered. Sub-id:" + subId);
143             sendEmptyMessageDelayed(MSG_WAIT_FOR_IMS_STATE_TIMEOUT, WAIT_FOR_IMS_STATE_TIMEOUT_MS);
144             post(() -> selectDomain());
145         } else {
146             mSelectorState = SelectorState.INACTIVE;
147             loge("Subscription-ids doesn't match. This instance is associated with sub-id:"
148                     + getSubId() + ", requested sub-id:" + subId);
149             // TODO: Throw anomaly here. This condition should never occur.
150         }
151     }
152 
153     @Override
reselectDomain(SelectionAttributes attributes)154     public void reselectDomain(SelectionAttributes attributes) {
155         logd("reselectDomain called");
156         mReselectDomain = true;
157         selectDomain(attributes, mTransportSelectorCallback);
158     }
159 
160     @Override
finishSelection()161     public synchronized void finishSelection() {
162         logd("finishSelection");
163         if (mSelectorState == SelectorState.ACTIVE) {
164             // This is cancel selection case.
165             cancelSelection();
166             return;
167         }
168 
169         if (mSelectorState != SelectorState.DESTROYED) {
170             mImsStateTracker.removeServiceStateListener(this);
171             mImsStateTracker.removeImsStateListener(this);
172             mSelectionAttributes = null;
173             mTransportSelectorCallback = null;
174             destroy();
175         }
176     }
177 
178     @Override
destroy()179     public void destroy() {
180         logd("destroy");
181         switch (mSelectorState) {
182             case INACTIVE:
183                 mSelectorState = SelectorState.DESTROYED;
184                 super.destroy();
185                 break;
186 
187             case ACTIVE:
188                 loge("destroy is called when selector state is in ACTIVE state");
189                 cancelSelection();
190                 break;
191 
192             case DESTROYED:
193                 super.destroy();
194                 break;
195         }
196     }
197 
cancelSelection()198     public void cancelSelection() {
199         logd("cancelSelection");
200         mSelectorState = SelectorState.INACTIVE;
201         mReselectDomain = false;
202         if (mTransportSelectorCallback != null) {
203             mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.OUTGOING_CANCELED);
204         }
205         finishSelection();
206     }
207 
208     @Override
onImsRegistrationStateChanged()209     public void onImsRegistrationStateChanged() {
210         logd("onImsRegistrationStateChanged. IsImsRegistered: "
211                 + mImsStateTracker.isImsRegistered());
212         mImsRegStateReceived = true;
213         selectDomain();
214     }
215 
216     @Override
onImsMmTelCapabilitiesChanged()217     public void onImsMmTelCapabilitiesChanged() {
218         logd("onImsMmTelCapabilitiesChanged. ImsVoiceCap: " + mImsStateTracker.isImsVoiceCapable()
219                 + " ImsVideoCap: " + mImsStateTracker.isImsVideoCapable());
220         mMmTelCapabilitiesReceived = true;
221         selectDomain();
222     }
223 
224     @Override
onImsMmTelFeatureAvailableChanged()225     public void onImsMmTelFeatureAvailableChanged() {
226         logd("onImsMmTelFeatureAvailableChanged");
227         selectDomain();
228     }
229 
230     @Override
onServiceStateUpdated(ServiceState serviceState)231     public void onServiceStateUpdated(ServiceState serviceState) {
232         logd("onServiceStateUpdated");
233         mServiceState = serviceState;
234         selectDomain();
235     }
236 
notifyPsSelected()237     private void notifyPsSelected() {
238         logd("notifyPsSelected");
239         mSelectorState = SelectorState.INACTIVE;
240         if (mImsStateTracker.isImsRegisteredOverWlan()) {
241             logd("WLAN selected");
242             mTransportSelectorCallback.onWlanSelected(false);
243         } else {
244             if (mWwanSelectorCallback == null) {
245                 mTransportSelectorCallback.onWwanSelected((callback) -> {
246                     mWwanSelectorCallback = callback;
247                     notifyPsSelectedInternal();
248                 });
249             } else {
250                 notifyPsSelectedInternal();
251             }
252         }
253     }
254 
notifyPsSelectedInternal()255     private void notifyPsSelectedInternal() {
256         if (mWwanSelectorCallback != null) {
257             logd("notifyPsSelected - onWwanSelected");
258             mWwanSelectorCallback.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS, false);
259         } else {
260             loge("wwanSelectorCallback is null");
261             mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.OUTGOING_FAILURE);
262         }
263     }
264 
notifyCsSelected()265     private void notifyCsSelected() {
266         logd("notifyCsSelected");
267         mSelectorState = SelectorState.INACTIVE;
268         if (mWwanSelectorCallback == null) {
269             mTransportSelectorCallback.onWwanSelected((callback) -> {
270                 mWwanSelectorCallback = callback;
271                 notifyCsSelectedInternal();
272             });
273         } else {
274             notifyCsSelectedInternal();
275         }
276     }
277 
notifyCsSelectedInternal()278     private void notifyCsSelectedInternal() {
279         if (mWwanSelectorCallback != null) {
280             logd("wwanSelectorCallback -> onDomainSelected(DOMAIN_CS)");
281             mWwanSelectorCallback.onDomainSelected(NetworkRegistrationInfo.DOMAIN_CS, false);
282         } else {
283             loge("wwanSelectorCallback is null");
284             mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.OUTGOING_FAILURE);
285         }
286     }
287 
notifySelectionTerminated(@isconnectCauses int cause)288     private void notifySelectionTerminated(@DisconnectCauses int cause) {
289         mSelectorState = SelectorState.INACTIVE;
290         if (mTransportSelectorCallback != null) {
291             mTransportSelectorCallback.onSelectionTerminated(cause);
292             finishSelection();
293         }
294     }
295 
isOutOfService()296     private boolean isOutOfService() {
297         return (mServiceState.getState() == ServiceState.STATE_OUT_OF_SERVICE
298                 || mServiceState.getState() == ServiceState.STATE_POWER_OFF
299                 || mServiceState.getState() == ServiceState.STATE_EMERGENCY_ONLY);
300     }
301 
isWpsCallSupportedByIms()302     private boolean isWpsCallSupportedByIms() {
303         CarrierConfigManager configManager = mContext.getSystemService(CarrierConfigManager.class);
304 
305         PersistableBundle config = null;
306         if (configManager != null) {
307             config = configManager.getConfigForSubId(mSelectionAttributes.getSubscriptionId(),
308                     new String[] {CarrierConfigManager.KEY_SUPPORT_WPS_OVER_IMS_BOOL});
309         }
310 
311         return (config != null)
312                 ? config.getBoolean(CarrierConfigManager.KEY_SUPPORT_WPS_OVER_IMS_BOOL) : false;
313     }
314 
handleWpsCall()315     private void handleWpsCall() {
316         if (isWpsCallSupportedByIms()) {
317             logd("WPS call placed over PS");
318             notifyPsSelected();
319         } else {
320             if (isOutOfService()) {
321                 loge("Cannot place call in current ServiceState: " + mServiceState.getState());
322                 notifySelectionTerminated(DisconnectCause.OUT_OF_SERVICE);
323             } else {
324                 logd("WPS call placed over CS");
325                 notifyCsSelected();
326             }
327         }
328     }
329 
isTtySupportedByIms()330     private boolean isTtySupportedByIms() {
331         CarrierConfigManager configManager = mContext.getSystemService(CarrierConfigManager.class);
332 
333         PersistableBundle config = null;
334         if (configManager != null) {
335             config = configManager.getConfigForSubId(mSelectionAttributes.getSubscriptionId(),
336                     new String[] {CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL});
337         }
338 
339         return (config != null)
340                 && config.getBoolean(CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL);
341     }
342 
isTtyModeEnabled()343     private boolean isTtyModeEnabled() {
344         TelecomManager tm = mContext.getSystemService(TelecomManager.class);
345         if (tm == null) {
346             loge("isTtyModeEnabled: telecom not available");
347             return false;
348         }
349         return tm.getCurrentTtyMode() != TelecomManager.TTY_MODE_OFF;
350     }
351 
selectDomain()352     private synchronized void selectDomain() {
353         if (mSelectorState != SelectorState.ACTIVE || mSelectionAttributes == null
354                 || mTransportSelectorCallback == null) {
355             mSelectorState = SelectorState.INACTIVE;
356             logd("Domain Selection is stopped.");
357             return;
358         }
359 
360         if (mServiceState == null) {
361             logd("Waiting for ServiceState callback.");
362             return;
363         }
364 
365         // Check if this is a re-dial scenario
366         ImsReasonInfo imsReasonInfo = mSelectionAttributes.getPsDisconnectCause();
367         if (mReselectDomain) {
368             mReselectDomain = false;
369 
370             // Out of service
371             if (isOutOfService()) {
372                 loge("Cannot place call in current ServiceState: " + mServiceState.getState());
373                 notifySelectionTerminated(DisconnectCause.OUT_OF_SERVICE);
374 
375                 return;
376             }
377 
378             // IMS -> CS
379             if (imsReasonInfo != null) {
380                 logd("PsDisconnectCause:" + imsReasonInfo.getCode());
381                 if (imsReasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED) {
382                     logd("Redialing over CS");
383                     notifyCsSelected();
384                 } else {
385                     // Not a valid redial
386                     logd("Redialing cancelled.");
387                     notifySelectionTerminated(DisconnectCause.NOT_VALID);
388                 }
389                 return;
390             }
391 
392             // CS -> IMS
393             int csDisconnectCause = mSelectionAttributes.getCsDisconnectCause();
394             switch (csDisconnectCause) {
395                 case CallFailCause.EMC_REDIAL_ON_IMS:
396                 case CallFailCause.EMC_REDIAL_ON_VOWIFI:
397                     // Check IMS registration state.
398                     if (mImsStateTracker.isImsRegistered()) {
399                         logd("IMS is registered");
400                         notifyPsSelected();
401                         return;
402                     } else {
403                         logd("IMS is NOT registered");
404                     }
405             }
406 
407             // Not a valid redial
408             logd("Redialing cancelled.");
409             notifySelectionTerminated(DisconnectCause.NOT_VALID);
410             return;
411         }
412 
413         if (!mImsStateTracker.isMmTelFeatureAvailable()) {
414             logd("MmTelFeatureAvailable unavailable");
415             if (isOutOfService()) {
416                 loge("Cannot place call in current ServiceState: " + mServiceState.getState());
417                 notifySelectionTerminated(DisconnectCause.OUT_OF_SERVICE);
418             } else {
419                 notifyCsSelected();
420             }
421             return;
422         }
423 
424         if (!mImsRegStateReceived || !mMmTelCapabilitiesReceived) {
425             loge("Waiting for ImsState and MmTelCapabilities callbacks");
426             return;
427         }
428 
429         if (hasMessages(MSG_WAIT_FOR_IMS_STATE_TIMEOUT)) {
430             removeMessages(MSG_WAIT_FOR_IMS_STATE_TIMEOUT);
431         }
432 
433         // Check IMS registration state.
434         if (!mImsStateTracker.isImsRegistered()) {
435             logd("IMS is NOT registered");
436             if (isOutOfService()) {
437                 loge("Cannot place call in current ServiceState: " + mServiceState.getState());
438                 notifySelectionTerminated(DisconnectCause.OUT_OF_SERVICE);
439             } else {
440                 notifyCsSelected();
441             }
442             return;
443         }
444 
445         // Check TTY
446         if (isTtyModeEnabled() && !isTtySupportedByIms()) {
447             if (isOutOfService()) {
448                 loge("Cannot place call in current ServiceState: " + mServiceState.getState());
449                 notifySelectionTerminated(DisconnectCause.OUT_OF_SERVICE);
450             } else {
451                 notifyCsSelected();
452             }
453             return;
454         }
455 
456         // Handle video call.
457         if (mSelectionAttributes.isVideoCall()) {
458             logd("It's a video call");
459             if (mImsStateTracker.isImsVideoCapable()) {
460                 logd("IMS is video capable");
461                 notifyPsSelected();
462             } else {
463                 logd("IMS is not video capable. Ending the call");
464                 notifySelectionTerminated(DisconnectCause.OUTGOING_FAILURE);
465             }
466             return;
467         }
468 
469         // Handle voice call.
470         if (mImsStateTracker.isImsVoiceCapable()) {
471             logd("IMS is voice capable");
472             String number = mSelectionAttributes.getAddress().getSchemeSpecificPart();
473             if (PhoneNumberUtils.isWpsCallNumber(number)) {
474                 handleWpsCall();
475             } else {
476                 notifyPsSelected();
477             }
478         } else {
479             logd("IMS is not voice capable");
480             // Voice call CS fallback
481             if (isOutOfService()) {
482                 loge("Cannot place call in current ServiceState: " + mServiceState.getState());
483                 notifySelectionTerminated(DisconnectCause.OUT_OF_SERVICE);
484             } else {
485                 notifyCsSelected();
486             }
487         }
488     }
489 
490     @VisibleForTesting
getSelectorState()491     protected SelectorState getSelectorState() {
492         return mSelectorState;
493     }
494 }
495