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 android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.Context;
22 import android.os.Handler;
23 import android.os.HandlerThread;
24 import android.os.Looper;
25 import android.telephony.BarringInfo;
26 import android.telephony.DisconnectCause;
27 import android.telephony.DomainSelectionService;
28 import android.telephony.ServiceState;
29 import android.telephony.SubscriptionInfo;
30 import android.telephony.SubscriptionManager;
31 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
32 import android.telephony.TelephonyManager;
33 import android.telephony.TransportSelectorCallback;
34 import android.util.IndentingPrintWriter;
35 import android.util.LocalLog;
36 import android.util.Log;
37 import android.util.SparseArray;
38 
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.internal.telephony.flags.Flags;
41 
42 import java.io.FileDescriptor;
43 import java.io.PrintWriter;
44 import java.util.ArrayList;
45 import java.util.List;
46 import java.util.concurrent.Executor;
47 
48 /**
49  * Implements the telephony domain selection for various telephony features.
50  */
51 public class TelephonyDomainSelectionService extends DomainSelectionService {
52     /**
53      * Testing interface for injecting mock ImsStateTracker.
54      */
55     @VisibleForTesting
56     public interface ImsStateTrackerFactory {
57         /**
58          * @return The {@link ImsStateTracker} created for the specified slot.
59          */
create(Context context, int slotId, @NonNull Looper looper)60         ImsStateTracker create(Context context, int slotId, @NonNull Looper looper);
61     }
62 
63     /**
64      * Testing interface for injecting mock DomainSelector.
65      */
66     @VisibleForTesting
67     public interface DomainSelectorFactory {
68         /**
69          * @return The {@link DomainSelectorBase} created using the specified arguments.
70          */
create(Context context, int slotId, int subId, @SelectorType int selectorType, boolean isEmergency, @NonNull Looper looper, @NonNull ImsStateTracker imsStateTracker, @NonNull DomainSelectorBase.DestroyListener listener, @NonNull CrossSimRedialingController crossSimRedialingController, @NonNull DataConnectionStateHelper dataConnectionStateHelper)71         DomainSelectorBase create(Context context, int slotId, int subId,
72                 @SelectorType int selectorType, boolean isEmergency, @NonNull Looper looper,
73                 @NonNull ImsStateTracker imsStateTracker,
74                 @NonNull DomainSelectorBase.DestroyListener listener,
75                 @NonNull CrossSimRedialingController crossSimRedialingController,
76                 @NonNull DataConnectionStateHelper dataConnectionStateHelper);
77     }
78 
79     private static final class DefaultDomainSelectorFactory implements DomainSelectorFactory {
80         @Override
create(Context context, int slotId, int subId, @SelectorType int selectorType, boolean isEmergency, @NonNull Looper looper, @NonNull ImsStateTracker imsStateTracker, @NonNull DomainSelectorBase.DestroyListener listener, @NonNull CrossSimRedialingController crossSimRedialingController, @NonNull DataConnectionStateHelper dataConnectionStateHelper)81         public DomainSelectorBase create(Context context, int slotId, int subId,
82                 @SelectorType int selectorType, boolean isEmergency, @NonNull Looper looper,
83                 @NonNull ImsStateTracker imsStateTracker,
84                 @NonNull DomainSelectorBase.DestroyListener listener,
85                 @NonNull CrossSimRedialingController crossSimRedialingController,
86                 @NonNull DataConnectionStateHelper dataConnectionStateHelper) {
87             DomainSelectorBase selector = null;
88 
89             logi("create-DomainSelector: slotId=" + slotId + ", subId=" + subId
90                     + ", selectorType=" + selectorTypeToString(selectorType)
91                     + ", emergency=" + isEmergency);
92 
93             switch (selectorType) {
94                 case SELECTOR_TYPE_CALLING:
95                     if (isEmergency) {
96                         selector = new EmergencyCallDomainSelector(context, slotId, subId, looper,
97                                 imsStateTracker, listener, crossSimRedialingController,
98                                 dataConnectionStateHelper);
99                     } else {
100                         selector = new NormalCallDomainSelector(context, slotId, subId, looper,
101                                 imsStateTracker, listener);
102                     }
103                     break;
104                 case SELECTOR_TYPE_SMS:
105                     if (isEmergency) {
106                         selector = new EmergencySmsDomainSelector(context, slotId, subId, looper,
107                                 imsStateTracker, listener);
108                     } else {
109                         selector = new SmsDomainSelector(context, slotId, subId, looper,
110                                 imsStateTracker, listener);
111                     }
112                     break;
113                 default:
114                     // Not reachable.
115                     break;
116             }
117 
118             return selector;
119         }
120     };
121 
122     /**
123      * A container class to manage the domain selector per a slot and selector type.
124      * If the domain selector is not null and reusable, the same domain selector will be used
125      * for the specific slot.
126      */
127     private static final class DomainSelectorContainer {
128         private final int mSlotId;
129         private final @SelectorType int mSelectorType;
130         private final boolean mIsEmergency;
131         private final @NonNull DomainSelectorBase mSelector;
132 
DomainSelectorContainer(int slotId, @SelectorType int selectorType, boolean isEmergency, @NonNull DomainSelectorBase selector)133         DomainSelectorContainer(int slotId, @SelectorType int selectorType, boolean isEmergency,
134                 @NonNull DomainSelectorBase selector) {
135             mSlotId = slotId;
136             mSelectorType = selectorType;
137             mIsEmergency = isEmergency;
138             mSelector = selector;
139         }
140 
getSlotId()141         public int getSlotId() {
142             return mSlotId;
143         }
144 
getSelectorType()145         public @SelectorType int getSelectorType() {
146             return mSelectorType;
147         }
148 
getDomainSelector()149         public DomainSelectorBase getDomainSelector() {
150             return mSelector;
151         }
152 
isEmergency()153         public boolean isEmergency() {
154             return mIsEmergency;
155         }
156 
157         @Override
toString()158         public String toString() {
159             return new StringBuilder()
160                     .append("{ ")
161                     .append("slotId=").append(mSlotId)
162                     .append(", selectorType=").append(selectorTypeToString(mSelectorType))
163                     .append(", isEmergency=").append(mIsEmergency)
164                     .append(", selector=").append(mSelector)
165                     .append(" }").toString();
166         }
167     }
168 
169     private final DomainSelectorBase.DestroyListener mDestroyListener =
170             new DomainSelectorBase.DestroyListener() {
171         @Override
172         public void onDomainSelectorDestroyed(DomainSelectorBase selector) {
173             logd("DomainSelector destroyed: " + selector);
174             removeDomainSelector(selector);
175         }
176     };
177 
178     /**
179      * A class to listen for the subscription change for starting {@link ImsStateTracker}
180      * to monitor the IMS states.
181      */
182     private final OnSubscriptionsChangedListener mSubscriptionsChangedListener =
183             new OnSubscriptionsChangedListener() {
184                 @Override
185                 public void onSubscriptionsChanged() {
186                     handleSubscriptionsChanged();
187                 }
188             };
189 
190     private static final String TAG = TelephonyDomainSelectionService.class.getSimpleName();
191 
192     // Persistent Logging
193     private static final LocalLog sEventLog = new LocalLog(20);
194     private Context mContext;
195     // Map of slotId -> ImsStateTracker
196     private final SparseArray<ImsStateTracker> mImsStateTrackers = new SparseArray<>(2);
197     private final List<DomainSelectorContainer> mDomainSelectorContainers = new ArrayList<>();
198     private final ImsStateTrackerFactory mImsStateTrackerFactory;
199     private final DomainSelectorFactory mDomainSelectorFactory;
200     private Handler mServiceHandler;
201     private CrossSimRedialingController mCrossSimRedialingController;
202     private DataConnectionStateHelper mDataConnectionStateHelper;
203 
204     /** Default constructor. */
TelephonyDomainSelectionService()205     public TelephonyDomainSelectionService() {
206         this(ImsStateTracker::new, new DefaultDomainSelectorFactory(), null);
207     }
208 
209     @VisibleForTesting
TelephonyDomainSelectionService( @onNull ImsStateTrackerFactory imsStateTrackerFactory, @NonNull DomainSelectorFactory domainSelectorFactory, @Nullable DataConnectionStateHelper dataConnectionStateHelper)210     protected TelephonyDomainSelectionService(
211             @NonNull ImsStateTrackerFactory imsStateTrackerFactory,
212             @NonNull DomainSelectorFactory domainSelectorFactory,
213             @Nullable DataConnectionStateHelper dataConnectionStateHelper) {
214         mImsStateTrackerFactory = imsStateTrackerFactory;
215         mDomainSelectorFactory = domainSelectorFactory;
216         mDataConnectionStateHelper = dataConnectionStateHelper;
217     }
218 
219     @Override
onCreate()220     public void onCreate() {
221         logd("onCreate");
222         mContext = getApplicationContext();
223 
224         // Create a worker thread for this domain selection service.
225         getCreateExecutor();
226 
227         TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
228         int activeModemCount = (tm != null) ? tm.getActiveModemCount() : 1;
229         for (int i = 0; i < activeModemCount; ++i) {
230             mImsStateTrackers.put(i, mImsStateTrackerFactory.create(mContext, i, getLooper()));
231         }
232 
233         SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
234         if (sm != null) {
235             sm.addOnSubscriptionsChangedListener(getExecutor(), mSubscriptionsChangedListener);
236         } else {
237             loge("Adding OnSubscriptionChangedListener failed");
238         }
239 
240         mCrossSimRedialingController = new CrossSimRedialingController(mContext, getLooper());
241         if (mDataConnectionStateHelper == null) {
242             mDataConnectionStateHelper = new DataConnectionStateHelper(mContext, getLooper());
243         }
244 
245         logi("TelephonyDomainSelectionService created");
246     }
247 
248     @Override
onDestroy()249     public void onDestroy() {
250         logd("onDestroy");
251 
252         List<DomainSelectorContainer> domainSelectorContainers;
253 
254         synchronized (mDomainSelectorContainers) {
255             domainSelectorContainers = new ArrayList<>(mDomainSelectorContainers);
256             mDomainSelectorContainers.clear();
257         }
258 
259         for (DomainSelectorContainer dsc : domainSelectorContainers) {
260             DomainSelectorBase selector = dsc.getDomainSelector();
261             if (selector != null) {
262                 selector.destroy();
263             }
264         }
265         domainSelectorContainers.clear();
266 
267         synchronized (mImsStateTrackers) {
268             for (int i = 0; i < mImsStateTrackers.size(); ++i) {
269                 ImsStateTracker ist = mImsStateTrackers.get(i);
270                 if (ist != null) {
271                     ist.destroy();
272                 }
273             }
274             mImsStateTrackers.clear();
275         }
276 
277         SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
278         if (sm != null) {
279             sm.removeOnSubscriptionsChangedListener(mSubscriptionsChangedListener);
280         }
281 
282         if (mCrossSimRedialingController != null) {
283             mCrossSimRedialingController.destroy();
284             mCrossSimRedialingController = null;
285         }
286 
287         if (mDataConnectionStateHelper != null) {
288             mDataConnectionStateHelper.destroy();
289             mDataConnectionStateHelper = null;
290         }
291 
292         if (mServiceHandler != null) {
293             mServiceHandler.getLooper().quit();
294             mServiceHandler = null;
295         }
296     }
297 
298     /**
299      * Selects a domain for the given attributes and callback.
300      *
301      * @param attr required to determine the domain.
302      * @param callback the callback instance being registered.
303      */
304     @Override
onDomainSelection(@onNull SelectionAttributes attr, @NonNull TransportSelectorCallback callback)305     public void onDomainSelection(@NonNull SelectionAttributes attr,
306             @NonNull TransportSelectorCallback callback) {
307         final int slotId = attr.getSlotIndex();
308         final int subId = attr.getSubscriptionId();
309         final int selectorType = attr.getSelectorType();
310         final boolean isEmergency = attr.isEmergency();
311         ImsStateTracker ist = getImsStateTracker(slotId);
312         DomainSelectorBase selector = mDomainSelectorFactory.create(mContext, slotId, subId,
313                 selectorType, isEmergency, getLooper(), ist, mDestroyListener,
314                 mCrossSimRedialingController, mDataConnectionStateHelper);
315 
316         if (selector != null) {
317             // Ensures that ImsStateTracker is started before selecting the domain if not started
318             // for the specified subscription index.
319             ist.start(subId);
320             addDomainSelector(slotId, selectorType, isEmergency, selector);
321         } else {
322             loge("No proper domain selector: " + selectorTypeToString(selectorType));
323             // Executed through the service handler to ensure that the callbacks are not called
324             // directly in this execution flow.
325             mServiceHandler.post(() ->
326                     callback.onSelectionTerminated(DisconnectCause.ERROR_UNSPECIFIED));
327             return;
328         }
329 
330         // Executed through the service handler to ensure that the callbacks are not called
331         // directly in this execution flow.
332         mServiceHandler.post(() ->  {
333             // Notify the caller that the domain selector is created.
334             callback.onCreated(selector);
335             // Performs the domain selection.
336             selector.selectDomain(attr, callback);
337         });
338     }
339 
340     /**
341      * Called when the {@link ServiceState} needs to be updated for the specified slot and
342      * subcription index.
343      *
344      * @param slotId for which the service state changed.
345      * @param subId The current subscription for a specified slot.
346      * @param serviceState The {@link ServiceState} to be updated.
347      */
348     @Override
onServiceStateUpdated(int slotId, int subId, @NonNull ServiceState serviceState)349     public void onServiceStateUpdated(int slotId, int subId, @NonNull ServiceState serviceState) {
350         ImsStateTracker ist = getImsStateTracker(slotId);
351         if (ist != null) {
352             ist.updateServiceState(serviceState);
353         }
354     }
355 
356     /**
357      * Called when the {@link BarringInfo} needs to be updated for the specified slot and
358      * subscription index.
359      *
360      * @param slotId The slot the BarringInfo is updated for.
361      * @param subId The current subscription for a specified slot.
362      * @param barringInfo The {@link BarringInfo} to be updated.
363      */
364     @Override
onBarringInfoUpdated(int slotId, int subId, @NonNull BarringInfo barringInfo)365     public void onBarringInfoUpdated(int slotId, int subId, @NonNull BarringInfo barringInfo) {
366         ImsStateTracker ist = getImsStateTracker(slotId);
367         if (ist != null) {
368             ist.updateBarringInfo(barringInfo);
369         }
370     }
371 
372     /**
373      *  Returns an Executor used to execute methods called remotely by the framework.
374      */
375     @Override
getCreateExecutor()376     public @NonNull Executor getCreateExecutor() {
377         return getExecutor();
378     }
379 
380     /**
381      *  Returns an Executor used to execute methods called remotely by the framework.
382      */
383     @VisibleForTesting
getExecutor()384     public @NonNull Executor getExecutor() {
385         if (mServiceHandler == null) {
386             HandlerThread handlerThread = new HandlerThread(TAG);
387             handlerThread.start();
388             mServiceHandler = new Handler(handlerThread.getLooper());
389         }
390 
391         return mServiceHandler::post;
392     }
393 
394     /**
395      * Returns a Looper instance.
396      */
397     @VisibleForTesting
getLooper()398     public Looper getLooper() {
399         getExecutor();
400         return mServiceHandler.getLooper();
401     }
402 
403     /**
404      * Handles the subscriptions change.
405      */
handleSubscriptionsChanged()406     private void handleSubscriptionsChanged() {
407         SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
408         if (Flags.workProfileApiSplit()) {
409             sm = sm.createForAllUserProfiles();
410         }
411         List<SubscriptionInfo> subsInfoList =
412                 (sm != null) ? sm.getActiveSubscriptionInfoList() : null;
413 
414         if (subsInfoList == null || subsInfoList.isEmpty()) {
415             logd("handleSubscriptionsChanged: No valid SubscriptionInfo");
416             return;
417         }
418 
419         for (int i = 0; i < subsInfoList.size(); ++i) {
420             SubscriptionInfo subsInfo = subsInfoList.get(i);
421             int slotId = subsInfo.getSimSlotIndex();
422 
423             if (slotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
424                 logd("handleSubscriptionsChanged: slotId=" + slotId);
425                 ImsStateTracker ist = getImsStateTracker(slotId);
426                 ist.start(subsInfo.getSubscriptionId());
427             }
428         }
429     }
430 
431     /**
432      * Adds the {@link DomainSelectorBase} to the list of domain selector container.
433      */
addDomainSelector(int slotId, @SelectorType int selectorType, boolean isEmergency, @NonNull DomainSelectorBase selector)434     private void addDomainSelector(int slotId, @SelectorType int selectorType,
435             boolean isEmergency, @NonNull DomainSelectorBase selector) {
436         synchronized (mDomainSelectorContainers) {
437             // If the domain selector already exists, remove the previous one first.
438             for (int i = 0; i < mDomainSelectorContainers.size(); ++i) {
439                 DomainSelectorContainer dsc = mDomainSelectorContainers.get(i);
440 
441                 if (dsc.getSlotId() == slotId
442                         && dsc.getSelectorType() == selectorType
443                         && dsc.isEmergency() == isEmergency) {
444                     mDomainSelectorContainers.remove(i);
445                     DomainSelectorBase oldSelector = dsc.getDomainSelector();
446                     if (oldSelector != null) {
447                         logw("DomainSelector destroyed by new domain selection request: " + dsc);
448                         oldSelector.destroy();
449                     }
450                     break;
451                 }
452             }
453 
454             DomainSelectorContainer dsc =
455                     new DomainSelectorContainer(slotId, selectorType, isEmergency, selector);
456             mDomainSelectorContainers.add(dsc);
457 
458             logi("DomainSelector added: " + dsc + ", count=" + mDomainSelectorContainers.size());
459         }
460     }
461 
462     /**
463      * Removes the domain selector container that matches with the specified
464      * {@link DomainSelectorBase}.
465      */
removeDomainSelector(@onNull DomainSelectorBase selector)466     private void removeDomainSelector(@NonNull DomainSelectorBase selector) {
467         synchronized (mDomainSelectorContainers) {
468             for (int i = 0; i < mDomainSelectorContainers.size(); ++i) {
469                 DomainSelectorContainer dsc = mDomainSelectorContainers.get(i);
470 
471                 if (dsc.getDomainSelector() == selector) {
472                     mDomainSelectorContainers.remove(i);
473                     logi("DomainSelector removed: " + dsc
474                             + ", count=" + mDomainSelectorContainers.size());
475                     break;
476                 }
477             }
478         }
479     }
480 
481     /**
482      * Returns the {@link ImsStateTracker} instance for the specified slot.
483      * If the {@link ImsStateTracker} does not exist for the slot, it creates new instance
484      * and returns.
485      */
getImsStateTracker(int slotId)486     private ImsStateTracker getImsStateTracker(int slotId) {
487         synchronized (mImsStateTrackers) {
488             ImsStateTracker ist = mImsStateTrackers.get(slotId);
489 
490             if (ist == null) {
491                 ist = mImsStateTrackerFactory.create(mContext, slotId, getLooper());
492                 mImsStateTrackers.put(slotId, ist);
493             }
494 
495             return ist;
496         }
497     }
498 
selectorTypeToString(@electorType int selectorType)499     private static String selectorTypeToString(@SelectorType int selectorType) {
500         switch (selectorType) {
501             case SELECTOR_TYPE_CALLING: return "CALLING";
502             case SELECTOR_TYPE_SMS: return "SMS";
503             default: return Integer.toString(selectorType);
504         }
505     }
506 
logd(String s)507     private static void logd(String s) {
508         Log.d(TAG, s);
509     }
510 
logi(String s)511     private static void logi(String s) {
512         Log.i(TAG, s);
513         sEventLog.log(s);
514     }
515 
loge(String s)516     private static void loge(String s) {
517         Log.e(TAG, s);
518         sEventLog.log(s);
519     }
520 
logw(String s)521     private static void logw(String s) {
522         Log.w(TAG, s);
523         sEventLog.log(s);
524     }
525 
526     /**
527      * Dumps this instance into a readable format for dumpsys usage.
528      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)529     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
530         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
531         ipw.println("TelephonyDomainSelectionService:");
532         ipw.increaseIndent();
533         ipw.println("ImsStateTrackers:");
534         synchronized (mImsStateTrackers) {
535             for (int i = 0; i < mImsStateTrackers.size(); ++i) {
536                 ImsStateTracker ist = mImsStateTrackers.valueAt(i);
537                 ist.dump(ipw);
538             }
539         }
540         ipw.decreaseIndent();
541         ipw.increaseIndent();
542         synchronized (mDomainSelectorContainers) {
543             for (int i = 0; i < mDomainSelectorContainers.size(); ++i) {
544                 DomainSelectorContainer dsc = mDomainSelectorContainers.get(i);
545                 ipw.println("DomainSelector: " + dsc.toString());
546                 ipw.increaseIndent();
547                 DomainSelectorBase selector = dsc.getDomainSelector();
548                 if (selector != null) {
549                     selector.dump(ipw);
550                 }
551                 ipw.decreaseIndent();
552             }
553         }
554         ipw.decreaseIndent();
555         ipw.increaseIndent();
556         ipw.println("Event Log:");
557         ipw.increaseIndent();
558         sEventLog.dump(ipw);
559         ipw.decreaseIndent();
560         ipw.decreaseIndent();
561         ipw.println("________________________________");
562     }
563 }
564