1 /* 2 * Copyright (C) 2023 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.internal.telephony.satellite; 18 19 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; 20 import static android.telephony.ServiceState.STATE_EMERGENCY_ONLY; 21 import static android.telephony.ServiceState.STATE_IN_SERVICE; 22 import static android.telephony.ServiceState.STATE_OUT_OF_SERVICE; 23 import static android.telephony.TelephonyManager.EXTRA_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE; 24 import static android.telephony.TelephonyManager.EXTRA_EMERGENCY_CALL_TO_SATELLITE_LAUNCH_INTENT; 25 import static android.telephony.satellite.SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS; 26 import static android.telephony.satellite.SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911; 27 28 import static com.android.internal.telephony.flags.Flags.satellitePersistentLogging; 29 import static com.android.internal.telephony.satellite.SatelliteController.INVALID_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE; 30 31 import android.annotation.NonNull; 32 import android.annotation.Nullable; 33 import android.app.ActivityOptions; 34 import android.app.PendingIntent; 35 import android.content.ComponentName; 36 import android.content.Context; 37 import android.content.Intent; 38 import android.content.res.Resources; 39 import android.net.Uri; 40 import android.os.Bundle; 41 import android.os.Handler; 42 import android.os.Looper; 43 import android.os.Message; 44 import android.os.OutcomeReceiver; 45 import android.os.SystemProperties; 46 import android.provider.DeviceConfig; 47 import android.telecom.Connection; 48 import android.telephony.AccessNetworkConstants; 49 import android.telephony.DropBoxManagerLoggerBackend; 50 import android.telephony.NetworkRegistrationInfo; 51 import android.telephony.PersistentLogger; 52 import android.telephony.Rlog; 53 import android.telephony.ServiceState; 54 import android.telephony.SubscriptionManager; 55 import android.telephony.TelephonyManager; 56 import android.telephony.ims.ImsReasonInfo; 57 import android.telephony.ims.ImsRegistrationAttributes; 58 import android.telephony.ims.RegistrationManager; 59 import android.telephony.satellite.ISatelliteProvisionStateCallback; 60 import android.telephony.satellite.SatelliteManager; 61 import android.text.TextUtils; 62 import android.util.Pair; 63 import android.util.SparseArray; 64 65 import com.android.ims.ImsException; 66 import com.android.ims.ImsManager; 67 import com.android.internal.R; 68 import com.android.internal.annotations.GuardedBy; 69 import com.android.internal.annotations.VisibleForTesting; 70 import com.android.internal.telephony.Phone; 71 import com.android.internal.telephony.PhoneFactory; 72 import com.android.internal.telephony.SmsApplication; 73 import com.android.internal.telephony.metrics.SatelliteStats; 74 75 import java.util.List; 76 import java.util.concurrent.atomic.AtomicBoolean; 77 78 79 /** 80 * This module is responsible for monitoring the cellular service state and IMS registration state 81 * during an emergency call and notify Dialer when Telephony is not able to find any network and 82 * the call likely will not get connected so that Dialer will prompt the user if they would like to 83 * switch to satellite messaging. 84 */ 85 public class SatelliteSOSMessageRecommender extends Handler { 86 private static final String TAG = "SatelliteSOSMessageRecommender"; 87 private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem"; 88 private static final String BOOT_ALLOW_MOCK_MODEM_PROPERTY = "ro.boot.radio.allow_mock_modem"; 89 private static final int EVENT_EMERGENCY_CALL_STARTED = 1; 90 protected static final int EVENT_SERVICE_STATE_CHANGED = 2; 91 protected static final int EVENT_TIME_OUT = 3; 92 private static final int EVENT_SATELLITE_PROVISIONED_STATE_CHANGED = 4; 93 private static final int EVENT_EMERGENCY_CALL_CONNECTION_STATE_CHANGED = 5; 94 private static final int CMD_SEND_EVENT_DISPLAY_EMERGENCY_MESSAGE_FORCEFULLY = 6; 95 private static final int EVENT_SATELLITE_ACCESS_RESTRICTION_CHECKING_RESULT = 7; 96 97 @NonNull private final Context mContext; 98 @NonNull 99 private final SatelliteController mSatelliteController; 100 private ImsManager mImsManager; 101 102 private Connection mEmergencyConnection = null; 103 private final ISatelliteProvisionStateCallback mISatelliteProvisionStateCallback; 104 /** Key: Phone ID; Value: IMS RegistrationCallback */ 105 private SparseArray<RegistrationManager.RegistrationCallback> 106 mImsRegistrationCallbacks = new SparseArray<>(); 107 @GuardedBy("mLock") 108 private boolean mIsSatelliteAllowedForCurrentLocation = false; 109 @GuardedBy("mLock") 110 private boolean mCheckingAccessRestrictionInProgress = false; 111 protected long mTimeoutMillis = 0; 112 private final long mOemEnabledTimeoutMillis; 113 private final AtomicBoolean mIsSatelliteConnectedViaCarrierWithinHysteresisTime = 114 new AtomicBoolean(false); 115 @GuardedBy("mLock") 116 private boolean mIsTimerTimedOut = false; 117 protected int mCountOfTimerStarted = 0; 118 private final Object mLock = new Object(); 119 120 @Nullable private PersistentLogger mPersistentLogger = null; 121 122 /** 123 * Create an instance of SatelliteSOSMessageRecommender. 124 * 125 * @param context The Context for the SatelliteSOSMessageRecommender. 126 * @param looper The looper used with the handler of this class. 127 */ SatelliteSOSMessageRecommender(@onNull Context context, @NonNull Looper looper)128 public SatelliteSOSMessageRecommender(@NonNull Context context, 129 @NonNull Looper looper) { 130 this(context, looper, SatelliteController.getInstance(), null); 131 } 132 133 /** 134 * Create an instance of SatelliteSOSMessageRecommender. This constructor should be used in 135 * only unit tests. 136 * 137 * @param context The Context for the SatelliteSOSMessageRecommender. 138 * @param looper The looper used with the handler of this class. 139 * @param satelliteController The SatelliteController singleton instance. 140 * @param imsManager The ImsManager instance associated with the phone, which is used for making 141 * the emergency call. This argument is not null only in unit tests. 142 */ 143 @VisibleForTesting SatelliteSOSMessageRecommender(@onNull Context context, @NonNull Looper looper, @NonNull SatelliteController satelliteController, ImsManager imsManager)144 protected SatelliteSOSMessageRecommender(@NonNull Context context, @NonNull Looper looper, 145 @NonNull SatelliteController satelliteController, 146 ImsManager imsManager) { 147 super(looper); 148 if (isSatellitePersistentLoggingEnabled(context)) { 149 mPersistentLogger = new PersistentLogger( 150 DropBoxManagerLoggerBackend.getInstance(context)); 151 } 152 mContext = context; 153 mSatelliteController = satelliteController; 154 mImsManager = imsManager; 155 mOemEnabledTimeoutMillis = 156 getOemEnabledEmergencyCallWaitForConnectionTimeoutMillis(context); 157 mISatelliteProvisionStateCallback = new ISatelliteProvisionStateCallback.Stub() { 158 @Override 159 public void onSatelliteProvisionStateChanged(boolean provisioned) { 160 plogd("onSatelliteProvisionStateChanged: provisioned=" + provisioned); 161 sendMessage(obtainMessage(EVENT_SATELLITE_PROVISIONED_STATE_CHANGED, provisioned)); 162 } 163 }; 164 } 165 166 @Override handleMessage(@onNull Message msg)167 public void handleMessage(@NonNull Message msg) { 168 switch (msg.what) { 169 case EVENT_EMERGENCY_CALL_STARTED: 170 handleEmergencyCallStartedEvent((Connection) msg.obj); 171 break; 172 case EVENT_TIME_OUT: 173 handleTimeoutEvent(); 174 break; 175 case EVENT_SATELLITE_PROVISIONED_STATE_CHANGED: 176 handleSatelliteProvisionStateChangedEvent((boolean) msg.obj); 177 break; 178 case EVENT_EMERGENCY_CALL_CONNECTION_STATE_CHANGED: 179 handleEmergencyCallConnectionStateChangedEvent((Pair<String, Integer>) msg.obj); 180 break; 181 case EVENT_SERVICE_STATE_CHANGED: 182 handleStateChangedEventForHysteresisTimer(); 183 break; 184 case CMD_SEND_EVENT_DISPLAY_EMERGENCY_MESSAGE_FORCEFULLY: 185 handleCmdSendEventDisplayEmergencyMessageForcefully((Connection) msg.obj); 186 break; 187 case EVENT_SATELLITE_ACCESS_RESTRICTION_CHECKING_RESULT: 188 handleSatelliteAccessRestrictionCheckingResult((boolean) msg.obj); 189 break; 190 default: 191 plogd("handleMessage: unexpected message code: " + msg.what); 192 break; 193 } 194 } 195 196 /** 197 * Inform SatelliteSOSMessageRecommender that an emergency call has just started. 198 * 199 * @param connection The connection created by TelephonyConnectionService for the emergency 200 * call. 201 */ onEmergencyCallStarted(@onNull Connection connection)202 public void onEmergencyCallStarted(@NonNull Connection connection) { 203 if (!isSatelliteSupported()) { 204 plogd("onEmergencyCallStarted: satellite is not supported"); 205 return; 206 } 207 208 if (hasMessages(EVENT_EMERGENCY_CALL_STARTED)) { 209 logd("onEmergencyCallStarted: Ignoring due to ongoing event:"); 210 return; 211 } 212 213 /* 214 * Right now, assume that the device is connected to satellite via carrier within hysteresis 215 * time. However, this might not be correct when the monitoring timer expires. Thus, we 216 * should do this check now so that we have higher chance of sending the event 217 * EVENT_DISPLAY_EMERGENCY_MESSAGE to Dialer. 218 */ 219 mIsSatelliteConnectedViaCarrierWithinHysteresisTime.set( 220 mSatelliteController.isSatelliteConnectedViaCarrierWithinHysteresisTime()); 221 sendMessage(obtainMessage(EVENT_EMERGENCY_CALL_STARTED, connection)); 222 } 223 224 /** 225 * Inform SatelliteSOSMessageRecommender that the state of the emergency call connection has 226 * changed. 227 * 228 * @param callId The ID of the emergency call. 229 * @param state The connection state of the emergency call. 230 */ onEmergencyCallConnectionStateChanged( String callId, @Connection.ConnectionState int state)231 public void onEmergencyCallConnectionStateChanged( 232 String callId, @Connection.ConnectionState int state) { 233 plogd("callId=" + callId + ", state=" + state); 234 if (!isSatelliteSupported()) { 235 plogd("onEmergencyCallConnectionStateChanged: satellite is not supported"); 236 return; 237 } 238 Pair<String, Integer> argument = new Pair<>(callId, state); 239 sendMessage(obtainMessage(EVENT_EMERGENCY_CALL_CONNECTION_STATE_CHANGED, argument)); 240 } 241 242 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) getDefaultSmsApp()243 protected ComponentName getDefaultSmsApp() { 244 return SmsApplication.getDefaultSendToApplication(mContext, false); 245 } 246 handleEmergencyCallStartedEvent(@onNull Connection connection)247 private void handleEmergencyCallStartedEvent(@NonNull Connection connection) { 248 mSatelliteController.setLastEmergencyCallTime(); 249 250 if (sendEventDisplayEmergencyMessageForcefully(connection)) { 251 return; 252 } 253 254 selectEmergencyCallWaitForConnectionTimeoutDuration(); 255 if (mEmergencyConnection == null) { 256 registerForInterestedStateChangedEvents(); 257 } 258 mEmergencyConnection = connection; 259 handleStateChangedEventForHysteresisTimer(); 260 261 synchronized (mLock) { 262 mCheckingAccessRestrictionInProgress = false; 263 mIsSatelliteAllowedForCurrentLocation = false; 264 } 265 } 266 handleSatelliteProvisionStateChangedEvent(boolean provisioned)267 private void handleSatelliteProvisionStateChangedEvent(boolean provisioned) { 268 if (!provisioned) { 269 cleanUpResources(); 270 } 271 } 272 handleTimeoutEvent()273 private void handleTimeoutEvent() { 274 synchronized (mLock) { 275 mIsTimerTimedOut = true; 276 evaluateSendingConnectionEventDisplayEmergencyMessage(); 277 } 278 } 279 evaluateSendingConnectionEventDisplayEmergencyMessage()280 private void evaluateSendingConnectionEventDisplayEmergencyMessage() { 281 synchronized (mLock) { 282 if (mEmergencyConnection == null) { 283 ploge("No emergency call is ongoing..."); 284 return; 285 } 286 287 if (!mIsTimerTimedOut || mCheckingAccessRestrictionInProgress) { 288 plogd("mIsTimerTimedOut=" + mIsTimerTimedOut 289 + ", mCheckingAccessRestrictionInProgress=" 290 + mCheckingAccessRestrictionInProgress); 291 return; 292 } 293 294 /* 295 * The device might be connected to satellite after the emergency call started. Thus, we 296 * need to do this check again so that we will have higher chance of sending the event 297 * EVENT_DISPLAY_EMERGENCY_MESSAGE to Dialer. 298 */ 299 updateSatelliteViaCarrierAvailability(); 300 301 boolean isDialerNotified = false; 302 if (!isCellularAvailable() 303 && isSatelliteAllowed() 304 && (isSatelliteViaOemAvailable() || isSatelliteViaCarrierAvailable()) 305 && shouldTrackCall(mEmergencyConnection.getState())) { 306 plogd("handleTimeoutEvent: Sent EVENT_DISPLAY_EMERGENCY_MESSAGE to Dialer"); 307 Bundle extras = createExtraBundleForEventDisplayEmergencyMessage(); 308 mEmergencyConnection.sendConnectionEvent( 309 TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE, extras); 310 isDialerNotified = true; 311 312 } 313 plogd("handleTimeoutEvent: isImsRegistered=" + isImsRegistered() 314 + ", isCellularAvailable=" + isCellularAvailable() 315 + ", isSatelliteAllowed=" + isSatelliteAllowed() 316 + ", shouldTrackCall=" + shouldTrackCall(mEmergencyConnection.getState())); 317 reportEsosRecommenderDecision(isDialerNotified); 318 cleanUpResources(); 319 } 320 } 321 isSatelliteAllowed()322 private boolean isSatelliteAllowed() { 323 synchronized (mLock) { 324 if (isSatelliteViaCarrierAvailable()) return true; 325 return mIsSatelliteAllowedForCurrentLocation; 326 } 327 } 328 updateSatelliteViaCarrierAvailability()329 private void updateSatelliteViaCarrierAvailability() { 330 if (!mIsSatelliteConnectedViaCarrierWithinHysteresisTime.get()) { 331 mIsSatelliteConnectedViaCarrierWithinHysteresisTime.set( 332 mSatelliteController.isSatelliteConnectedViaCarrierWithinHysteresisTime()); 333 } 334 } 335 336 /** 337 * Check if satellite is available via OEM 338 * @return {@code true} if satellite is provisioned via OEM else return {@code false} 339 */ 340 @VisibleForTesting isSatelliteViaOemAvailable()341 public boolean isSatelliteViaOemAvailable() { 342 Boolean satelliteProvisioned = mSatelliteController.isSatelliteViaOemProvisioned(); 343 return satelliteProvisioned != null ? satelliteProvisioned : false; 344 } 345 isSatelliteViaCarrierAvailable()346 private boolean isSatelliteViaCarrierAvailable() { 347 return mIsSatelliteConnectedViaCarrierWithinHysteresisTime.get(); 348 } 349 handleEmergencyCallConnectionStateChangedEvent( @onNull Pair<String, Integer> arg)350 private void handleEmergencyCallConnectionStateChangedEvent( 351 @NonNull Pair<String, Integer> arg) { 352 mSatelliteController.setLastEmergencyCallTime(); 353 if (mEmergencyConnection == null) { 354 // Either the call was not created or the timer already timed out. 355 return; 356 } 357 358 String callId = arg.first; 359 int state = arg.second; 360 if (!mEmergencyConnection.getTelecomCallId().equals(callId)) { 361 ploge("handleEmergencyCallConnectionStateChangedEvent: unexpected state changed event " 362 + ", mEmergencyConnection=" + mEmergencyConnection + ", callId=" + callId 363 + ", state=" + state); 364 /* 365 * TelephonyConnectionService sent us a connection state changed event for a call that 366 * we're not tracking. There must be some unexpected things happened in 367 * TelephonyConnectionService. Thus, we need to clean up the resources. 368 */ 369 cleanUpResources(); 370 return; 371 } 372 373 if (!shouldTrackCall(state)) { 374 reportEsosRecommenderDecision(false); 375 cleanUpResources(); 376 } else { 377 // Location service will enter emergency mode only when connection state changes to 378 // STATE_DIALING 379 if (state == Connection.STATE_DIALING 380 && mSatelliteController.isSatelliteSupportedViaOem()) { 381 requestIsSatelliteAllowedForCurrentLocation(); 382 } 383 } 384 } 385 reportEsosRecommenderDecision(boolean isDialerNotified)386 private void reportEsosRecommenderDecision(boolean isDialerNotified) { 387 SatelliteStats.getInstance().onSatelliteSosMessageRecommender( 388 new SatelliteStats.SatelliteSosMessageRecommenderParams.Builder() 389 .setDisplaySosMessageSent(isDialerNotified) 390 .setCountOfTimerStarted(mCountOfTimerStarted) 391 .setImsRegistered(isImsRegistered()) 392 .setCellularServiceState(getBestCellularServiceState()) 393 .setIsMultiSim(isMultiSim()) 394 .setRecommendingHandoverType(getEmergencyCallToSatelliteHandoverType()) 395 .setIsSatelliteAllowedInCurrentLocation(isSatelliteAllowed()) 396 .build()); 397 } 398 cleanUpResources()399 private void cleanUpResources() { 400 synchronized (mLock) { 401 stopTimer(); 402 if (mEmergencyConnection != null) { 403 unregisterForInterestedStateChangedEvents(); 404 } 405 mEmergencyConnection = null; 406 mCountOfTimerStarted = 0; 407 mIsTimerTimedOut = false; 408 mCheckingAccessRestrictionInProgress = false; 409 mIsSatelliteAllowedForCurrentLocation = false; 410 } 411 } 412 registerForInterestedStateChangedEvents()413 private void registerForInterestedStateChangedEvents() { 414 mSatelliteController.registerForSatelliteProvisionStateChanged( 415 SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, mISatelliteProvisionStateCallback); 416 for (Phone phone : PhoneFactory.getPhones()) { 417 phone.registerForServiceStateChanged( 418 this, EVENT_SERVICE_STATE_CHANGED, null); 419 } 420 } 421 registerForImsRegistrationStateChanged(@onNull Phone phone)422 private void registerForImsRegistrationStateChanged(@NonNull Phone phone) { 423 ImsManager imsManager = (mImsManager != null) ? mImsManager : ImsManager.getInstance( 424 phone.getContext(), phone.getPhoneId()); 425 try { 426 imsManager.addRegistrationCallback( 427 getOrCreateImsRegistrationCallback(phone.getPhoneId()), this::post); 428 } catch (ImsException ex) { 429 ploge("registerForImsRegistrationStateChanged: ex=" + ex); 430 } 431 } 432 unregisterForInterestedStateChangedEvents()433 private void unregisterForInterestedStateChangedEvents() { 434 mSatelliteController.unregisterForSatelliteProvisionStateChanged( 435 SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, mISatelliteProvisionStateCallback); 436 for (Phone phone : PhoneFactory.getPhones()) { 437 phone.unregisterForServiceStateChanged(this); 438 } 439 } 440 unregisterForImsRegistrationStateChanged(@onNull Phone phone)441 private void unregisterForImsRegistrationStateChanged(@NonNull Phone phone) { 442 if (mImsRegistrationCallbacks.contains(phone.getPhoneId())) { 443 ImsManager imsManager = 444 (mImsManager != null) ? mImsManager : ImsManager.getInstance( 445 phone.getContext(), phone.getPhoneId()); 446 imsManager.removeRegistrationListener( 447 mImsRegistrationCallbacks.get(phone.getPhoneId())); 448 } else { 449 ploge("Phone ID=" + phone.getPhoneId() + " was not registered with ImsManager"); 450 } 451 } 452 isCellularAvailable()453 private boolean isCellularAvailable() { 454 for (Phone phone : PhoneFactory.getPhones()) { 455 ServiceState serviceState = phone.getServiceState(); 456 if (serviceState != null) { 457 int state = serviceState.getState(); 458 if ((state == STATE_IN_SERVICE || state == STATE_EMERGENCY_ONLY 459 || serviceState.isEmergencyOnly()) 460 && !isSatellitePlmn(phone.getSubId(), serviceState)) { 461 logv("isCellularAvailable true"); 462 return true; 463 } 464 } 465 } 466 logv("isCellularAvailable false"); 467 return false; 468 } 469 isSatellitePlmn(int subId, @NonNull ServiceState serviceState)470 private boolean isSatellitePlmn(int subId, @NonNull ServiceState serviceState) { 471 List<String> satellitePlmnList = 472 mSatelliteController.getSatellitePlmnsForCarrier(subId); 473 if (satellitePlmnList.isEmpty()) { 474 logv("isSatellitePlmn: satellitePlmnList is empty"); 475 return false; 476 } 477 478 for (NetworkRegistrationInfo nri : 479 serviceState.getNetworkRegistrationInfoListForTransportType( 480 AccessNetworkConstants.TRANSPORT_TYPE_WWAN)) { 481 String registeredPlmn = nri.getRegisteredPlmn(); 482 String mccmnc = nri.getCellIdentity().getMccString() 483 + nri.getCellIdentity().getMncString(); 484 for (String satellitePlmn : satellitePlmnList) { 485 if (TextUtils.equals(satellitePlmn, registeredPlmn) 486 || TextUtils.equals(satellitePlmn, mccmnc)) { 487 logv("isSatellitePlmn: return true, satellitePlmn:" + satellitePlmn 488 + " registeredPlmn:" + registeredPlmn + " mccmnc:" + mccmnc); 489 return true; 490 } 491 } 492 } 493 494 logv("isSatellitePlmn: return false"); 495 return false; 496 } 497 498 /** 499 * @return {@link ServiceState#STATE_IN_SERVICE} if any subscription is in this state; else 500 * {@link ServiceState#STATE_EMERGENCY_ONLY} if any subscription is in this state; else 501 * {@link ServiceState#STATE_OUT_OF_SERVICE}. 502 */ getBestCellularServiceState()503 private int getBestCellularServiceState() { 504 boolean isStateOutOfService = true; 505 for (Phone phone : PhoneFactory.getPhones()) { 506 ServiceState serviceState = phone.getServiceState(); 507 if (serviceState != null) { 508 int state = serviceState.getState(); 509 if (!serviceState.isUsingNonTerrestrialNetwork()) { 510 if ((state == STATE_IN_SERVICE)) { 511 return STATE_IN_SERVICE; 512 } else if (state == STATE_EMERGENCY_ONLY) { 513 isStateOutOfService = false; 514 } 515 } 516 } 517 } 518 return isStateOutOfService ? STATE_OUT_OF_SERVICE : STATE_EMERGENCY_ONLY; 519 } 520 isImsRegistered()521 private boolean isImsRegistered() { 522 for (Phone phone : PhoneFactory.getPhones()) { 523 if (phone.isImsRegistered()) return true; 524 } 525 return false; 526 } 527 handleStateChangedEventForHysteresisTimer()528 private synchronized void handleStateChangedEventForHysteresisTimer() { 529 if (!isCellularAvailable() && mEmergencyConnection != null) { 530 startTimer(); 531 } else { 532 logv("handleStateChangedEventForHysteresisTimer stopTimer"); 533 stopTimer(); 534 } 535 } 536 startTimer()537 private void startTimer() { 538 synchronized (mLock) { 539 if (hasMessages(EVENT_TIME_OUT)) { 540 return; 541 } 542 sendMessageDelayed(obtainMessage(EVENT_TIME_OUT), mTimeoutMillis); 543 mCountOfTimerStarted++; 544 mIsTimerTimedOut = false; 545 logd("startTimer mCountOfTimerStarted=" + mCountOfTimerStarted); 546 } 547 } 548 stopTimer()549 private void stopTimer() { 550 synchronized (mLock) { 551 removeMessages(EVENT_TIME_OUT); 552 } 553 } 554 handleSatelliteAccessRestrictionCheckingResult(boolean satelliteAllowed)555 private void handleSatelliteAccessRestrictionCheckingResult(boolean satelliteAllowed) { 556 synchronized (mLock) { 557 mIsSatelliteAllowedForCurrentLocation = satelliteAllowed; 558 mCheckingAccessRestrictionInProgress = false; 559 evaluateSendingConnectionEventDisplayEmergencyMessage(); 560 } 561 } 562 selectEmergencyCallWaitForConnectionTimeoutDuration()563 private void selectEmergencyCallWaitForConnectionTimeoutDuration() { 564 if (mSatelliteController.isSatelliteEmergencyMessagingSupportedViaCarrier()) { 565 mTimeoutMillis = 566 mSatelliteController.getCarrierEmergencyCallWaitForConnectionTimeoutMillis(); 567 } else { 568 mTimeoutMillis = mOemEnabledTimeoutMillis; 569 } 570 plogd("mTimeoutMillis = " + mTimeoutMillis); 571 } 572 getOemEnabledEmergencyCallWaitForConnectionTimeoutMillis( @onNull Context context)573 private static long getOemEnabledEmergencyCallWaitForConnectionTimeoutMillis( 574 @NonNull Context context) { 575 return context.getResources().getInteger( 576 R.integer.config_emergency_call_wait_for_connection_timeout_millis); 577 } 578 579 /** 580 * @return The Pair(PackageName, ClassName) of the oem-enabled satellite handover app. 581 */ 582 @NonNull getOemEnabledSatelliteHandoverAppFromOverlayConfig( @onNull Context context)583 private static Pair<String, String> getOemEnabledSatelliteHandoverAppFromOverlayConfig( 584 @NonNull Context context) { 585 String app = null; 586 try { 587 app = context.getResources().getString( 588 R.string.config_oem_enabled_satellite_sos_handover_app); 589 } catch (Resources.NotFoundException ex) { 590 loge("getOemEnabledSatelliteHandoverAppFromOverlayConfig: ex=" + ex); 591 } 592 if (TextUtils.isEmpty(app) && isMockModemAllowed()) { 593 logd("getOemEnabledSatelliteHandoverAppFromOverlayConfig: Read " 594 + "config_oem_enabled_satellite_sos_handover_app from device config"); 595 app = DeviceConfig.getString(DeviceConfig.NAMESPACE_TELEPHONY, 596 "config_oem_enabled_satellite_sos_handover_app", ""); 597 } 598 if (TextUtils.isEmpty(app)) return new Pair<>("", ""); 599 600 String[] appComponent = app.split(";"); 601 if (appComponent.length == 2) { 602 return new Pair<>(appComponent[0], appComponent[1]); 603 } else { 604 loge("getOemEnabledSatelliteHandoverAppFromOverlayConfig: invalid configured app=" 605 + app); 606 } 607 return new Pair<>("", ""); 608 } 609 610 611 @Nullable getSatelliteEmergencyHandoverIntentActionFromOverlayConfig( @onNull Context context)612 private static String getSatelliteEmergencyHandoverIntentActionFromOverlayConfig( 613 @NonNull Context context) { 614 String action; 615 try { 616 action = context.getResources().getString( 617 R.string.config_satellite_emergency_handover_intent_action); 618 } catch (Resources.NotFoundException ex) { 619 loge("getSatelliteEmergencyHandoverIntentFilterActionFromOverlayConfig: ex=" + ex); 620 action = null; 621 } 622 if (TextUtils.isEmpty(action) && isMockModemAllowed()) { 623 logd("getSatelliteEmergencyHandoverIntentActionFromOverlayConfig: Read " 624 + "config_satellite_emergency_handover_intent_action from device config"); 625 action = DeviceConfig.getString(DeviceConfig.NAMESPACE_TELEPHONY, 626 "config_satellite_emergency_handover_intent_action", null); 627 } 628 return action; 629 } 630 shouldTrackCall(int connectionState)631 private boolean shouldTrackCall(int connectionState) { 632 /** 633 * An active connection state means both parties are connected to the call and can actively 634 * communicate. A disconnected connection state means the emergency call has ended. In both 635 * cases, we don't need to track the call anymore. 636 */ 637 return (connectionState != Connection.STATE_ACTIVE 638 && connectionState != Connection.STATE_DISCONNECTED); 639 } 640 641 @NonNull getOrCreateImsRegistrationCallback( int phoneId)642 private RegistrationManager.RegistrationCallback getOrCreateImsRegistrationCallback( 643 int phoneId) { 644 RegistrationManager.RegistrationCallback callback = 645 mImsRegistrationCallbacks.get(phoneId); 646 if (callback == null) { 647 callback = new RegistrationManager.RegistrationCallback() { 648 @Override 649 public void onRegistered(ImsRegistrationAttributes attributes) { 650 sendMessage(obtainMessage(EVENT_SERVICE_STATE_CHANGED)); 651 } 652 653 @Override 654 public void onUnregistered(ImsReasonInfo info) { 655 sendMessage(obtainMessage(EVENT_SERVICE_STATE_CHANGED)); 656 } 657 }; 658 mImsRegistrationCallbacks.put(phoneId, callback); 659 } 660 return callback; 661 } 662 createExtraBundleForEventDisplayEmergencyMessage()663 @NonNull private Bundle createExtraBundleForEventDisplayEmergencyMessage() { 664 int handoverType = EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS; 665 Pair<String, String> oemSatelliteMessagingApp = 666 getOemEnabledSatelliteHandoverAppFromOverlayConfig(mContext); 667 String packageName = oemSatelliteMessagingApp.first; 668 String className = oemSatelliteMessagingApp.second; 669 String action = getSatelliteEmergencyHandoverIntentActionFromOverlayConfig(mContext); 670 671 if (isSatelliteViaCarrierAvailable() 672 || isEmergencyCallToSatelliteHandoverTypeT911Enforced()) { 673 ComponentName defaultSmsAppComponent = getDefaultSmsApp(); 674 handoverType = EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911; 675 packageName = defaultSmsAppComponent.getPackageName(); 676 className = defaultSmsAppComponent.getClassName(); 677 } 678 plogd("EVENT_DISPLAY_EMERGENCY_MESSAGE: handoverType=" + handoverType + ", packageName=" 679 + packageName + ", className=" + className + ", action=" + action); 680 681 Bundle result = new Bundle(); 682 result.putInt(EXTRA_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE, handoverType); 683 if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) { 684 result.putParcelable(EXTRA_EMERGENCY_CALL_TO_SATELLITE_LAUNCH_INTENT, 685 createHandoverAppLaunchPendingIntent( 686 handoverType, packageName, className, action)); 687 } 688 return result; 689 } 690 createHandoverAppLaunchPendingIntent(int handoverType, @NonNull String packageName, @NonNull String className, @Nullable String action)691 @NonNull private PendingIntent createHandoverAppLaunchPendingIntent(int handoverType, 692 @NonNull String packageName, @NonNull String className, @Nullable String action) { 693 Intent intent; 694 if (handoverType == EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911) { 695 String emergencyNumber = "911"; 696 if (mEmergencyConnection != null) { 697 emergencyNumber = mEmergencyConnection.getAddress().getSchemeSpecificPart(); 698 } 699 plogd("emergencyNumber=" + emergencyNumber); 700 701 Uri uri = Uri.parse("smsto:" + emergencyNumber); 702 intent = new Intent(Intent.ACTION_SENDTO, uri); 703 } else { 704 intent = new Intent(action); 705 intent.addFlags(FLAG_ACTIVITY_CLEAR_TOP); 706 } 707 Bundle activityOptions = ActivityOptions.makeBasic() 708 .setPendingIntentCreatorBackgroundActivityStartMode( 709 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) 710 .toBundle(); 711 intent.setComponent(new ComponentName(packageName, className)); 712 return PendingIntent.getActivity(mContext, 0, intent, 713 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE, activityOptions); 714 } 715 isEmergencyCallToSatelliteHandoverTypeT911Enforced()716 private boolean isEmergencyCallToSatelliteHandoverTypeT911Enforced() { 717 return (mSatelliteController.getEnforcedEmergencyCallToSatelliteHandoverType() 718 == EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911); 719 } 720 sendEventDisplayEmergencyMessageForcefully(@onNull Connection connection)721 private boolean sendEventDisplayEmergencyMessageForcefully(@NonNull Connection connection) { 722 if (mSatelliteController.getEnforcedEmergencyCallToSatelliteHandoverType() 723 == INVALID_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE) { 724 return false; 725 } 726 727 long delaySeconds = mSatelliteController.getDelayInSendingEventDisplayEmergencyMessage(); 728 sendMessageDelayed( 729 obtainMessage(CMD_SEND_EVENT_DISPLAY_EMERGENCY_MESSAGE_FORCEFULLY, connection), 730 delaySeconds * 1000); 731 return true; 732 } 733 handleCmdSendEventDisplayEmergencyMessageForcefully( @onNull Connection connection)734 private void handleCmdSendEventDisplayEmergencyMessageForcefully( 735 @NonNull Connection connection) { 736 plogd("Sent EVENT_DISPLAY_EMERGENCY_MESSAGE to Dialer forcefully."); 737 mEmergencyConnection = connection; 738 Bundle extras = createExtraBundleForEventDisplayEmergencyMessage(); 739 connection.sendConnectionEvent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE, extras); 740 mEmergencyConnection = null; 741 } 742 isMultiSim()743 private boolean isMultiSim() { 744 TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class); 745 if (telephonyManager == null) { 746 ploge("isMultiSim: telephonyManager is null"); 747 return false; 748 } 749 return telephonyManager.isMultiSimEnabled(); 750 } 751 getEmergencyCallToSatelliteHandoverType()752 private int getEmergencyCallToSatelliteHandoverType() { 753 if (isSatelliteViaCarrierAvailable()) { 754 return EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911; 755 } else { 756 return EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS; 757 } 758 } 759 requestIsSatelliteAllowedForCurrentLocation()760 private void requestIsSatelliteAllowedForCurrentLocation() { 761 synchronized (mLock) { 762 if (mCheckingAccessRestrictionInProgress) { 763 plogd("requestIsSatelliteCommunicationAllowedForCurrentLocation was already sent"); 764 return; 765 } 766 mCheckingAccessRestrictionInProgress = true; 767 } 768 769 OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> callback = 770 new OutcomeReceiver<>() { 771 @Override 772 public void onResult(Boolean result) { 773 plogd("requestIsSatelliteAllowedForCurrentLocation: result=" + result); 774 sendMessage(obtainMessage( 775 EVENT_SATELLITE_ACCESS_RESTRICTION_CHECKING_RESULT, result)); 776 } 777 778 @Override 779 public void onError(SatelliteManager.SatelliteException ex) { 780 plogd("requestIsSatelliteAllowedForCurrentLocation: onError, ex=" + ex); 781 sendMessage(obtainMessage( 782 EVENT_SATELLITE_ACCESS_RESTRICTION_CHECKING_RESULT, false)); 783 } 784 }; 785 requestIsSatelliteCommunicationAllowedForCurrentLocation(callback); 786 } 787 788 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) requestIsSatelliteCommunicationAllowedForCurrentLocation( @onNull OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> callback)789 protected void requestIsSatelliteCommunicationAllowedForCurrentLocation( 790 @NonNull OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> callback) { 791 SatelliteManager satelliteManager = mContext.getSystemService(SatelliteManager.class); 792 satelliteManager.requestIsCommunicationAllowedForCurrentLocation( 793 this::post, callback); 794 } 795 isMockModemAllowed()796 private static boolean isMockModemAllowed() { 797 return (SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false) 798 || SystemProperties.getBoolean(BOOT_ALLOW_MOCK_MODEM_PROPERTY, false)); 799 } 800 isSatelliteSupported()801 private boolean isSatelliteSupported() { 802 if (mSatelliteController.isSatelliteEmergencyMessagingSupportedViaCarrier()) return true; 803 if (mSatelliteController.isSatelliteSupportedViaOem() && isSatelliteViaOemProvisioned()) { 804 return true; 805 } 806 return false; 807 } 808 isSatelliteViaOemProvisioned()809 private boolean isSatelliteViaOemProvisioned() { 810 Boolean provisioned = mSatelliteController.isSatelliteViaOemProvisioned(); 811 return (provisioned != null) && provisioned; 812 } 813 logv(@onNull String log)814 private static void logv(@NonNull String log) { 815 Rlog.v(TAG, log); 816 } 817 logd(@onNull String log)818 private static void logd(@NonNull String log) { 819 Rlog.d(TAG, log); 820 } 821 loge(@onNull String log)822 private static void loge(@NonNull String log) { 823 Rlog.e(TAG, log); 824 } 825 isSatellitePersistentLoggingEnabled( @onNull Context context)826 private boolean isSatellitePersistentLoggingEnabled( 827 @NonNull Context context) { 828 if (satellitePersistentLogging()) { 829 return true; 830 } 831 try { 832 return context.getResources().getBoolean( 833 R.bool.config_dropboxmanager_persistent_logging_enabled); 834 } catch (RuntimeException e) { 835 return false; 836 } 837 } 838 plogd(@onNull String log)839 private void plogd(@NonNull String log) { 840 Rlog.d(TAG, log); 841 if (mPersistentLogger != null) { 842 mPersistentLogger.debug(TAG, log); 843 } 844 } 845 ploge(@onNull String log)846 private void ploge(@NonNull String log) { 847 Rlog.e(TAG, log); 848 if (mPersistentLogger != null) { 849 mPersistentLogger.error(TAG, log); 850 } 851 } 852 } 853