content: /*
   * Copyright (C) 2017 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.os.Binder.withCleanCallingIdentity;
  import static android.telephony.AccessNetworkConstants.AccessNetworkType.EUTRAN;
  import static android.telephony.AccessNetworkConstants.AccessNetworkType.GERAN;
  import static android.telephony.AccessNetworkConstants.AccessNetworkType.UTRAN;
  
  import android.content.Context;
  import android.hardware.radio.V1_0.RadioError;
  import android.os.AsyncResult;
  import android.os.Build;
  import android.os.Bundle;
  import android.os.Handler;
  import android.os.IBinder;
  import android.os.Message;
  import android.os.Messenger;
  import android.os.Process;
  import android.os.RemoteException;
  import android.telephony.CellInfo;
  import android.telephony.LocationAccessPolicy;
  import android.telephony.NetworkScan;
  import android.telephony.NetworkScanRequest;
  import android.telephony.RadioAccessSpecifier;
  import android.telephony.SubscriptionInfo;
  import android.telephony.TelephonyScanManager;
  import android.util.Log;
  
  import java.util.Collection;
  import java.util.List;
  import java.util.Set;
  import java.util.concurrent.atomic.AtomicInteger;
  import java.util.stream.Collectors;
  import java.util.stream.Stream;
  
  /**
   * Manages radio access network scan requests.
   *
   * Provides methods to start and stop network scan requests, and keeps track of all the live scans.
   *
   * {@hide}
   */
  public final class NetworkScanRequestTracker {
  
      private static final String TAG = "ScanRequestTracker";
  
      private static final int CMD_START_NETWORK_SCAN = 1;
      private static final int EVENT_START_NETWORK_SCAN_DONE = 2;
      private static final int EVENT_RECEIVE_NETWORK_SCAN_RESULT = 3;
      private static final int CMD_STOP_NETWORK_SCAN = 4;
      private static final int EVENT_STOP_NETWORK_SCAN_DONE = 5;
      private static final int CMD_INTERRUPT_NETWORK_SCAN = 6;
      private static final int EVENT_INTERRUPT_NETWORK_SCAN_DONE = 7;
  
      private final Handler mHandler = new Handler() {
          @Override
          public void handleMessage(Message msg) {
              switch (msg.what) {
                  case CMD_START_NETWORK_SCAN:
                      mScheduler.doStartScan((NetworkScanRequestInfo) msg.obj);
                      break;
  
                  case EVENT_START_NETWORK_SCAN_DONE:
                      mScheduler.startScanDone((AsyncResult) msg.obj);
                      break;
  
                  case EVENT_RECEIVE_NETWORK_SCAN_RESULT:
                      mScheduler.receiveResult((AsyncResult) msg.obj);
                      break;
  
                  case CMD_STOP_NETWORK_SCAN:
                      mScheduler.doStopScan(msg.arg1);
                      break;
  
                  case EVENT_STOP_NETWORK_SCAN_DONE:
                      mScheduler.stopScanDone((AsyncResult) msg.obj);
                      break;
  
                  case CMD_INTERRUPT_NETWORK_SCAN:
                      mScheduler.doInterruptScan(msg.arg1);
                      break;
  
                  case EVENT_INTERRUPT_NETWORK_SCAN_DONE:
                      mScheduler.interruptScanDone((AsyncResult) msg.obj);
                      break;
              }
          }
      };
  
      // The sequence number of NetworkScanRequests
      private final AtomicInteger mNextNetworkScanRequestId = new AtomicInteger(1);
      private final NetworkScanRequestScheduler mScheduler = new NetworkScanRequestScheduler();
  
      private void logEmptyResultOrException(AsyncResult ar) {
          if (ar.result == null) {
              Log.e(TAG, "NetworkScanResult: Empty result");
          } else {
              Log.e(TAG, "NetworkScanResult: Exception: " + ar.exception);
          }
      }
  
      private boolean isValidScan(NetworkScanRequestInfo nsri) {
          if (nsri.mRequest == null || nsri.mRequest.getSpecifiers() == null) {
              return false;
          }
          if (nsri.mRequest.getSpecifiers().length > NetworkScanRequest.MAX_RADIO_ACCESS_NETWORKS) {
              return false;
          }
          for (RadioAccessSpecifier ras : nsri.mRequest.getSpecifiers()) {
              if (ras.getRadioAccessNetwork() != GERAN && ras.getRadioAccessNetwork() != UTRAN
                      && ras.getRadioAccessNetwork() != EUTRAN) {
                  return false;
              }
              if (ras.getBands() != null && ras.getBands().length > NetworkScanRequest.MAX_BANDS) {
                  return false;
              }
              if (ras.getChannels() != null
                      && ras.getChannels().length > NetworkScanRequest.MAX_CHANNELS) {
                  return false;
              }
          }
  
          if ((nsri.mRequest.getSearchPeriodicity() < NetworkScanRequest.MIN_SEARCH_PERIODICITY_SEC)
                  || (nsri.mRequest.getSearchPeriodicity()
                  > NetworkScanRequest.MAX_SEARCH_PERIODICITY_SEC)) {
              return false;
          }
  
          if ((nsri.mRequest.getMaxSearchTime() < NetworkScanRequest.MIN_SEARCH_MAX_SEC)
                  || (nsri.mRequest.getMaxSearchTime() > NetworkScanRequest.MAX_SEARCH_MAX_SEC)) {
              return false;
          }
  
          if ((nsri.mRequest.getIncrementalResultsPeriodicity()
                  < NetworkScanRequest.MIN_INCREMENTAL_PERIODICITY_SEC)
                  || (nsri.mRequest.getIncrementalResultsPeriodicity()
                  > NetworkScanRequest.MAX_INCREMENTAL_PERIODICITY_SEC)) {
              return false;
          }
  
          if ((nsri.mRequest.getSearchPeriodicity() > nsri.mRequest.getMaxSearchTime())
                  || (nsri.mRequest.getIncrementalResultsPeriodicity()
                          > nsri.mRequest.getMaxSearchTime())) {
              return false;
          }
  
          if ((nsri.mRequest.getPlmns() != null)
                  && (nsri.mRequest.getPlmns().size() > NetworkScanRequest.MAX_MCC_MNC_LIST_SIZE)) {
              return false;
          }
          return true;
      }
  
      private static boolean doesCellInfoCorrespondToKnownMccMnc(CellInfo ci,
              Collection<String> knownMccMncs) {
          String mccMnc = ci.getCellIdentity().getMccString()
                  + ci.getCellIdentity().getMncString();
          return knownMccMncs.contains(mccMnc);
      }
  
      /**
       * @return A list of MCC/MNC ids that apps should be allowed to see as results from a network
       * scan when scan results are restricted due to location privacy.
       */
      public static Set<String> getAllowedMccMncsForLocationRestrictedScan(Context context) {
          return withCleanCallingIdentity(() -> SubscriptionController.getInstance()
              .getAvailableSubscriptionInfoList(context.getOpPackageName()).stream()
              .flatMap(NetworkScanRequestTracker::getAllowableMccMncsFromSubscriptionInfo)
              .collect(Collectors.toSet()));
      }
  
      private static Stream<String> getAllowableMccMncsFromSubscriptionInfo(SubscriptionInfo info) {
          Stream<String> plmns = Stream.of(info.getEhplmns(), info.getHplmns()).flatMap(List::stream);
          if (info.getMccString() != null && info.getMncString() != null) {
              plmns = Stream.concat(plmns, Stream.of(info.getMccString() + info.getMncString()));
          }
          return plmns;
      }
  
      /** Sends a message back to the application via its callback. */
      private void notifyMessenger(NetworkScanRequestInfo nsri, int what, int err,
              List<CellInfo> result) {
          Messenger messenger = nsri.mMessenger;
          Message message = Message.obtain();
          message.what = what;
          message.arg1 = err;
          message.arg2 = nsri.mScanId;
  
          if (result != null) {
              if (what == TelephonyScanManager.CALLBACK_RESTRICTED_SCAN_RESULTS) {
                  Set<String> allowedMccMncs =
                          getAllowedMccMncsForLocationRestrictedScan(nsri.mPhone.getContext());
  
                  result = result.stream().map(CellInfo::sanitizeLocationInfo)
                          .filter(ci -> doesCellInfoCorrespondToKnownMccMnc(ci, allowedMccMncs))
                          .collect(Collectors.toList());
              }
  
              CellInfo[] ci = result.toArray(new CellInfo[result.size()]);
              Bundle b = new Bundle();
              b.putParcelableArray(TelephonyScanManager.SCAN_RESULT_KEY, ci);
              message.setData(b);
          } else {
              message.obj = null;
          }
          try {
              messenger.send(message);
          } catch (RemoteException e) {
              Log.e(TAG, "Exception in notifyMessenger: " + e);
          }
      }
  
      /**
      * Tracks info about the radio network scan.
       *
      * Also used to notice when the calling process dies so we can self-expire.
      */
      class NetworkScanRequestInfo implements IBinder.DeathRecipient {
          private final NetworkScanRequest mRequest;
          private final Messenger mMessenger;
          private final IBinder mBinder;
          private final Phone mPhone;
          private final int mScanId;
          private final int mUid;
          private final int mPid;
          private final String mCallingPackage;
          private boolean mIsBinderDead;
  
          NetworkScanRequestInfo(NetworkScanRequest r, Messenger m, IBinder b, int id, Phone phone,
                  int callingUid, int callingPid, String callingPackage) {
              super();
              mRequest = r;
              mMessenger = m;
              mBinder = b;
              mScanId = id;
              mPhone = phone;
              mUid = callingUid;
              mPid = callingPid;
              mCallingPackage = callingPackage;
              mIsBinderDead = false;
  
              try {
                  mBinder.linkToDeath(this, 0);
              } catch (RemoteException e) {
                  binderDied();
              }
          }
  
          synchronized void setIsBinderDead(boolean val) {
              mIsBinderDead = val;
          }
  
          synchronized boolean getIsBinderDead() {
              return mIsBinderDead;
          }
  
          NetworkScanRequest getRequest() {
              return mRequest;
          }
  
          void unlinkDeathRecipient() {
              if (mBinder != null) {
                  mBinder.unlinkToDeath(this, 0);
              }
          }
  
          @Override
          public void binderDied() {
              Log.e(TAG, "PhoneInterfaceManager NetworkScanRequestInfo binderDied("
                      + mRequest + ", " + mBinder + ")");
              setIsBinderDead(true);
              interruptNetworkScan(mScanId);
          }
      }
  
      /**
       * Handles multiplexing and scheduling for multiple requests.
       */
      private class NetworkScanRequestScheduler {
  
          private NetworkScanRequestInfo mLiveRequestInfo;
          private NetworkScanRequestInfo mPendingRequestInfo;
  
          private int rilErrorToScanError(int rilError) {
              switch (rilError) {
                  case RadioError.NONE:
                      return NetworkScan.SUCCESS;
                  case RadioError.RADIO_NOT_AVAILABLE:
                      Log.e(TAG, "rilErrorToScanError: RADIO_NOT_AVAILABLE");
                      return NetworkScan.ERROR_MODEM_ERROR;
                  case RadioError.REQUEST_NOT_SUPPORTED:
                      Log.e(TAG, "rilErrorToScanError: REQUEST_NOT_SUPPORTED");
                      return NetworkScan.ERROR_UNSUPPORTED;
                  case RadioError.NO_MEMORY:
                      Log.e(TAG, "rilErrorToScanError: NO_MEMORY");
                      return NetworkScan.ERROR_MODEM_ERROR;
                  case RadioError.INTERNAL_ERR:
                      Log.e(TAG, "rilErrorToScanError: INTERNAL_ERR");
                      return NetworkScan.ERROR_MODEM_ERROR;
                  case RadioError.MODEM_ERR:
                      Log.e(TAG, "rilErrorToScanError: MODEM_ERR");
                      return NetworkScan.ERROR_MODEM_ERROR;
                  case RadioError.OPERATION_NOT_ALLOWED:
                      Log.e(TAG, "rilErrorToScanError: OPERATION_NOT_ALLOWED");
                      return NetworkScan.ERROR_MODEM_ERROR;
                  case RadioError.INVALID_ARGUMENTS:
                      Log.e(TAG, "rilErrorToScanError: INVALID_ARGUMENTS");
                      return NetworkScan.ERROR_INVALID_SCAN;
                  case RadioError.DEVICE_IN_USE:
                      Log.e(TAG, "rilErrorToScanError: DEVICE_IN_USE");
                      return NetworkScan.ERROR_MODEM_UNAVAILABLE;
                  default:
                      Log.e(TAG, "rilErrorToScanError: Unexpected RadioError " +  rilError);
                      return NetworkScan.ERROR_RADIO_INTERFACE_ERROR;
              }
          }
  
          private int commandExceptionErrorToScanError(CommandException.Error error) {
              switch (error) {
                  case RADIO_NOT_AVAILABLE:
                      Log.e(TAG, "commandExceptionErrorToScanError: RADIO_NOT_AVAILABLE");
                      return NetworkScan.ERROR_MODEM_ERROR;
                  case REQUEST_NOT_SUPPORTED:
                      Log.e(TAG, "commandExceptionErrorToScanError: REQUEST_NOT_SUPPORTED");
                      return NetworkScan.ERROR_UNSUPPORTED;
                  case NO_MEMORY:
                      Log.e(TAG, "commandExceptionErrorToScanError: NO_MEMORY");
                      return NetworkScan.ERROR_MODEM_ERROR;
                  case INTERNAL_ERR:
                      Log.e(TAG, "commandExceptionErrorToScanError: INTERNAL_ERR");
                      return NetworkScan.ERROR_MODEM_ERROR;
                  case MODEM_ERR:
                      Log.e(TAG, "commandExceptionErrorToScanError: MODEM_ERR");
                      return NetworkScan.ERROR_MODEM_ERROR;
                  case OPERATION_NOT_ALLOWED:
                      Log.e(TAG, "commandExceptionErrorToScanError: OPERATION_NOT_ALLOWED");
                      return NetworkScan.ERROR_MODEM_ERROR;
                  case INVALID_ARGUMENTS:
                      Log.e(TAG, "commandExceptionErrorToScanError: INVALID_ARGUMENTS");
                      return NetworkScan.ERROR_INVALID_SCAN;
                  case DEVICE_IN_USE:
                      Log.e(TAG, "commandExceptionErrorToScanError: DEVICE_IN_USE");
                      return NetworkScan.ERROR_MODEM_UNAVAILABLE;
                  default:
                      Log.e(TAG, "commandExceptionErrorToScanError: Unexpected CommandExceptionError "
                              +  error);
                      return NetworkScan.ERROR_RADIO_INTERFACE_ERROR;
              }
          }
  
          private void doStartScan(NetworkScanRequestInfo nsri) {
              if (nsri == null) {
                  Log.e(TAG, "CMD_START_NETWORK_SCAN: nsri is null");
                  return;
              }
              if (!isValidScan(nsri)) {
                  notifyMessenger(nsri, TelephonyScanManager.CALLBACK_SCAN_ERROR,
                          NetworkScan.ERROR_INVALID_SCAN, null);
                  return;
              }
              if (nsri.getIsBinderDead()) {
                  Log.e(TAG, "CMD_START_NETWORK_SCAN: Binder has died");
                  return;
              }
              if (!startNewScan(nsri)) {
                  if (!interruptLiveScan(nsri)) {
                      if (!cacheScan(nsri)) {
                          notifyMessenger(nsri, TelephonyScanManager.CALLBACK_SCAN_ERROR,
                                  NetworkScan.ERROR_MODEM_UNAVAILABLE, null);
                      }
                  }
              }
          }
  
          private synchronized void startScanDone(AsyncResult ar) {
              NetworkScanRequestInfo nsri = (NetworkScanRequestInfo) ar.userObj;
              if (nsri == null) {
                  Log.e(TAG, "EVENT_START_NETWORK_SCAN_DONE: nsri is null");
                  return;
              }
              if (mLiveRequestInfo == null || nsri.mScanId != mLiveRequestInfo.mScanId) {
                  Log.e(TAG, "EVENT_START_NETWORK_SCAN_DONE: nsri does not match mLiveRequestInfo");
                  return;
              }
              if (ar.exception == null && ar.result != null) {
                  // Register for the scan results if the scan started successfully.
                  nsri.mPhone.mCi.registerForNetworkScanResult(mHandler,
                          EVENT_RECEIVE_NETWORK_SCAN_RESULT, nsri);
              } else {
                  logEmptyResultOrException(ar);
                  if (ar.exception != null) {
                      CommandException.Error error =
                              ((CommandException) (ar.exception)).getCommandError();
                      deleteScanAndMayNotify(nsri, commandExceptionErrorToScanError(error), true);
                  } else {
                      Log.wtf(TAG, "EVENT_START_NETWORK_SCAN_DONE: ar.exception can not be null!");
                  }
              }
          }
  
          private void receiveResult(AsyncResult ar) {
              NetworkScanRequestInfo nsri = (NetworkScanRequestInfo) ar.userObj;
              if (nsri == null) {
                  Log.e(TAG, "EVENT_RECEIVE_NETWORK_SCAN_RESULT: nsri is null");
                  return;
              }
              LocationAccessPolicy.LocationPermissionQuery locationQuery =
                      new LocationAccessPolicy.LocationPermissionQuery.Builder()
                      .setCallingPackage(nsri.mCallingPackage)
                      .setCallingPid(nsri.mPid)
                      .setCallingUid(nsri.mUid)
                      .setMinSdkVersionForFine(Build.VERSION_CODES.Q)
                      .setMethod("NetworkScanTracker#onResult")
                      .build();
              if (ar.exception == null && ar.result != null) {
                  NetworkScanResult nsr = (NetworkScanResult) ar.result;
                  boolean isLocationAccessAllowed = LocationAccessPolicy.checkLocationPermission(
                          nsri.mPhone.getContext(), locationQuery)
                          == LocationAccessPolicy.LocationPermissionResult.ALLOWED;
                  int notifyMsg = isLocationAccessAllowed
                          ? TelephonyScanManager.CALLBACK_SCAN_RESULTS
                          : TelephonyScanManager.CALLBACK_RESTRICTED_SCAN_RESULTS;
                  if (nsr.scanError == NetworkScan.SUCCESS) {
                      if (nsri.mPhone.getServiceStateTracker() != null) {
                          nsri.mPhone.getServiceStateTracker().updateOperatorNameForCellInfo(
                                  nsr.networkInfos);
                      }
  
                      notifyMessenger(nsri, notifyMsg,
                              rilErrorToScanError(nsr.scanError), nsr.networkInfos);
                      if (nsr.scanStatus == NetworkScanResult.SCAN_STATUS_COMPLETE) {
                          deleteScanAndMayNotify(nsri, NetworkScan.SUCCESS, true);
                          nsri.mPhone.mCi.unregisterForNetworkScanResult(mHandler);
                      }
                  } else {
                      if (nsr.networkInfos != null) {
                          notifyMessenger(nsri, notifyMsg,
                                  rilErrorToScanError(nsr.scanError), nsr.networkInfos);
                      }
                      deleteScanAndMayNotify(nsri, rilErrorToScanError(nsr.scanError), true);
                      nsri.mPhone.mCi.unregisterForNetworkScanResult(mHandler);
                  }
              } else {
                  logEmptyResultOrException(ar);
                  deleteScanAndMayNotify(nsri, NetworkScan.ERROR_RADIO_INTERFACE_ERROR, true);
                  nsri.mPhone.mCi.unregisterForNetworkScanResult(mHandler);
              }
          }
  
          // Stops the scan if the scanId and uid match the mScanId and mUid.
          // If the scan to be stopped is the live scan, we only send the request to RIL, while the
          // mLiveRequestInfo will not be cleared and the user will not be notified either.
          // If the scan to be stopped is the pending scan, we will clear mPendingRequestInfo and
          // notify the user.
          private synchronized void doStopScan(int scanId) {
              if (mLiveRequestInfo != null && scanId == mLiveRequestInfo.mScanId) {
                  mLiveRequestInfo.mPhone.stopNetworkScan(
                          mHandler.obtainMessage(EVENT_STOP_NETWORK_SCAN_DONE, mLiveRequestInfo));
              } else if (mPendingRequestInfo != null && scanId == mPendingRequestInfo.mScanId) {
                  notifyMessenger(mPendingRequestInfo,
                          TelephonyScanManager.CALLBACK_SCAN_COMPLETE, NetworkScan.SUCCESS, null);
                  mPendingRequestInfo = null;
              } else {
                  Log.e(TAG, "stopScan: scan " + scanId + " does not exist!");
              }
          }
  
          private void stopScanDone(AsyncResult ar) {
              NetworkScanRequestInfo nsri = (NetworkScanRequestInfo) ar.userObj;
              if (nsri == null) {
                  Log.e(TAG, "EVENT_STOP_NETWORK_SCAN_DONE: nsri is null");
                  return;
              }
              if (ar.exception == null && ar.result != null) {
                  deleteScanAndMayNotify(nsri, NetworkScan.SUCCESS, true);
              } else {
                  logEmptyResultOrException(ar);
                  if (ar.exception != null) {
                      CommandException.Error error =
                              ((CommandException) (ar.exception)).getCommandError();
                      deleteScanAndMayNotify(nsri, commandExceptionErrorToScanError(error), true);
                  } else {
                      Log.wtf(TAG, "EVENT_STOP_NETWORK_SCAN_DONE: ar.exception can not be null!");
                  }
              }
              nsri.mPhone.mCi.unregisterForNetworkScanResult(mHandler);
          }
  
          // Interrupts the live scan is the scanId matches the mScanId of the mLiveRequestInfo.
          private synchronized void doInterruptScan(int scanId) {
              if (mLiveRequestInfo != null && scanId == mLiveRequestInfo.mScanId) {
                  mLiveRequestInfo.mPhone.stopNetworkScan(mHandler.obtainMessage(
                          EVENT_INTERRUPT_NETWORK_SCAN_DONE, mLiveRequestInfo));
              } else {
                  Log.e(TAG, "doInterruptScan: scan " + scanId + " does not exist!");
              }
          }
  
          private void interruptScanDone(AsyncResult ar) {
              NetworkScanRequestInfo nsri = (NetworkScanRequestInfo) ar.userObj;
              if (nsri == null) {
                  Log.e(TAG, "EVENT_INTERRUPT_NETWORK_SCAN_DONE: nsri is null");
                  return;
              }
              nsri.mPhone.mCi.unregisterForNetworkScanResult(mHandler);
              deleteScanAndMayNotify(nsri, 0, false);
          }
  
          // Interrupts the live scan and caches nsri in mPendingRequestInfo. Once the live scan is
          // stopped, a new scan will automatically start with nsri.
          // The new scan can interrupt the live scan only when all the below requirements are met:
          //   1. There is 1 live scan and no other pending scan
          //   2. The new scan is requested by mobile network setting menu (owned by PHONE process)
          //   3. The live scan is not requested by mobile network setting menu
          private synchronized boolean interruptLiveScan(NetworkScanRequestInfo nsri) {
              if (mLiveRequestInfo != null && mPendingRequestInfo == null
                      && nsri.mUid == Process.PHONE_UID
                              && mLiveRequestInfo.mUid != Process.PHONE_UID) {
                  doInterruptScan(mLiveRequestInfo.mScanId);
                  mPendingRequestInfo = nsri;
                  notifyMessenger(mLiveRequestInfo, TelephonyScanManager.CALLBACK_SCAN_ERROR,
                          NetworkScan.ERROR_INTERRUPTED, null);
                  return true;
              }
              return false;
          }
  
          private boolean cacheScan(NetworkScanRequestInfo nsri) {
              // TODO(30954762): Cache periodic scan for OC-MR1.
              return false;
          }
  
          // Starts a new scan with nsri if there is no live scan running.
          private synchronized boolean startNewScan(NetworkScanRequestInfo nsri) {
              if (mLiveRequestInfo == null) {
                  mLiveRequestInfo = nsri;
                  nsri.mPhone.startNetworkScan(nsri.getRequest(),
                          mHandler.obtainMessage(EVENT_START_NETWORK_SCAN_DONE, nsri));
                  return true;
              }
              return false;
          }
  
  
          // Deletes the mLiveRequestInfo and notify the user if it matches nsri.
          private synchronized void deleteScanAndMayNotify(NetworkScanRequestInfo nsri, int error,
                  boolean notify) {
              if (mLiveRequestInfo != null && nsri.mScanId == mLiveRequestInfo.mScanId) {
                  if (notify) {
                      if (error == NetworkScan.SUCCESS) {
                          notifyMessenger(nsri, TelephonyScanManager.CALLBACK_SCAN_COMPLETE, error,
                                  null);
                      } else {
                          notifyMessenger(nsri, TelephonyScanManager.CALLBACK_SCAN_ERROR, error,
                                  null);
                      }
                  }
                  mLiveRequestInfo = null;
                  if (mPendingRequestInfo != null) {
                      startNewScan(mPendingRequestInfo);
                      mPendingRequestInfo = null;
                  }
              }
          }
      }
  
      /**
       * Interrupts an ongoing network scan
       *
       * This method is similar to stopNetworkScan, since they both stops an ongoing scan. The
       * difference is that stopNetworkScan is only used by the callers to stop their own scans, so
       * sanity check will be done to make sure the request is valid; while this method is only
       * internally used by NetworkScanRequestTracker so sanity check is not needed.
       */
      private void interruptNetworkScan(int scanId) {
          // scanId will be stored at Message.arg1
          mHandler.obtainMessage(CMD_INTERRUPT_NETWORK_SCAN, scanId, 0).sendToTarget();
      }
  
      /**
       * Starts a new network scan
       *
       * This function only wraps all the incoming information and delegate then to the handler thread
       * which will actually handles the scan request. So a new scanId will always be generated and
       * returned to the user, no matter how this scan will be actually handled.
       */
      public int startNetworkScan(
              NetworkScanRequest request, Messenger messenger, IBinder binder, Phone phone,
              int callingUid, int callingPid, String callingPackage) {
          int scanId = mNextNetworkScanRequestId.getAndIncrement();
          NetworkScanRequestInfo nsri =
                  new NetworkScanRequestInfo(request, messenger, binder, scanId, phone,
                          callingUid, callingPid, callingPackage);
          // nsri will be stored as Message.obj
          mHandler.obtainMessage(CMD_START_NETWORK_SCAN, nsri).sendToTarget();
          return scanId;
      }
  
      /**
       * Stops an ongoing network scan
       *
       * The ongoing scan will be stopped only when the input scanId and caller's uid matches the
       * corresponding information associated with it.
       */
      public void stopNetworkScan(int scanId, int callingUid) {
          synchronized (mScheduler) {
              if ((mScheduler.mLiveRequestInfo != null
                      && scanId == mScheduler.mLiveRequestInfo.mScanId
                      && callingUid == mScheduler.mLiveRequestInfo.mUid)
                      || (mScheduler.mPendingRequestInfo != null
                      && scanId == mScheduler.mPendingRequestInfo.mScanId
                      && callingUid == mScheduler.mPendingRequestInfo.mUid)) {
                  // scanId will be stored at Message.arg1
                  mHandler.obtainMessage(CMD_STOP_NETWORK_SCAN, scanId, 0).sendToTarget();
              } else {
                  throw new IllegalArgumentException("Scan with id: " + scanId + " does not exist!");
              }
          }
      }
  }
  