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