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