• 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             if (!mReselectDomain) {
144                 sendEmptyMessageDelayed(MSG_WAIT_FOR_IMS_STATE_TIMEOUT,
145                         WAIT_FOR_IMS_STATE_TIMEOUT_MS);
146             }
147             post(() -> selectDomain());
148         } else {
149             mSelectorState = SelectorState.INACTIVE;
150             loge("Subscription-ids doesn't match. This instance is associated with sub-id:"
151                     + getSubId() + ", requested sub-id:" + subId);
152             // TODO: Throw anomaly here. This condition should never occur.
153         }
154     }
155 
156     @Override
reselectDomain(SelectionAttributes attributes)157     public void reselectDomain(SelectionAttributes attributes) {
158         logd("reselectDomain called");
159         mReselectDomain = true;
160         selectDomain(attributes, mTransportSelectorCallback);
161     }
162 
163     @Override
finishSelection()164     public synchronized void finishSelection() {
165         logd("finishSelection");
166         if (mSelectorState == SelectorState.ACTIVE) {
167             // This is cancel selection case.
168             cancelSelection();
169             return;
170         }
171 
172         if (mSelectorState != SelectorState.DESTROYED) {
173             mImsStateTracker.removeServiceStateListener(this);
174             mImsStateTracker.removeImsStateListener(this);
175             mSelectionAttributes = null;
176             mTransportSelectorCallback = null;
177             destroy();
178         }
179     }
180 
181     @Override
destroy()182     public void destroy() {
183         logd("destroy");
184         switch (mSelectorState) {
185             case INACTIVE:
186                 mSelectorState = SelectorState.DESTROYED;
187                 super.destroy();
188                 break;
189 
190             case ACTIVE:
191                 loge("destroy is called when selector state is in ACTIVE state");
192                 cancelSelection();
193                 break;
194 
195             case DESTROYED:
196                 super.destroy();
197                 break;
198         }
199     }
200 
cancelSelection()201     public void cancelSelection() {
202         logd("cancelSelection");
203         mSelectorState = SelectorState.INACTIVE;
204         mReselectDomain = false;
205         if (mTransportSelectorCallback != null) {
206             mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.OUTGOING_CANCELED);
207         }
208         finishSelection();
209     }
210 
211     @Override
onImsRegistrationStateChanged()212     public void onImsRegistrationStateChanged() {
213         logd("onImsRegistrationStateChanged. IsImsRegistered: "
214                 + mImsStateTracker.isImsRegistered());
215         mImsRegStateReceived = true;
216         selectDomain();
217     }
218 
219     @Override
onImsMmTelCapabilitiesChanged()220     public void onImsMmTelCapabilitiesChanged() {
221         logd("onImsMmTelCapabilitiesChanged. ImsVoiceCap: " + mImsStateTracker.isImsVoiceCapable()
222                 + " ImsVideoCap: " + mImsStateTracker.isImsVideoCapable());
223         mMmTelCapabilitiesReceived = true;
224         selectDomain();
225     }
226 
227     @Override
onImsMmTelFeatureAvailableChanged()228     public void onImsMmTelFeatureAvailableChanged() {
229         logd("onImsMmTelFeatureAvailableChanged");
230         selectDomain();
231     }
232 
233     @Override
onServiceStateUpdated(ServiceState serviceState)234     public void onServiceStateUpdated(ServiceState serviceState) {
235         logd("onServiceStateUpdated:" + serviceState.getState());
236         mServiceState = serviceState;
237         selectDomain();
238     }
239 
notifyPsSelected()240     private void notifyPsSelected() {
241         logd("notifyPsSelected");
242         mSelectorState = SelectorState.INACTIVE;
243         if (mImsStateTracker.isImsRegisteredOverWlan()) {
244             logd("WLAN selected");
245             mTransportSelectorCallback.onWlanSelected(false);
246         } else {
247             if (mWwanSelectorCallback == null) {
248                 mTransportSelectorCallback.onWwanSelected((callback) -> {
249                     mWwanSelectorCallback = callback;
250                     notifyPsSelectedInternal();
251                 });
252             } else {
253                 notifyPsSelectedInternal();
254             }
255         }
256     }
257 
notifyPsSelectedInternal()258     private void notifyPsSelectedInternal() {
259         if (mWwanSelectorCallback != null) {
260             logd("notifyPsSelected - onWwanSelected");
261             mWwanSelectorCallback.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS, false);
262         } else {
263             loge("wwanSelectorCallback is null");
264             mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.OUTGOING_FAILURE);
265         }
266     }
267 
notifyCsSelected(boolean checkServiceState)268     private void notifyCsSelected(boolean checkServiceState) {
269         if (checkServiceState && isOutOfService()) {
270             loge("Cannot place call in current ServiceState: " + mServiceState.getState());
271             notifySelectionTerminated(DisconnectCause.OUT_OF_SERVICE);
272             return;
273         }
274 
275         logd("notifyCsSelected");
276         mSelectorState = SelectorState.INACTIVE;
277         if (mWwanSelectorCallback == null) {
278             mTransportSelectorCallback.onWwanSelected((callback) -> {
279                 mWwanSelectorCallback = callback;
280                 notifyCsSelectedInternal();
281             });
282         } else {
283             notifyCsSelectedInternal();
284         }
285     }
286 
notifyCsSelectedInternal()287     private void notifyCsSelectedInternal() {
288         if (mWwanSelectorCallback != null) {
289             logd("wwanSelectorCallback -> onDomainSelected(DOMAIN_CS)");
290             mWwanSelectorCallback.onDomainSelected(NetworkRegistrationInfo.DOMAIN_CS, false);
291         } else {
292             loge("wwanSelectorCallback is null");
293             mTransportSelectorCallback.onSelectionTerminated(DisconnectCause.OUTGOING_FAILURE);
294         }
295     }
296 
notifySelectionTerminated(@isconnectCauses int cause)297     private void notifySelectionTerminated(@DisconnectCauses int cause) {
298         mSelectorState = SelectorState.INACTIVE;
299         if (mTransportSelectorCallback != null) {
300             mTransportSelectorCallback.onSelectionTerminated(cause);
301             finishSelection();
302         }
303     }
304 
isOutOfService()305     private boolean isOutOfService() {
306         return (mServiceState.getState() == ServiceState.STATE_OUT_OF_SERVICE
307                 || mServiceState.getState() == ServiceState.STATE_POWER_OFF
308                 || mServiceState.getState() == ServiceState.STATE_EMERGENCY_ONLY);
309     }
310 
isWpsCallSupportedByIms()311     private boolean isWpsCallSupportedByIms() {
312         CarrierConfigManager configManager = mContext.getSystemService(CarrierConfigManager.class);
313 
314         PersistableBundle config = null;
315         if (configManager != null) {
316             config = configManager.getConfigForSubId(mSelectionAttributes.getSubscriptionId(),
317                     new String[] {CarrierConfigManager.KEY_SUPPORT_WPS_OVER_IMS_BOOL});
318         }
319 
320         return (config != null)
321                 ? config.getBoolean(CarrierConfigManager.KEY_SUPPORT_WPS_OVER_IMS_BOOL) : false;
322     }
323 
handleWpsCall()324     private void handleWpsCall() {
325         if (isWpsCallSupportedByIms()) {
326             logd("WPS call placed over PS");
327             notifyPsSelected();
328         } else {
329             logd("WPS call placed over CS");
330             notifyCsSelected(true);
331         }
332     }
333 
handleReselectDomain(ImsReasonInfo imsReasonInfo)334     private void handleReselectDomain(ImsReasonInfo imsReasonInfo) {
335         mReselectDomain = false;
336 
337         // IMS -> CS
338         if (imsReasonInfo != null) {
339             logd("PsDisconnectCause:" + imsReasonInfo.getCode());
340             if (imsReasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED) {
341                 logd("Redialing over CS");
342                 // Don't check for ServiceState when CODE_LOCAL_CALL_CS_RETRY_REQUIRED is received
343                 // as requested here b/380412925.
344                 notifyCsSelected(false);
345             } else {
346                 // Not a valid redial
347                 logd("Redialing cancelled.");
348                 notifySelectionTerminated(DisconnectCause.NOT_VALID);
349             }
350             return;
351         }
352 
353         // CS -> IMS
354         int csDisconnectCause = mSelectionAttributes.getCsDisconnectCause();
355         if (csDisconnectCause == CallFailCause.EMC_REDIAL_ON_IMS
356                 || csDisconnectCause == CallFailCause.EMC_REDIAL_ON_VOWIFI) {
357             // Check IMS registration state.
358             if (mImsStateTracker.isImsRegistered()) {
359                 logd("IMS is registered");
360                 notifyPsSelected();
361                 return;
362             }
363 
364             logd("IMS is NOT registered");
365         }
366 
367         // Not a valid redial
368         logd("Redialing cancelled.");
369         notifySelectionTerminated(DisconnectCause.NOT_VALID);
370     }
371 
isTtySupportedByIms()372     private boolean isTtySupportedByIms() {
373         CarrierConfigManager configManager = mContext.getSystemService(CarrierConfigManager.class);
374 
375         PersistableBundle config = null;
376         if (configManager != null) {
377             config = configManager.getConfigForSubId(mSelectionAttributes.getSubscriptionId(),
378                     new String[] {CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL});
379         }
380 
381         return (config != null)
382                 && config.getBoolean(CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL);
383     }
384 
isTtyModeEnabled()385     private boolean isTtyModeEnabled() {
386         TelecomManager tm = mContext.getSystemService(TelecomManager.class);
387         if (tm == null) {
388             loge("isTtyModeEnabled: telecom not available");
389             return false;
390         }
391         return tm.getCurrentTtyMode() != TelecomManager.TTY_MODE_OFF;
392     }
393 
selectDomain()394     private synchronized void selectDomain() {
395         if (mSelectorState != SelectorState.ACTIVE || mSelectionAttributes == null
396                 || mTransportSelectorCallback == null) {
397             mSelectorState = SelectorState.INACTIVE;
398             logd("Domain Selection is stopped.");
399             return;
400         }
401 
402         if (mServiceState == null) {
403             logd("Waiting for ServiceState callback.");
404             return;
405         }
406 
407         // Check if this is a re-dial scenario
408         if (mReselectDomain) {
409             handleReselectDomain(mSelectionAttributes.getPsDisconnectCause());
410             return;
411         }
412 
413         if (!mImsStateTracker.isMmTelFeatureAvailable()) {
414             logd("MmTelFeatureAvailable unavailable");
415             notifyCsSelected(true);
416             return;
417         }
418 
419         if (!mImsRegStateReceived || !mMmTelCapabilitiesReceived) {
420             loge("Waiting for ImsState and MmTelCapabilities callbacks");
421             return;
422         }
423 
424         if (hasMessages(MSG_WAIT_FOR_IMS_STATE_TIMEOUT)) {
425             removeMessages(MSG_WAIT_FOR_IMS_STATE_TIMEOUT);
426         }
427 
428         // Check IMS registration state.
429         if (!mImsStateTracker.isImsRegistered()) {
430             logd("IMS is NOT registered");
431             notifyCsSelected(true);
432             return;
433         }
434 
435         // Check TTY
436         if (isTtyModeEnabled() && !isTtySupportedByIms()) {
437             notifyCsSelected(true);
438             return;
439         }
440 
441         // Handle video call.
442         if (mSelectionAttributes.isVideoCall()) {
443             logd("It's a video call");
444             if (mImsStateTracker.isImsVideoCapable()) {
445                 logd("IMS is video capable");
446                 notifyPsSelected();
447             } else {
448                 logd("IMS is not video capable. Ending the call");
449                 notifySelectionTerminated(DisconnectCause.OUTGOING_FAILURE);
450             }
451             return;
452         }
453 
454         // Handle voice call.
455         if (mImsStateTracker.isImsVoiceCapable()) {
456             logd("IMS is voice capable");
457             String number = mSelectionAttributes.getAddress().getSchemeSpecificPart();
458             if (PhoneNumberUtils.isWpsCallNumber(number)) {
459                 handleWpsCall();
460             } else {
461                 notifyPsSelected();
462             }
463         } else {
464             logd("IMS is not voice capable");
465             // Voice call CS fallback
466             notifyCsSelected(true);
467         }
468     }
469 
470     @VisibleForTesting
getSelectorState()471     protected SelectorState getSelectorState() {
472         return mSelectorState;
473     }
474 }
475