content: /*
  * Copyright (C) 2015 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
  
  package com.android.internal.telephony;
  
  import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
  import static android.telephony.CarrierConfigManager.KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG;
  import static android.telephony.SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
  import static android.telephony.SubscriptionManager.INVALID_PHONE_INDEX;
  import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
  import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION;
  import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_SUCCESS;
  import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED;
  
  import android.annotation.UnsupportedAppUsage;
  import android.content.BroadcastReceiver;
  import android.content.Context;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.net.ConnectivityManager;
  import android.net.MatchAllNetworkSpecifier;
  import android.net.Network;
  import android.net.NetworkCapabilities;
  import android.net.NetworkFactory;
  import android.net.NetworkRequest;
  import android.net.NetworkSpecifier;
  import android.net.StringNetworkSpecifier;
  import android.os.AsyncResult;
  import android.os.Handler;
  import android.os.Looper;
  import android.os.Message;
  import android.os.PersistableBundle;
  import android.os.Registrant;
  import android.os.RegistrantList;
  import android.os.RemoteException;
  import android.os.ServiceManager;
  import android.telephony.CarrierConfigManager;
  import android.telephony.PhoneCapability;
  import android.telephony.PhoneStateListener;
  import android.telephony.Rlog;
  import android.telephony.SubscriptionManager;
  import android.telephony.TelephonyManager;
  import android.telephony.data.ApnSetting;
  import android.util.LocalLog;
  
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.internal.telephony.dataconnection.DcRequest;
  import com.android.internal.telephony.metrics.TelephonyMetrics;
  import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent;
  import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.DataSwitch;
  import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.OnDemandDataSwitch;
  import com.android.internal.util.IndentingPrintWriter;
  
  import java.io.FileDescriptor;
  import java.io.PrintWriter;
  import java.util.ArrayList;
  import java.util.Calendar;
  import java.util.Collections;
  import java.util.List;
  import java.util.concurrent.CompletableFuture;
  
  /**
   * Utility singleton to monitor subscription changes and incoming NetworkRequests
   * and determine which phone/phones are active.
   *
   * Manages the ALLOW_DATA calls to modems and notifies phones about changes to
   * the active phones.  Note we don't wait for data attach (which may not happen anyway).
   */
  public class PhoneSwitcher extends Handler {
      private static final String LOG_TAG = "PhoneSwitcher";
      private static final boolean VDBG = false;
  
      private static final int DEFAULT_NETWORK_CHANGE_TIMEOUT_MS = 5000;
      private static final int MODEM_COMMAND_RETRY_PERIOD_MS     = 5000;
      // After the emergency call ends, wait for a few seconds to see if we enter ECBM before starting
      // the countdown to remove the emergency DDS override.
      @VisibleForTesting
      // not final for testing.
      public static int ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS = 5000;
      // Wait for a few seconds after the override request comes in to receive the outgoing call
      // event. If it does not happen before the timeout specified, cancel the override.
      @VisibleForTesting
      public static int DEFAULT_DATA_OVERRIDE_TIMEOUT_MS = 5000;
  
      // If there are no subscriptions in a device, then the phone to be used for emergency should
      // always be the "first" phone.
      private static final int DEFAULT_EMERGENCY_PHONE_ID = 0;
  
      /**
       * Container for an ongoing request to override the DDS in the context of an ongoing emergency
       * call to allow for carrier specific operations, such as provide SUPL updates during or after
       * the emergency call, since some modems do not support these operations on the non DDS.
       */
      private static final class EmergencyOverrideRequest {
          /* The Phone ID that the DDS should be set to. */
          int mPhoneId = INVALID_PHONE_INDEX;
          /* The time after the emergency call ends that the DDS should be overridden for. */
          int mGnssOverrideTimeMs = -1;
          /* A callback to the requester notifying them if the initial call to the modem to override
           * the DDS was successful.
           */
          CompletableFuture<Boolean> mOverrideCompleteFuture;
          /* In the special case that the device goes into emergency callback mode after the emergency
           * call ends, keep the override until ECM finishes and then start the mGnssOverrideTimeMs
           * timer to leave DDS override.
           */
          boolean mRequiresEcmFinish = false;
  
          /*
           * Keeps track of whether or not this request has already serviced the outgoing emergency
           * call. Once finished, do not delay for any other calls.
           */
          boolean mPendingOriginatingCall = true;
  
          /**
           * @return true if there is a pending override complete callback.
           */
          boolean isCallbackAvailable() {
              return mOverrideCompleteFuture != null;
          }
  
          /**
           * Send the override complete callback the result of setting the DDS to the new value.
           */
          void sendOverrideCompleteCallbackResultAndClear(boolean result) {
              if (isCallbackAvailable()) {
                  mOverrideCompleteFuture.complete(result);
                  mOverrideCompleteFuture = null;
              }
          }
  
  
          @Override
          public String toString() {
              return String.format("EmergencyOverrideRequest: [phoneId= %d, overrideMs= %d,"
                      + " hasCallback= %b, ecmFinishStatus= %b]", mPhoneId, mGnssOverrideTimeMs,
                      isCallbackAvailable(), mRequiresEcmFinish);
          }
      }
  
      private final List<DcRequest> mPrioritizedDcRequests = new ArrayList<DcRequest>();
      private final RegistrantList mActivePhoneRegistrants;
      private final SubscriptionController mSubscriptionController;
      private final int[] mPhoneSubscriptions;
      private final CommandsInterface[] mCommandsInterfaces;
      private final Context mContext;
      private final PhoneState[] mPhoneStates;
      @UnsupportedAppUsage
      private final int mNumPhones;
      @UnsupportedAppUsage
      private final Phone[] mPhones;
      private final LocalLog mLocalLog;
      @VisibleForTesting
      public final PhoneStateListener mPhoneStateListener;
      private final CellularNetworkValidator mValidator;
      @VisibleForTesting
      public final CellularNetworkValidator.ValidationCallback mValidationCallback =
              (validated, subId) -> Message.obtain(PhoneSwitcher.this,
                      EVENT_NETWORK_VALIDATION_DONE, subId, validated ? 1 : 0).sendToTarget();
      @UnsupportedAppUsage
      private int mMaxActivePhones;
      private static PhoneSwitcher sPhoneSwitcher = null;
  
      // Which primary (non-opportunistic) subscription is set as data subscription among all primary
      // subscriptions. This value usually comes from user setting, and it's the subscription used for
      // Internet data if mOpptDataSubId is not set.
      private int mPrimaryDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
  
      // mOpptDataSubId must be an active subscription. If it's set, it overrides mPrimaryDataSubId
      // to be used for Internet data.
      private int mOpptDataSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
  
      // The phone ID that has an active voice call. If set, and its mobile data setting is on,
      // it will become the mPreferredDataPhoneId.
      private int mPhoneIdInVoiceCall = SubscriptionManager.INVALID_PHONE_INDEX;
  
      @VisibleForTesting
      // It decides:
      // 1. In modem layer, which modem is DDS (preferred to have data traffic on)
      // 2. In TelephonyNetworkFactory, which subscription will apply default network requests, which
      //    are requests without specifying a subId.
      // Corresponding phoneId after considering mOpptDataSubId, mPrimaryDataSubId and
      // mPhoneIdInVoiceCall above.
      protected int mPreferredDataPhoneId = SubscriptionManager.INVALID_PHONE_INDEX;
  
      // Subscription ID corresponds to mPreferredDataPhoneId.
      private int mPreferredDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
  
      // If non-null, An emergency call is about to be started, is ongoing, or has just ended and we
      // are overriding the DDS.
      // Internal state, should ONLY be accessed/modified inside of the handler.
      private EmergencyOverrideRequest mEmergencyOverride;
  
      private ISetOpportunisticDataCallback mSetOpptSubCallback;
  
      private static final int EVENT_PRIMARY_DATA_SUB_CHANGED       = 101;
      private static final int EVENT_SUBSCRIPTION_CHANGED           = 102;
      private static final int EVENT_REQUEST_NETWORK                = 103;
      private static final int EVENT_RELEASE_NETWORK                = 104;
      // ECBM has started/ended. If we just ended an emergency call and mEmergencyOverride is not
      // null, we will wait for EVENT_EMERGENCY_TOGGLE again with ECBM ending to send the message
      // EVENT_REMOVE_DDS_EMERGENCY_OVERRIDE to remove the override after the mEmergencyOverride
      // override timer ends.
      private static final int EVENT_EMERGENCY_TOGGLE               = 105;
      private static final int EVENT_RADIO_CAPABILITY_CHANGED       = 106;
      private static final int EVENT_OPPT_DATA_SUB_CHANGED          = 107;
      private static final int EVENT_RADIO_AVAILABLE                = 108;
      // A call has either started or ended. If an emergency ended and DDS is overridden using
      // mEmergencyOverride, start the countdown to remove the override using the message
      // EVENT_REMOVE_DDS_EMERGENCY_OVERRIDE. The only exception to this is if the device moves to
      // ECBM, which is detected by EVENT_EMERGENCY_TOGGLE.
      @VisibleForTesting
      public static final int EVENT_PRECISE_CALL_STATE_CHANGED      = 109;
      private static final int EVENT_NETWORK_VALIDATION_DONE        = 110;
      private static final int EVENT_REMOVE_DEFAULT_NETWORK_CHANGE_CALLBACK = 111;
      private static final int EVENT_MODEM_COMMAND_DONE             = 112;
      private static final int EVENT_MODEM_COMMAND_RETRY            = 113;
      @VisibleForTesting
      public static final int EVENT_DATA_ENABLED_CHANGED            = 114;
      // An emergency call is about to be originated and requires the DDS to be overridden.
      // Uses EVENT_PRECISE_CALL_STATE_CHANGED message to start countdown to finish override defined
      // in mEmergencyOverride. If EVENT_PRECISE_CALL_STATE_CHANGED does not come in
      // DEFAULT_DATA_OVERRIDE_TIMEOUT_MS milliseconds, then the override will be removed.
      private static final int EVENT_OVERRIDE_DDS_FOR_EMERGENCY     = 115;
      // If it exists, remove the current mEmergencyOverride DDS override.
      private static final int EVENT_REMOVE_DDS_EMERGENCY_OVERRIDE  = 116;
  
      // Depending on version of IRadioConfig, we need to send either RIL_REQUEST_ALLOW_DATA if it's
      // 1.0, or RIL_REQUEST_SET_PREFERRED_DATA if it's 1.1 or later. So internally mHalCommandToUse
      // will be either HAL_COMMAND_ALLOW_DATA or HAL_COMMAND_ALLOW_DATA or HAL_COMMAND_UNKNOWN.
      private static final int HAL_COMMAND_UNKNOWN        = 0;
      private static final int HAL_COMMAND_ALLOW_DATA     = 1;
      private static final int HAL_COMMAND_PREFERRED_DATA = 2;
      private int mHalCommandToUse = HAL_COMMAND_UNKNOWN;
  
      private RadioConfig mRadioConfig;
  
      private final static int MAX_LOCAL_LOG_LINES = 30;
  
      // Default timeout value of network validation in millisecond.
      private final static int DEFAULT_VALIDATION_EXPIRATION_TIME = 2000;
  
      private Boolean mHasRegisteredDefaultNetworkChangeCallback = false;
  
      private ConnectivityManager mConnectivityManager;
  
      private class DefaultNetworkCallback extends ConnectivityManager.NetworkCallback {
          public int mExpectedSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
          public int mSwitchReason = TelephonyEvent.DataSwitch.Reason.DATA_SWITCH_REASON_UNKNOWN;
          @Override
          public void onCapabilitiesChanged(Network network,
                  NetworkCapabilities networkCapabilities) {
              if (networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
                      && SubscriptionManager.isValidSubscriptionId(mExpectedSubId)
                      && mExpectedSubId == getSubIdFromNetworkSpecifier(
                              networkCapabilities.getNetworkSpecifier())) {
                  logDataSwitchEvent(
                          mExpectedSubId,
                          TelephonyEvent.EventState.EVENT_STATE_END,
                          mSwitchReason);
                  removeDefaultNetworkChangeCallback();
              }
          }
      }
  
      private final DefaultNetworkCallback mDefaultNetworkCallback = new DefaultNetworkCallback();
  
      /**
       * Method to get singleton instance.
       */
      public static PhoneSwitcher getInstance() {
          return sPhoneSwitcher;
      }
  
      /**
       * Method to create singleton instance.
       */
      public static PhoneSwitcher make(int maxActivePhones, int numPhones, Context context,
              SubscriptionController subscriptionController, Looper looper, ITelephonyRegistry tr,
              CommandsInterface[] cis, Phone[] phones) {
          if (sPhoneSwitcher == null) {
              sPhoneSwitcher = new PhoneSwitcher(maxActivePhones, numPhones, context,
                      subscriptionController, looper, tr, cis, phones);
          }
  
          return sPhoneSwitcher;
      }
  
      /** This constructor is only used for testing purpose. */
      @VisibleForTesting
      public PhoneSwitcher(int numPhones, Looper looper) {
          super(looper);
          mMaxActivePhones = 0;
          mSubscriptionController = null;
          mCommandsInterfaces = null;
          mContext = null;
          mPhoneStates = null;
          mPhones = null;
          mLocalLog = null;
          mActivePhoneRegistrants = null;
          mNumPhones = numPhones;
          mPhoneSubscriptions = new int[numPhones];
          mRadioConfig = RadioConfig.getInstance(mContext);
          mPhoneStateListener = new PhoneStateListener(looper) {
              public void onPhoneCapabilityChanged(PhoneCapability capability) {
                  onPhoneCapabilityChangedInternal(capability);
              }
          };
          mValidator = CellularNetworkValidator.getInstance();
      }
  
      private boolean isPhoneInVoiceCallChanged() {
          int oldPhoneIdInVoiceCall = mPhoneIdInVoiceCall;
          // If there's no active call, the value will become INVALID_PHONE_INDEX
          // and internet data will be switched back to system selected or user selected
          // subscription.
          mPhoneIdInVoiceCall = SubscriptionManager.INVALID_PHONE_INDEX;
          for (Phone phone : mPhones) {
              if (isPhoneInVoiceCall(phone) || isPhoneInVoiceCall(phone.getImsPhone())) {
                  mPhoneIdInVoiceCall = phone.getPhoneId();
                  break;
              }
          }
  
          if (mPhoneIdInVoiceCall != oldPhoneIdInVoiceCall) {
              log("isPhoneInVoiceCallChanged from phoneId " + oldPhoneIdInVoiceCall
                      + " to phoneId " + mPhoneIdInVoiceCall);
              return true;
          } else {
              return false;
          }
      }
  
      @VisibleForTesting
      public PhoneSwitcher(int maxActivePhones, int numPhones, Context context,
              SubscriptionController subscriptionController, Looper looper, ITelephonyRegistry tr,
              CommandsInterface[] cis, Phone[] phones) {
          super(looper);
          mContext = context;
          mNumPhones = numPhones;
          mPhones = phones;
          mPhoneSubscriptions = new int[numPhones];
          mMaxActivePhones = maxActivePhones;
          mLocalLog = new LocalLog(MAX_LOCAL_LOG_LINES);
  
          mSubscriptionController = subscriptionController;
          mRadioConfig = RadioConfig.getInstance(mContext);
  
          mPhoneStateListener = new PhoneStateListener(looper) {
              @Override
              public void onPhoneCapabilityChanged(PhoneCapability capability) {
                  onPhoneCapabilityChangedInternal(capability);
              }
          };
  
          mValidator = CellularNetworkValidator.getInstance();
  
          mActivePhoneRegistrants = new RegistrantList();
          mPhoneStates = new PhoneState[numPhones];
          for (int i = 0; i < numPhones; i++) {
              mPhoneStates[i] = new PhoneState();
              if (mPhones[i] != null) {
                  mPhones[i].registerForEmergencyCallToggle(this, EVENT_EMERGENCY_TOGGLE, null);
                  // TODO (b/135566422): combine register for both GsmCdmaPhone and ImsPhone.
                  mPhones[i].registerForPreciseCallStateChanged(
                          this, EVENT_PRECISE_CALL_STATE_CHANGED, null);
                  if (mPhones[i].getImsPhone() != null) {
                      mPhones[i].getImsPhone().registerForPreciseCallStateChanged(
                              this, EVENT_PRECISE_CALL_STATE_CHANGED, null);
                  }
                  mPhones[i].getDataEnabledSettings().registerForDataEnabledChanged(
                          this, EVENT_DATA_ENABLED_CHANGED, null);
              }
          }
  
          mCommandsInterfaces = cis;
  
          if (numPhones > 0) {
              mCommandsInterfaces[0].registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
          }
  
          try {
              tr.addOnSubscriptionsChangedListener(context.getOpPackageName(),
                      mSubscriptionsChangedListener);
          } catch (RemoteException e) {
          }
  
          mConnectivityManager =
              (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
  
          mContext.registerReceiver(mDefaultDataChangedReceiver,
                  new IntentFilter(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED));
  
          NetworkCapabilities netCap = new NetworkCapabilities();
          netCap.addTransportType(TRANSPORT_CELLULAR);
          netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
          netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL);
          netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
          netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_FOTA);
          netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
          netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_CBS);
          netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_IA);
          netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_RCS);
          netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_XCAP);
          netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS);
          netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
          netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
          netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_MCX);
          netCap.setNetworkSpecifier(new MatchAllNetworkSpecifier());
  
          NetworkFactory networkFactory = new PhoneSwitcherNetworkRequestListener(looper, context,
                  netCap, this);
          // we want to see all requests
          networkFactory.setScoreFilter(101);
          networkFactory.register();
  
          log("PhoneSwitcher started");
      }
  
      private final BroadcastReceiver mDefaultDataChangedReceiver = new BroadcastReceiver() {
          @Override
          public void onReceive(Context context, Intent intent) {
              Message msg = PhoneSwitcher.this.obtainMessage(EVENT_PRIMARY_DATA_SUB_CHANGED);
              msg.sendToTarget();
          }
      };
  
      private final IOnSubscriptionsChangedListener mSubscriptionsChangedListener =
              new IOnSubscriptionsChangedListener.Stub() {
          @Override
          public void onSubscriptionsChanged() {
              Message msg = PhoneSwitcher.this.obtainMessage(EVENT_SUBSCRIPTION_CHANGED);
              msg.sendToTarget();
          }
      };
  
      @Override
      public void handleMessage(Message msg) {
          switch (msg.what) {
              case EVENT_SUBSCRIPTION_CHANGED: {
                  onEvaluate(REQUESTS_UNCHANGED, "subChanged");
                  break;
              }
              case EVENT_PRIMARY_DATA_SUB_CHANGED: {
                  if (onEvaluate(REQUESTS_UNCHANGED, "primary data subId changed")) {
                      logDataSwitchEvent(mPreferredDataSubId,
                              TelephonyEvent.EventState.EVENT_STATE_START,
                              DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL);
                      registerDefaultNetworkChangeCallback(mPreferredDataSubId,
                              DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL);
                  }
                  break;
              }
              case EVENT_REQUEST_NETWORK: {
                  onRequestNetwork((NetworkRequest)msg.obj);
                  break;
              }
              case EVENT_RELEASE_NETWORK: {
                  onReleaseNetwork((NetworkRequest)msg.obj);
                  break;
              }
              case EVENT_EMERGENCY_TOGGLE: {
                  boolean isInEcm = isInEmergencyCallbackMode();
                  if (mEmergencyOverride != null) {
                      log("Emergency override - ecbm status = " + isInEcm);
                      if (isInEcm) {
                          // The device has gone into ECBM. Wait until it's out.
                          removeMessages(EVENT_REMOVE_DDS_EMERGENCY_OVERRIDE);
                          mEmergencyOverride.mRequiresEcmFinish = true;
                      } else if (mEmergencyOverride.mRequiresEcmFinish) {
                          // we have exited ECM! Start the timer to exit DDS override.
                          Message msg2 = obtainMessage(EVENT_REMOVE_DDS_EMERGENCY_OVERRIDE);
                          sendMessageDelayed(msg2, mEmergencyOverride.mGnssOverrideTimeMs);
                      }
                  }
                  onEvaluate(REQUESTS_CHANGED, "emergencyToggle");
                  break;
              }
              case EVENT_RADIO_CAPABILITY_CHANGED: {
                  final int phoneId = msg.arg1;
                  sendRilCommands(phoneId);
                  break;
              }
              case EVENT_OPPT_DATA_SUB_CHANGED: {
                  int subId = msg.arg1;
                  boolean needValidation = (msg.arg2 == 1);
                  ISetOpportunisticDataCallback callback =
                          (ISetOpportunisticDataCallback) msg.obj;
                  setOpportunisticDataSubscription(subId, needValidation, callback);
                  break;
              }
              case EVENT_RADIO_AVAILABLE: {
                  updateHalCommandToUse();
                  onEvaluate(REQUESTS_UNCHANGED, "EVENT_RADIO_AVAILABLE");
                  break;
              }
              case EVENT_PRECISE_CALL_STATE_CHANGED: {
                  // If the phoneId in voice call didn't change, do nothing.
                  if (!isPhoneInVoiceCallChanged()) break;
  
                  // Only handle this event if we are currently waiting for the emergency call
                  // associated with the override request to start or end.
                  if (mEmergencyOverride != null && mEmergencyOverride.mPendingOriginatingCall) {
                      removeMessages(EVENT_REMOVE_DDS_EMERGENCY_OVERRIDE);
                      if (mPhoneIdInVoiceCall == SubscriptionManager.INVALID_PHONE_INDEX) {
                          // not in a call anymore.
                          Message msg2 = obtainMessage(EVENT_REMOVE_DDS_EMERGENCY_OVERRIDE);
                          sendMessageDelayed(msg2, mEmergencyOverride.mGnssOverrideTimeMs
                                  + ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS);
                          // Do not extend the emergency override by waiting for other calls to end.
                          // If it needs to be extended, a new request will come in and replace the
                          // current override.
                          mEmergencyOverride.mPendingOriginatingCall = false;
                      }
                  }
              }
              // fall through
              case EVENT_DATA_ENABLED_CHANGED:
                  if (onEvaluate(REQUESTS_UNCHANGED, "EVENT_PRECISE_CALL_STATE_CHANGED")) {
                      logDataSwitchEvent(mPreferredDataSubId,
                              TelephonyEvent.EventState.EVENT_STATE_START,
                              DataSwitch.Reason.DATA_SWITCH_REASON_IN_CALL);
                      registerDefaultNetworkChangeCallback(mPreferredDataSubId,
                              DataSwitch.Reason.DATA_SWITCH_REASON_IN_CALL);
                  }
                  break;
              case EVENT_NETWORK_VALIDATION_DONE: {
                  int subId = msg.arg1;
                  boolean passed = (msg.arg2 == 1);
                  onValidationDone(subId, passed);
                  break;
              }
              case EVENT_REMOVE_DEFAULT_NETWORK_CHANGE_CALLBACK: {
                  removeDefaultNetworkChangeCallback();
                  break;
              }
              case EVENT_MODEM_COMMAND_DONE: {
                  AsyncResult ar = (AsyncResult) msg.obj;
                  boolean commandSuccess = ar != null && ar.exception == null;
                  if (mEmergencyOverride != null) {
                      log("Emergency override result sent = " + commandSuccess);
                      mEmergencyOverride.sendOverrideCompleteCallbackResultAndClear(commandSuccess);
                      // Do not retry , as we do not allow changes in onEvaluate during an emergency
                      // call. When the call ends, we will start the countdown to remove the override.
                  } else if (!commandSuccess) {
                      int phoneId = (int) ar.userObj;
                      log("Modem command failed. with exception " + ar.exception);
                      sendMessageDelayed(Message.obtain(this, EVENT_MODEM_COMMAND_RETRY,
                              phoneId), MODEM_COMMAND_RETRY_PERIOD_MS);
                  }
                  break;
              }
              case EVENT_MODEM_COMMAND_RETRY: {
                  int phoneId = (int) msg.obj;
                  log("Resend modem command on phone " + phoneId);
                  sendRilCommands(phoneId);
                  break;
              }
              case EVENT_OVERRIDE_DDS_FOR_EMERGENCY: {
                  EmergencyOverrideRequest req = (EmergencyOverrideRequest) msg.obj;
                  if (mEmergencyOverride != null) {
                      // If an override request comes in for a different phone ID than what is already
                      // being overridden, ignore. We should not try to switch DDS while already
                      // waiting for SUPL.
                      if (mEmergencyOverride.mPhoneId != req.mPhoneId) {
                          log("emergency override requested for phone id " + req.mPhoneId + " when "
                                  + "there is already an override in place for phone id "
                                  + mEmergencyOverride.mPhoneId + ". Ignoring.");
                          if (req.isCallbackAvailable()) {
                              // Send failed result
                              req.mOverrideCompleteFuture.complete(false);
                          }
                          break;
                      } else {
                          if (mEmergencyOverride.isCallbackAvailable()) {
                              // Unblock any waiting overrides if a new request comes in before the
                              // previous one is processed.
                              mEmergencyOverride.mOverrideCompleteFuture.complete(false);
                          }
                      }
                      mEmergencyOverride = req;
                  } else {
                      mEmergencyOverride = req;
                  }
  
                  log("new emergency override - " + mEmergencyOverride);
                  // a new request has been created, remove any previous override complete scheduled.
                  removeMessages(EVENT_REMOVE_DDS_EMERGENCY_OVERRIDE);
                  Message msg2 = obtainMessage(EVENT_REMOVE_DDS_EMERGENCY_OVERRIDE);
                  // Make sure that if we never get an incall indication that we remove the override.
                  sendMessageDelayed(msg2, DEFAULT_DATA_OVERRIDE_TIMEOUT_MS);
                  // Wait for call to end and EVENT_PRECISE_CALL_STATE_CHANGED to be called, then
                  // start timer to remove DDS emergency override.
                  if (!onEvaluate(REQUESTS_UNCHANGED, "emer_override_dds")) {
                      // Nothing changed as a result of override, so no modem command was sent. Treat
                      // as success.
                      mEmergencyOverride.sendOverrideCompleteCallbackResultAndClear(true);
                      // Do not clear mEmergencyOverride here, as we still want to keep the override
                      // active for the time specified in case the user tries to switch default data.
                  }
                  break;
              }
              case EVENT_REMOVE_DDS_EMERGENCY_OVERRIDE: {
                  log("Emergency override removed - " + mEmergencyOverride);
                  mEmergencyOverride = null;
                  onEvaluate(REQUESTS_UNCHANGED, "emer_rm_override_dds");
                  break;
              }
          }
      }
  
      private boolean isInEmergencyCallbackMode() {
          for (Phone p : mPhones) {
              if (p == null) continue;
              if (p.isInEcm()) return true;
              Phone imsPhone = p.getImsPhone();
              if (imsPhone != null && imsPhone.isInEcm()) {
                  return true;
              }
          }
          return false;
      }
  
      private static class PhoneSwitcherNetworkRequestListener extends NetworkFactory {
          private final PhoneSwitcher mPhoneSwitcher;
          public PhoneSwitcherNetworkRequestListener (Looper l, Context c,
                  NetworkCapabilities nc, PhoneSwitcher ps) {
              super(l, c, "PhoneSwitcherNetworkRequstListener", nc);
              mPhoneSwitcher = ps;
          }
  
          @Override
          protected void needNetworkFor(NetworkRequest networkRequest, int score) {
              if (VDBG) log("needNetworkFor " + networkRequest + ", " + score);
              Message msg = mPhoneSwitcher.obtainMessage(EVENT_REQUEST_NETWORK);
              msg.obj = networkRequest;
              msg.sendToTarget();
          }
  
          @Override
          protected void releaseNetworkFor(NetworkRequest networkRequest) {
              if (VDBG) log("releaseNetworkFor " + networkRequest);
              Message msg = mPhoneSwitcher.obtainMessage(EVENT_RELEASE_NETWORK);
              msg.obj = networkRequest;
              msg.sendToTarget();
          }
      }
  
      private void onRequestNetwork(NetworkRequest networkRequest) {
          final DcRequest dcRequest = new DcRequest(networkRequest, mContext);
          if (!mPrioritizedDcRequests.contains(dcRequest)) {
              collectRequestNetworkMetrics(networkRequest);
              mPrioritizedDcRequests.add(dcRequest);
              Collections.sort(mPrioritizedDcRequests);
              onEvaluate(REQUESTS_CHANGED, "netRequest");
          }
      }
  
      private void onReleaseNetwork(NetworkRequest networkRequest) {
          final DcRequest dcRequest = new DcRequest(networkRequest, mContext);
  
          if (mPrioritizedDcRequests.remove(dcRequest)) {
              onEvaluate(REQUESTS_CHANGED, "netReleased");
              collectReleaseNetworkMetrics(networkRequest);
          }
      }
  
      private void removeDefaultNetworkChangeCallback() {
          removeMessages(EVENT_REMOVE_DEFAULT_NETWORK_CHANGE_CALLBACK);
          mDefaultNetworkCallback.mExpectedSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
          mDefaultNetworkCallback.mSwitchReason =
                  TelephonyEvent.DataSwitch.Reason.DATA_SWITCH_REASON_UNKNOWN;
          mConnectivityManager.unregisterNetworkCallback(mDefaultNetworkCallback);
      }
  
      private void registerDefaultNetworkChangeCallback(int expectedSubId, int reason) {
          mDefaultNetworkCallback.mExpectedSubId = expectedSubId;
          mDefaultNetworkCallback.mSwitchReason = reason;
          mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback, this);
          sendMessageDelayed(
                  obtainMessage(EVENT_REMOVE_DEFAULT_NETWORK_CHANGE_CALLBACK),
                  DEFAULT_NETWORK_CHANGE_TIMEOUT_MS);
      }
  
      private void collectRequestNetworkMetrics(NetworkRequest networkRequest) {
          // Request network for MMS will temporary disable the network on default data subscription,
          // this only happen on multi-sim device.
          if (mNumPhones > 1 && networkRequest.networkCapabilities.hasCapability(
                  NetworkCapabilities.NET_CAPABILITY_MMS)) {
              OnDemandDataSwitch onDemandDataSwitch = new OnDemandDataSwitch();
              onDemandDataSwitch.apn = TelephonyEvent.ApnType.APN_TYPE_MMS;
              onDemandDataSwitch.state = TelephonyEvent.EventState.EVENT_STATE_START;
              TelephonyMetrics.getInstance().writeOnDemandDataSwitch(onDemandDataSwitch);
          }
      }
  
      private void collectReleaseNetworkMetrics(NetworkRequest networkRequest) {
          // Release network for MMS will recover the network on default data subscription, this only
          // happen on multi-sim device.
          if (mNumPhones > 1 && networkRequest.networkCapabilities.hasCapability(
                  NetworkCapabilities.NET_CAPABILITY_MMS)) {
              OnDemandDataSwitch onDemandDataSwitch = new OnDemandDataSwitch();
              onDemandDataSwitch.apn = TelephonyEvent.ApnType.APN_TYPE_MMS;
              onDemandDataSwitch.state = TelephonyEvent.EventState.EVENT_STATE_END;
              TelephonyMetrics.getInstance().writeOnDemandDataSwitch(onDemandDataSwitch);
          }
      }
  
      private static final boolean REQUESTS_CHANGED   = true;
      private static final boolean REQUESTS_UNCHANGED = false;
      /**
       * Re-evaluate things. Do nothing if nothing's changed.
       *
       * Otherwise, go through the requests in priority order adding their phone until we've added up
       * to the max allowed.  Then go through shutting down phones that aren't in the active phone
       * list. Finally, activate all phones in the active phone list.
       *
       * @return {@code True} if the default data subscription need to be changed.
       */
      private boolean onEvaluate(boolean requestsChanged, String reason) {
          StringBuilder sb = new StringBuilder(reason);
  
          // If we use HAL_COMMAND_PREFERRED_DATA,
          boolean diffDetected = mHalCommandToUse != HAL_COMMAND_PREFERRED_DATA && requestsChanged;
  
          // Check if user setting of default non-opportunistic data sub is changed.
          final int primaryDataSubId = mSubscriptionController.getDefaultDataSubId();
          if (primaryDataSubId != mPrimaryDataSubId) {
              sb.append(" mPrimaryDataSubId ").append(mPrimaryDataSubId).append("->")
                  .append(primaryDataSubId);
              mPrimaryDataSubId = primaryDataSubId;
          }
  
          // Check to see if there is any active subscription on any phone
          boolean hasAnyActiveSubscription = false;
  
          // Check if phoneId to subId mapping is changed.
          for (int i = 0; i < mNumPhones; i++) {
              int sub = mSubscriptionController.getSubIdUsingPhoneId(i);
  
              if (SubscriptionManager.isValidSubscriptionId(sub)) hasAnyActiveSubscription = true;
  
              if (sub != mPhoneSubscriptions[i]) {
                  sb.append(" phone[").append(i).append("] ").append(mPhoneSubscriptions[i]);
                  sb.append("->").append(sub);
                  mPhoneSubscriptions[i] = sub;
                  diffDetected = true;
              }
          }
  
          if (!hasAnyActiveSubscription) {
              transitionToEmergencyPhone();
          } else {
              if (VDBG) log("Found an active subscription");
          }
  
          // Check if phoneId for preferred data is changed.
          int oldPreferredDataPhoneId = mPreferredDataPhoneId;
  
          // When there are no subscriptions, the preferred data phone ID is invalid, but we want
          // to keep a valid phoneId for Emergency, so skip logic that updates for preferred data
          // phone ID. Ideally there should be a single set of checks that evaluate the correct
          // phoneId on a service-by-service basis (EIMS being one), but for now... just bypass
          // this logic in the no-SIM case.
          if (hasAnyActiveSubscription) updatePreferredDataPhoneId();
  
          if (oldPreferredDataPhoneId != mPreferredDataPhoneId) {
              sb.append(" preferred phoneId ").append(oldPreferredDataPhoneId)
                      .append("->").append(mPreferredDataPhoneId);
              diffDetected = true;
          }
  
          if (diffDetected) {
              log("evaluating due to " + sb.toString());
              if (mHalCommandToUse == HAL_COMMAND_PREFERRED_DATA) {
                  // With HAL_COMMAND_PREFERRED_DATA, all phones are assumed to allow PS attach.
                  // So marking all phone as active, and the phone with mPreferredDataPhoneId
                  // will send radioConfig command.
                  for (int phoneId = 0; phoneId < mNumPhones; phoneId++) {
                      mPhoneStates[phoneId].active = true;
                  }
                  sendRilCommands(mPreferredDataPhoneId);
              } else {
                  List<Integer> newActivePhones = new ArrayList<Integer>();
  
                  /**
                   * If all phones can have PS attached, activate all.
                   * Otherwise, choose to activate phones according to requests. And
                   * if list is not full, add mPreferredDataPhoneId.
                   */
                  if (mMaxActivePhones == mPhones.length) {
                      for (int i = 0; i < mMaxActivePhones; i++) {
                          newActivePhones.add(mPhones[i].getPhoneId());
                      }
                  } else {
                      // First try to activate phone in voice call.
                      if (mPhoneIdInVoiceCall != SubscriptionManager.INVALID_PHONE_INDEX) {
                          newActivePhones.add(mPhoneIdInVoiceCall);
                      }
  
                      if (newActivePhones.size() < mMaxActivePhones) {
                          for (DcRequest dcRequest : mPrioritizedDcRequests) {
                              int phoneIdForRequest = phoneIdForRequest(dcRequest.networkRequest);
                              if (phoneIdForRequest == INVALID_PHONE_INDEX) continue;
                              if (newActivePhones.contains(phoneIdForRequest)) continue;
                              newActivePhones.add(phoneIdForRequest);
                              if (newActivePhones.size() >= mMaxActivePhones) break;
                          }
                      }
  
                      if (newActivePhones.size() < mMaxActivePhones
                              && newActivePhones.contains(mPreferredDataPhoneId)
                              && SubscriptionManager.isUsableSubIdValue(mPreferredDataPhoneId)) {
                          newActivePhones.add(mPreferredDataPhoneId);
                      }
                  }
  
                  if (VDBG) {
                      log("mPrimaryDataSubId = " + mPrimaryDataSubId);
                      log("mOpptDataSubId = " + mOpptDataSubId);
                      for (int i = 0; i < mNumPhones; i++) {
                          log(" phone[" + i + "] using sub[" + mPhoneSubscriptions[i] + "]");
                      }
                      log(" newActivePhones:");
                      for (Integer i : newActivePhones) log("  " + i);
                  }
  
                  for (int phoneId = 0; phoneId < mNumPhones; phoneId++) {
                      if (!newActivePhones.contains(phoneId)) {
                          deactivate(phoneId);
                      }
                  }
  
                  // only activate phones up to the limit
                  for (int phoneId : newActivePhones) {
                      activate(phoneId);
                  }
              }
  
              notifyPreferredDataSubIdChanged();
  
              // Notify all registrants.
              mActivePhoneRegistrants.notifyRegistrants();
          }
          return diffDetected;
      }
  
      private static class PhoneState {
          public volatile boolean active = false;
          public long lastRequested = 0;
      }
  
      @UnsupportedAppUsage
      private void activate(int phoneId) {
          switchPhone(phoneId, true);
      }
  
      @UnsupportedAppUsage
      private void deactivate(int phoneId) {
          switchPhone(phoneId, false);
      }
  
      private void switchPhone(int phoneId, boolean active) {
          PhoneState state = mPhoneStates[phoneId];
          if (state.active == active) return;
          state.active = active;
          log((active ? "activate " : "deactivate ") + phoneId);
          state.lastRequested = System.currentTimeMillis();
          sendRilCommands(phoneId);
      }
  
      /**
       * Used when the modem may have been rebooted and we
       * want to resend setDataAllowed or setPreferredDataSubscriptionId
       */
      public void onRadioCapChanged(int phoneId) {
          validatePhoneId(phoneId);
          Message msg = obtainMessage(EVENT_RADIO_CAPABILITY_CHANGED);
          msg.arg1 = phoneId;
          msg.sendToTarget();
      }
  
      /**
       * Switch the Default data for the context of an outgoing emergency call.
       *
       * In some cases, we need to try to switch the Default Data subscription before placing the
       * emergency call on DSDS devices. This includes the following situation:
       * - The modem does not support processing GNSS SUPL requests on the non-default data
       * subscription. For some carriers that do not provide a control plane fallback mechanism, the
       * SUPL request will be dropped and we will not be able to get the user's location for the
       * emergency call. In this case, we need to swap default data temporarily.
       * @param phoneId The phone to use to evaluate whether or not the default data should be moved
       *                to this subscription.
       * @param overrideTimeSec The amount of time to override the default data setting for after the
       *                       emergency call ends.
       * @param dataSwitchResult A {@link CompletableFuture} to be called with a {@link Boolean}
       *                         result when the default data switch has either completed (true) or
       *                         failed (false).
       */
      public void overrideDefaultDataForEmergency(int phoneId, int overrideTimeSec,
              CompletableFuture<Boolean> dataSwitchResult) {
          validatePhoneId(phoneId);
          Message msg = obtainMessage(EVENT_OVERRIDE_DDS_FOR_EMERGENCY);
          EmergencyOverrideRequest request  = new EmergencyOverrideRequest();
          request.mPhoneId = phoneId;
          request.mGnssOverrideTimeMs = overrideTimeSec * 1000;
          request.mOverrideCompleteFuture = dataSwitchResult;
          msg.obj = request;
          msg.sendToTarget();
      }
  
      private void sendRilCommands(int phoneId) {
          if (!SubscriptionManager.isValidPhoneId(phoneId) || phoneId >= mNumPhones) return;
  
          Message message = Message.obtain(this, EVENT_MODEM_COMMAND_DONE, phoneId);
          if (mHalCommandToUse == HAL_COMMAND_ALLOW_DATA || mHalCommandToUse == HAL_COMMAND_UNKNOWN) {
              // Skip ALLOW_DATA for single SIM device
              if (mNumPhones > 1) {
                  mCommandsInterfaces[phoneId].setDataAllowed(isPhoneActive(phoneId), message);
              }
          } else if (phoneId == mPreferredDataPhoneId) {
              // Only setPreferredDataModem if the phoneId equals to current mPreferredDataPhoneId.
              mRadioConfig.setPreferredDataModem(mPreferredDataPhoneId, message);
          }
      }
  
      private void onPhoneCapabilityChangedInternal(PhoneCapability capability) {
          int newMaxActivePhones = TelephonyManager.getDefault()
                  .getNumberOfModemsWithSimultaneousDataConnections();
          if (mMaxActivePhones != newMaxActivePhones) {
              mMaxActivePhones = newMaxActivePhones;
              log("Max active phones changed to " + mMaxActivePhones);
              onEvaluate(REQUESTS_UNCHANGED, "phoneCfgChanged");
          }
      }
  
      private int phoneIdForRequest(NetworkRequest netRequest) {
          int subId = getSubIdFromNetworkSpecifier(netRequest.networkCapabilities
                  .getNetworkSpecifier());
  
          if (subId == DEFAULT_SUBSCRIPTION_ID) return mPreferredDataPhoneId;
          if (subId == INVALID_SUBSCRIPTION_ID) return INVALID_PHONE_INDEX;
  
          int preferredDataSubId = SubscriptionManager.isValidPhoneId(mPreferredDataPhoneId)
                  ? mPhoneSubscriptions[mPreferredDataPhoneId] : INVALID_SUBSCRIPTION_ID;
  
          // Currently we assume multi-SIM devices will only support one Internet PDN connection. So
          // if Internet PDN is established on the non-preferred phone, it will interrupt
          // Internet connection on the preferred phone. So we only accept Internet request with
          // preferred data subscription or no specified subscription.
          // One exception is, if it's restricted request (doesn't have NET_CAPABILITY_NOT_RESTRICTED)
          // it will be accepted, which is used temporary data usage from system.
          if (netRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                  && netRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
                  && subId != preferredDataSubId && subId != mValidator.getSubIdInValidation()) {
              // Returning INVALID_PHONE_INDEX will result in netRequest not being handled.
              return INVALID_PHONE_INDEX;
          }
  
          // Try to find matching phone ID. If it doesn't exist, we'll end up returning INVALID.
          int phoneId = INVALID_PHONE_INDEX;
          for (int i = 0; i < mNumPhones; i++) {
              if (mPhoneSubscriptions[i] == subId) {
                  phoneId = i;
                  break;
              }
          }
          return phoneId;
      }
  
      private int getSubIdFromNetworkSpecifier(NetworkSpecifier specifier) {
          if (specifier == null) {
              return DEFAULT_SUBSCRIPTION_ID;
          }
  
          int subId;
  
          if (specifier instanceof StringNetworkSpecifier) {
              try {
                  subId = Integer.parseInt(((StringNetworkSpecifier) specifier).specifier);
              } catch (NumberFormatException e) {
                  Rlog.e(LOG_TAG, "NumberFormatException on "
                          + ((StringNetworkSpecifier) specifier).specifier);
                  return INVALID_SUBSCRIPTION_ID;
              }
          } else {
              return INVALID_SUBSCRIPTION_ID;
          }
  
          return subId;
      }
  
      private int getSubIdForDefaultNetworkRequests() {
          if (mSubscriptionController.isActiveSubId(mOpptDataSubId)) {
              return mOpptDataSubId;
          } else {
              return mPrimaryDataSubId;
          }
      }
  
      // This updates mPreferredDataPhoneId which decides which phone should handle default network
      // requests.
      private void updatePreferredDataPhoneId() {
          Phone voicePhone = findPhoneById(mPhoneIdInVoiceCall);
          if (mEmergencyOverride != null && findPhoneById(mEmergencyOverride.mPhoneId) != null) {
              // Override DDS for emergency even if user data is not enabled, since it is an
              // emergency.
              // TODO: Provide a notification to the user that metered data is currently being
              // used during this period.
              log("updatePreferredDataPhoneId: preferred data overridden for emergency."
                      + " phoneId = " + mEmergencyOverride.mPhoneId);
              mPreferredDataPhoneId = mEmergencyOverride.mPhoneId;
          } else if (voicePhone != null && voicePhone.getDataEnabledSettings().isDataEnabled(
                  ApnSetting.TYPE_DEFAULT)) {
              // If a phone is in call and user enabled its mobile data, we
              // should switch internet connection to it. Because the other modem
              // will lose data connection anyway.
              // TODO: validate network first.
              mPreferredDataPhoneId = mPhoneIdInVoiceCall;
          } else {
              int subId = getSubIdForDefaultNetworkRequests();
              int phoneId = SubscriptionManager.INVALID_PHONE_INDEX;
  
              if (SubscriptionManager.isUsableSubIdValue(subId)) {
                  for (int i = 0; i < mNumPhones; i++) {
                      if (mPhoneSubscriptions[i] == subId) {
                          phoneId = i;
                          break;
                      }
                  }
              }
  
              mPreferredDataPhoneId = phoneId;
          }
  
          mPreferredDataSubId = mSubscriptionController.getSubIdUsingPhoneId(mPreferredDataPhoneId);
      }
  
      private void transitionToEmergencyPhone() {
          if (mPreferredDataPhoneId != DEFAULT_EMERGENCY_PHONE_ID) {
              log("No active subscriptions: resetting preferred phone to 0 for emergency");
              mPreferredDataPhoneId = DEFAULT_EMERGENCY_PHONE_ID;
          }
  
          if (mPreferredDataSubId != INVALID_SUBSCRIPTION_ID) {
              mPreferredDataSubId = INVALID_SUBSCRIPTION_ID;
              notifyPreferredDataSubIdChanged();
          }
      }
  
      private Phone findPhoneById(final int phoneId) {
          if (phoneId < 0 || phoneId >= mNumPhones) {
              return null;
          }
          return mPhones[phoneId];
      }
  
      public boolean shouldApplyNetworkRequest(NetworkRequest networkRequest, int phoneId) {
          validatePhoneId(phoneId);
  
          // In any case, if phone state is inactive, don't apply the network request.
          if (!isPhoneActive(phoneId) || (
                  mSubscriptionController.getSubIdUsingPhoneId(phoneId) == INVALID_SUBSCRIPTION_ID
                  && !isEmergencyNetworkRequest(networkRequest))) {
              return false;
          }
  
          int phoneIdToHandle = phoneIdForRequest(networkRequest);
  
          return phoneId == phoneIdToHandle;
      }
  
      boolean isEmergencyNetworkRequest(NetworkRequest networkRequest) {
          return networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_EIMS);
      }
  
      @VisibleForTesting
      protected boolean isPhoneActive(int phoneId) {
          return mPhoneStates[phoneId].active;
      }
  
      /**
       * If preferred phone changes, or phone activation status changes, registrants
       * will be notified.
       */
      public void registerForActivePhoneSwitch(Handler h, int what, Object o) {
          Registrant r = new Registrant(h, what, o);
          mActivePhoneRegistrants.add(r);
          r.notifyRegistrant();
      }
  
      public void unregisterForActivePhoneSwitch(Handler h) {
          mActivePhoneRegistrants.remove(h);
      }
  
      @VisibleForTesting
      protected void validatePhoneId(int phoneId) {
          if (phoneId < 0 || phoneId >= mNumPhones) {
              throw new IllegalArgumentException("Invalid PhoneId");
          }
      }
  
      /**
       * Set opportunistic data subscription. It's an indication to switch Internet data to this
       * subscription. It has to be an active subscription, and PhoneSwitcher will try to validate
       * it first if needed. If subId is DEFAULT_SUBSCRIPTION_ID, it means we are un-setting
       * opportunistic data sub and switch data back to primary sub.
       *
       * @param subId the opportunistic data subscription to switch to. pass DEFAULT_SUBSCRIPTION_ID
       *              if un-setting it.
       * @param needValidation whether Telephony will wait until the network is validated by
       *              connectivity service before switching data to it. More details see
       *              {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED}.
       * @param callback Callback will be triggered once it succeeds or failed.
       *                 Pass null if don't care about the result.
       */
      private void setOpportunisticDataSubscription(int subId, boolean needValidation,
              ISetOpportunisticDataCallback callback) {
          if (!mSubscriptionController.isActiveSubId(subId)
                  && subId != SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
              log("Can't switch data to inactive subId " + subId);
              sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION);
              return;
          }
  
          // Remove EVENT_NETWORK_VALIDATION_DONE. Don't handle validation result of previously subId
          // if queued.
          removeMessages(EVENT_NETWORK_VALIDATION_DONE);
  
          int subIdToValidate = (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)
                  ? mPrimaryDataSubId : subId;
  
          if (mValidator.isValidating()) {
              mValidator.stopValidation();
              sendSetOpptCallbackHelper(mSetOpptSubCallback, SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED);
              mSetOpptSubCallback = null;
          }
  
          if (subId == mOpptDataSubId) {
              sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_SUCCESS);
              return;
          }
  
          logDataSwitchEvent(subId == DEFAULT_SUBSCRIPTION_ID ? mPrimaryDataSubId : subId,
                  TelephonyEvent.EventState.EVENT_STATE_START,
                  DataSwitch.Reason.DATA_SWITCH_REASON_CBRS);
          registerDefaultNetworkChangeCallback(
                  subId == DEFAULT_SUBSCRIPTION_ID ? mPrimaryDataSubId : subId,
                  DataSwitch.Reason.DATA_SWITCH_REASON_CBRS);
  
          // If validation feature is not supported, set it directly. Otherwise,
          // start validation on the subscription first.
          if (mValidator.isValidationFeatureSupported() && needValidation) {
              mSetOpptSubCallback = callback;
              long validationTimeout = DEFAULT_VALIDATION_EXPIRATION_TIME;
              CarrierConfigManager configManager = (CarrierConfigManager)
                      mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
              if (configManager != null) {
                  PersistableBundle b = configManager.getConfigForSubId(subIdToValidate);
                  if (b != null) {
                      validationTimeout = b.getLong(KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG);
                  }
              }
              mValidator.validate(subIdToValidate, validationTimeout, false, mValidationCallback);
          } else {
              setOpportunisticSubscriptionInternal(subId);
              sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_SUCCESS);
          }
      }
  
      private void sendSetOpptCallbackHelper(ISetOpportunisticDataCallback callback, int result) {
          if (callback == null) return;
          try {
              callback.onComplete(result);
          } catch (RemoteException exception) {
              log("RemoteException " + exception);
          }
      }
  
      /**
       * Set opportunistic data subscription.
       */
      private void setOpportunisticSubscriptionInternal(int subId) {
          if (mOpptDataSubId != subId) {
              mOpptDataSubId = subId;
              onEvaluate(REQUESTS_UNCHANGED, "oppt data subId changed");
          }
      }
  
      private void onValidationDone(int subId, boolean passed) {
          log("onValidationDone: " + (passed ? "passed" : "failed")
                  + " on subId " + subId);
          int resultForCallBack;
  
          if (!mSubscriptionController.isActiveSubId(subId)) {
              log("onValidationDone: subId " + subId + " is no longer active");
              resultForCallBack = SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION;
          } else if (!passed) {
              resultForCallBack = SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED;
          } else {
              if (mSubscriptionController.isOpportunistic(subId)) {
                  setOpportunisticSubscriptionInternal(subId);
              } else {
                  // Switching data back to primary subscription.
                  setOpportunisticSubscriptionInternal(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
              }
              resultForCallBack = SET_OPPORTUNISTIC_SUB_SUCCESS;
          }
  
          // Trigger callback if needed
          sendSetOpptCallbackHelper(mSetOpptSubCallback, resultForCallBack);
          mSetOpptSubCallback = null;
      }
  
      /**
       * Notify PhoneSwitcher to try to switch data to an opportunistic subscription.
       *
       * Set opportunistic data subscription. It's an indication to switch Internet data to this
       * subscription. It has to be an active subscription, and PhoneSwitcher will try to validate
       * it first if needed. If subId is DEFAULT_SUBSCRIPTION_ID, it means we are un-setting
       * opportunistic data sub and switch data back to primary sub.
       *
       * @param subId the opportunistic data subscription to switch to. pass DEFAULT_SUBSCRIPTION_ID
       *              if un-setting it.
       * @param needValidation whether Telephony will wait until the network is validated by
       *              connectivity service before switching data to it. More details see
       *              {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED}.
       * @param callback Callback will be triggered once it succeeds or failed.
       *                 Pass null if don't care about the result.
       */
      public void trySetOpportunisticDataSubscription(int subId, boolean needValidation,
              ISetOpportunisticDataCallback callback) {
          log("Try set opportunistic data subscription to subId " + subId
                  + (needValidation ? " with " : " without ") + "validation");
          PhoneSwitcher.this.obtainMessage(EVENT_OPPT_DATA_SUB_CHANGED,
                  subId, needValidation ? 1 : 0, callback).sendToTarget();
      }
  
      private boolean isPhoneInVoiceCall(Phone phone) {
          if (phone == null) {
              return false;
          }
  
          // A phone in voice call might trigger data being switched to it.
          // We only report true if its precise call state is ACTIVE, ALERTING or HOLDING.
          // The reason is data switching is interrupting, so we only switch when necessary and
          // acknowledged by the users. For incoming call, we don't switch until answered
          // (RINGING -> ACTIVE), for outgoing call we don't switch until call is connected
          // in network (DIALING -> ALERTING).
          return (phone.getForegroundCall().getState() == Call.State.ACTIVE
                  || phone.getForegroundCall().getState() == Call.State.ALERTING
                  || phone.getBackgroundCall().getState() == Call.State.HOLDING);
      }
  
      private void updateHalCommandToUse() {
          mHalCommandToUse = mRadioConfig.isSetPreferredDataCommandSupported()
                  ? HAL_COMMAND_PREFERRED_DATA : HAL_COMMAND_ALLOW_DATA;
      }
  
      public int getOpportunisticDataSubscriptionId() {
          return mOpptDataSubId;
      }
  
      public int getPreferredDataPhoneId() {
          return mPreferredDataPhoneId;
      }
  
      @UnsupportedAppUsage
      private void log(String l) {
          Rlog.d(LOG_TAG, l);
          mLocalLog.log(l);
      }
  
      private void logDataSwitchEvent(int subId, int state, int reason) {
          log("logDataSwitchEvent subId " + subId + " state " + state + " reason " + reason);
          DataSwitch dataSwitch = new DataSwitch();
          dataSwitch.state = state;
          dataSwitch.reason = reason;
          TelephonyMetrics.getInstance().writeDataSwitch(subId, dataSwitch);
      }
  
      /**
       * See {@link PhoneStateListener#LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE}.
       */
      private void notifyPreferredDataSubIdChanged() {
          ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
                  "telephony.registry"));
          try {
              log("notifyPreferredDataSubIdChanged to " + mPreferredDataSubId);
              tr.notifyActiveDataSubIdChanged(mPreferredDataSubId);
          } catch (RemoteException ex) {
              // Should never happen because its always available.
          }
      }
  
      /**
       * @return The active data subscription id
       */
      public int getActiveDataSubId() {
          return mPreferredDataSubId;
      }
  
      public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
          final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
          pw.println("PhoneSwitcher:");
          Calendar c = Calendar.getInstance();
          for (int i = 0; i < mNumPhones; i++) {
              PhoneState ps = mPhoneStates[i];
              c.setTimeInMillis(ps.lastRequested);
              pw.println("PhoneId(" + i + ") active=" + ps.active + ", lastRequest=" +
                      (ps.lastRequested == 0 ? "never" :
                       String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)));
          }
          pw.increaseIndent();
          mLocalLog.dump(fd, pw, args);
          pw.decreaseIndent();
      }
  }
  