/**
  * Returns the neighboring cell information of the device. The getAllCellInfo is preferred and use
  * this only if getAllCellInfo return nulls or an empty list.
  *
  * <p>In the future this call will be deprecated.
  *
  * <p>
  *
  * @return List of NeighboringCellInfo or null if info unavailable.
  *     <p>Requires Permission: (@link android.Manifest.permission#ACCESS_COARSE_UPDATES}
  */
 public List<NeighboringCellInfo> getNeighboringCellInfo() {
   try {
     return getITelephony().getNeighboringCellInfo(mContext.getOpPackageName());
   } catch (RemoteException ex) {
     return null;
   } catch (NullPointerException ex) {
     return null;
   }
 }
  private void initializeCarrierApps() {
    // Initialize carrier apps:
    // -Now (on system startup)
    // -Whenever new carrier privilege rules might change (new SIM is loaded)
    // -Whenever we switch to a new user
    mCurrentlyActiveUserId = 0;
    try {
      ActivityManagerNative.getDefault()
          .registerUserSwitchObserver(
              new IUserSwitchObserver.Stub() {
                @Override
                public void onUserSwitching(int newUserId, IRemoteCallback reply)
                    throws RemoteException {
                  mCurrentlyActiveUserId = newUserId;
                  CarrierAppUtils.disableCarrierAppsUntilPrivileged(
                      mContext.getOpPackageName(),
                      mPackageManager,
                      TelephonyManager.getDefault(),
                      mCurrentlyActiveUserId);

                  if (reply != null) {
                    try {
                      reply.sendResult(null);
                    } catch (RemoteException e) {
                    }
                  }
                }

                @Override
                public void onUserSwitchComplete(int newUserId) {
                  // Ignore.
                }

                @Override
                public void onForegroundProfileSwitch(int newProfileId) throws RemoteException {
                  // Ignore.
                }
              });
      mCurrentlyActiveUserId = ActivityManagerNative.getDefault().getCurrentUser().id;
    } catch (RemoteException e) {
      logd("Couldn't get current user ID; guessing it's 0: " + e.getMessage());
    }
    CarrierAppUtils.disableCarrierAppsUntilPrivileged(
        mContext.getOpPackageName(),
        mPackageManager,
        TelephonyManager.getDefault(),
        mCurrentlyActiveUserId);
  }
  /**
   * This function is called repeatedly after each asynchronous operation until all preconditions
   * for the connection have been satisfied and the connection is established (or not).
   */
  private void updateConnection() {
    // Step 0. Stop scans if necessary to prevent interference while connected.
    // Resume scans later when no longer attempting to connect.
    updateScanState();

    // Step 1. Before we try to connect to a new device, tell the system we
    // have disconnected from the old one.
    if ((mRemoteDisplay != null || mExtRemoteDisplay != null)
        && mConnectedDevice != mDesiredDevice) {
      Slog.i(
          TAG,
          "Stopped listening for RTSP connection on "
              + mRemoteDisplayInterface
              + " from Wifi display: "
              + mConnectedDevice.deviceName);

      if (mRemoteDisplay != null) {
        mRemoteDisplay.dispose();
      } else if (mExtRemoteDisplay != null) {
        ExtendedRemoteDisplayHelper.dispose(mExtRemoteDisplay);
      }

      mExtRemoteDisplay = null;
      mRemoteDisplay = null;
      mRemoteDisplayInterface = null;
      mRemoteDisplayConnected = false;
      mHandler.removeCallbacks(mRtspTimeout);

      mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_DISABLED);
      unadvertiseDisplay();

      // continue to next step
    }

    // Step 2. Before we try to connect to a new device, disconnect from the old one.
    if (mDisconnectingDevice != null) {
      return; // wait for asynchronous callback
    }
    if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {
      Slog.i(TAG, "Disconnecting from Wifi display: " + mConnectedDevice.deviceName);
      mDisconnectingDevice = mConnectedDevice;
      mConnectedDevice = null;
      mConnectedDeviceGroupInfo = null;

      unadvertiseDisplay();

      final WifiP2pDevice oldDevice = mDisconnectingDevice;
      mWifiP2pManager.removeGroup(
          mWifiP2pChannel,
          new ActionListener() {
            @Override
            public void onSuccess() {
              Slog.i(TAG, "Disconnected from Wifi display: " + oldDevice.deviceName);
              next();
            }

            @Override
            public void onFailure(int reason) {
              Slog.i(
                  TAG,
                  "Failed to disconnect from Wifi display: "
                      + oldDevice.deviceName
                      + ", reason="
                      + reason);
              next();
            }

            private void next() {
              if (mDisconnectingDevice == oldDevice) {
                mDisconnectingDevice = null;
                updateConnection();
              }
            }
          });
      return; // wait for asynchronous callback
    }

    // Step 3. Before we try to connect to a new device, stop trying to connect
    // to the old one.
    if (mCancelingDevice != null) {
      return; // wait for asynchronous callback
    }
    if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {
      Slog.i(TAG, "Canceling connection to Wifi display: " + mConnectingDevice.deviceName);
      mCancelingDevice = mConnectingDevice;
      mConnectingDevice = null;

      unadvertiseDisplay();
      mHandler.removeCallbacks(mConnectionTimeout);

      final WifiP2pDevice oldDevice = mCancelingDevice;
      mWifiP2pManager.cancelConnect(
          mWifiP2pChannel,
          new ActionListener() {
            @Override
            public void onSuccess() {
              Slog.i(TAG, "Canceled connection to Wifi display: " + oldDevice.deviceName);
              next();
            }

            @Override
            public void onFailure(int reason) {
              Slog.i(
                  TAG,
                  "Failed to cancel connection to Wifi display: "
                      + oldDevice.deviceName
                      + ", reason="
                      + reason);
              next();
            }

            private void next() {
              if (mCancelingDevice == oldDevice) {
                mCancelingDevice = null;
                updateConnection();
              }
            }
          });
      return; // wait for asynchronous callback
    }

    // Step 4. If we wanted to disconnect, or we're updating after starting an
    // autonomous GO, then mission accomplished.
    if (mDesiredDevice == null) {
      if (mWifiDisplayCertMode) {
        mListener.onDisplaySessionInfo(getSessionInfo(mConnectedDeviceGroupInfo, 0));
      }
      unadvertiseDisplay();
      return; // done
    }

    // Step 5. Try to connect.
    if (mConnectedDevice == null && mConnectingDevice == null) {
      Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName);

      mConnectingDevice = mDesiredDevice;
      WifiP2pConfig config = new WifiP2pConfig();
      WpsInfo wps = new WpsInfo();
      if (mWifiDisplayWpsConfig != WpsInfo.INVALID) {
        wps.setup = mWifiDisplayWpsConfig;
      } else if (mConnectingDevice.wpsPbcSupported()) {
        wps.setup = WpsInfo.PBC;
      } else if (mConnectingDevice.wpsDisplaySupported()) {
        // We do keypad if peer does display
        wps.setup = WpsInfo.KEYPAD;
      } else {
        wps.setup = WpsInfo.DISPLAY;
      }
      config.wps = wps;
      config.deviceAddress = mConnectingDevice.deviceAddress;
      // Helps with STA & P2P concurrency
      config.groupOwnerIntent = WifiP2pConfig.MIN_GROUP_OWNER_INTENT;

      WifiDisplay display = createWifiDisplay(mConnectingDevice);
      advertiseDisplay(display, null, 0, 0, 0);

      final WifiP2pDevice newDevice = mDesiredDevice;
      mWifiP2pManager.connect(
          mWifiP2pChannel,
          config,
          new ActionListener() {
            @Override
            public void onSuccess() {
              // The connection may not yet be established.  We still need to wait
              // for WIFI_P2P_CONNECTION_CHANGED_ACTION.  However, we might never
              // get that broadcast, so we register a timeout.
              Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName);

              mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000);
            }

            @Override
            public void onFailure(int reason) {
              if (mConnectingDevice == newDevice) {
                Slog.i(
                    TAG,
                    "Failed to initiate connection to Wifi display: "
                        + newDevice.deviceName
                        + ", reason="
                        + reason);
                mConnectingDevice = null;
                handleConnectionFailure(false);
              }
            }
          });
      return; // wait for asynchronous callback
    }

    // Step 6. Listen for incoming RTSP connection.
    if (mConnectedDevice != null && mRemoteDisplay == null && mExtRemoteDisplay == null) {
      Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo);
      if (addr == null) {
        Slog.i(
            TAG,
            "Failed to get local interface address for communicating "
                + "with Wifi display: "
                + mConnectedDevice.deviceName);
        handleConnectionFailure(false);
        return; // done
      }

      mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_SOURCE);

      final WifiP2pDevice oldDevice = mConnectedDevice;
      final int port = getPortNumber(mConnectedDevice);
      final String iface = addr.getHostAddress() + ":" + port;
      mRemoteDisplayInterface = iface;

      Slog.i(
          TAG,
          "Listening for RTSP connection on "
              + iface
              + " from Wifi display: "
              + mConnectedDevice.deviceName);

      RemoteDisplay.Listener listener =
          new RemoteDisplay.Listener() {
            @Override
            public void onDisplayConnected(
                Surface surface, int width, int height, int flags, int session) {
              if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) {
                Slog.i(
                    TAG,
                    "Opened RTSP connection with Wifi display: " + mConnectedDevice.deviceName);
                mRemoteDisplayConnected = true;
                mHandler.removeCallbacks(mRtspTimeout);

                if (mWifiDisplayCertMode) {
                  mListener.onDisplaySessionInfo(
                      getSessionInfo(mConnectedDeviceGroupInfo, session));
                }

                final WifiDisplay display = createWifiDisplay(mConnectedDevice);
                advertiseDisplay(display, surface, width, height, flags);
              }
            }

            @Override
            public void onDisplayDisconnected() {
              if (mConnectedDevice == oldDevice) {
                Slog.i(
                    TAG,
                    "Closed RTSP connection with Wifi display: " + mConnectedDevice.deviceName);
                mHandler.removeCallbacks(mRtspTimeout);
                disconnect();
              }
            }

            @Override
            public void onDisplayError(int error) {
              if (mConnectedDevice == oldDevice) {
                Slog.i(
                    TAG,
                    "Lost RTSP connection with Wifi display due to error "
                        + error
                        + ": "
                        + mConnectedDevice.deviceName);
                mHandler.removeCallbacks(mRtspTimeout);
                handleConnectionFailure(false);
              }
            }
          };
      if (ExtendedRemoteDisplayHelper.isAvailable()) {
        mExtRemoteDisplay = ExtendedRemoteDisplayHelper.listen(iface, listener, mHandler, mContext);
      } else {
        mRemoteDisplay =
            RemoteDisplay.listen(iface, listener, mHandler, mContext.getOpPackageName());
      }

      // Use extended timeout value for certification, as some tests require user inputs
      int rtspTimeout =
          mWifiDisplayCertMode ? RTSP_TIMEOUT_SECONDS_CERT_MODE : RTSP_TIMEOUT_SECONDS;

      mHandler.postDelayed(mRtspTimeout, rtspTimeout * 1000);
    }
  }
  /**
   * TODO: Simplify more, as no one is interested in what happened only what the current list
   * contains.
   */
  private synchronized void updateSubscriptionInfoByIccId() {
    logd("updateSubscriptionInfoByIccId:+ Start");

    mSubscriptionManager.clearSubscriptionInfo();

    for (int i = 0; i < PROJECT_SIM_NUM; i++) {
      mInsertSimState[i] = SIM_NOT_CHANGE;
    }

    int insertedSimCount = PROJECT_SIM_NUM;
    for (int i = 0; i < PROJECT_SIM_NUM; i++) {
      if (ICCID_STRING_FOR_NO_SIM.equals(mIccId[i])) {
        insertedSimCount--;
        mInsertSimState[i] = SIM_NOT_INSERT;
      }
    }
    logd("insertedSimCount = " + insertedSimCount);

    int index = 0;
    for (int i = 0; i < PROJECT_SIM_NUM; i++) {
      if (mInsertSimState[i] == SIM_NOT_INSERT) {
        continue;
      }
      index = 2;
      for (int j = i + 1; j < PROJECT_SIM_NUM; j++) {
        if (mInsertSimState[j] == SIM_NOT_CHANGE && mIccId[i].equals(mIccId[j])) {
          mInsertSimState[i] = 1;
          mInsertSimState[j] = index;
          index++;
        }
      }
    }

    ContentResolver contentResolver = mContext.getContentResolver();
    String[] oldIccId = new String[PROJECT_SIM_NUM];
    for (int i = 0; i < PROJECT_SIM_NUM; i++) {
      oldIccId[i] = null;
      List<SubscriptionInfo> oldSubInfo =
          SubscriptionController.getInstance()
              .getSubInfoUsingSlotIdWithCheck(i, false, mContext.getOpPackageName());
      if (oldSubInfo != null) {
        oldIccId[i] = oldSubInfo.get(0).getIccId();
        logd("updateSubscriptionInfoByIccId: oldSubId = " + oldSubInfo.get(0).getSubscriptionId());
        if (mInsertSimState[i] == SIM_NOT_CHANGE && !mIccId[i].equals(oldIccId[i])) {
          mInsertSimState[i] = SIM_CHANGED;
        }
        if (mInsertSimState[i] != SIM_NOT_CHANGE) {
          ContentValues value = new ContentValues(1);
          value.put(SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.INVALID_SIM_SLOT_INDEX);
          contentResolver.update(
              SubscriptionManager.CONTENT_URI,
              value,
              SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID
                  + "="
                  + Integer.toString(oldSubInfo.get(0).getSubscriptionId()),
              null);
        }
      } else {
        if (mInsertSimState[i] == SIM_NOT_CHANGE) {
          // no SIM inserted last time, but there is one SIM inserted now
          mInsertSimState[i] = SIM_CHANGED;
        }
        oldIccId[i] = ICCID_STRING_FOR_NO_SIM;
        logd("updateSubscriptionInfoByIccId: No SIM in slot " + i + " last time");
      }
    }

    for (int i = 0; i < PROJECT_SIM_NUM; i++) {
      logd(
          "updateSubscriptionInfoByIccId: oldIccId["
              + i
              + "] = "
              + oldIccId[i]
              + ", sIccId["
              + i
              + "] = "
              + mIccId[i]);
    }

    // check if the inserted SIM is new SIM
    int nNewCardCount = 0;
    int nNewSimStatus = 0;
    for (int i = 0; i < PROJECT_SIM_NUM; i++) {
      if (mInsertSimState[i] == SIM_NOT_INSERT) {
        logd("updateSubscriptionInfoByIccId: No SIM inserted in slot " + i + " this time");
      } else {
        if (mInsertSimState[i] > 0) {
          // some special SIMs may have the same IccIds, add suffix to distinguish them
          // FIXME: addSubInfoRecord can return an error.
          mSubscriptionManager.addSubscriptionInfoRecord(
              mIccId[i] + Integer.toString(mInsertSimState[i]), i);
          logd("SUB" + (i + 1) + " has invalid IccId");
        } else /*if (sInsertSimState[i] != SIM_NOT_INSERT)*/ {
          mSubscriptionManager.addSubscriptionInfoRecord(mIccId[i], i);
        }
        if (isNewSim(mIccId[i], oldIccId)) {
          nNewCardCount++;
          switch (i) {
            case PhoneConstants.SUB1:
              nNewSimStatus |= STATUS_SIM1_INSERTED;
              break;
            case PhoneConstants.SUB2:
              nNewSimStatus |= STATUS_SIM2_INSERTED;
              break;
            case PhoneConstants.SUB3:
              nNewSimStatus |= STATUS_SIM3_INSERTED;
              break;
              // case PhoneConstants.SUB3:
              //    nNewSimStatus |= STATUS_SIM4_INSERTED;
              //    break;
          }

          mInsertSimState[i] = SIM_NEW;
        }
      }
    }

    for (int i = 0; i < PROJECT_SIM_NUM; i++) {
      if (mInsertSimState[i] == SIM_CHANGED) {
        mInsertSimState[i] = SIM_REPOSITION;
      }
      logd("updateSubscriptionInfoByIccId: sInsertSimState[" + i + "] = " + mInsertSimState[i]);
    }

    List<SubscriptionInfo> subInfos = mSubscriptionManager.getActiveSubscriptionInfoList();
    int nSubCount = (subInfos == null) ? 0 : subInfos.size();
    logd("updateSubscriptionInfoByIccId: nSubCount = " + nSubCount);
    for (int i = 0; i < nSubCount; i++) {
      SubscriptionInfo temp = subInfos.get(i);

      String msisdn =
          TelephonyManager.getDefault().getLine1NumberForSubscriber(temp.getSubscriptionId());

      if (msisdn != null) {
        ContentValues value = new ContentValues(1);
        value.put(SubscriptionManager.NUMBER, msisdn);
        contentResolver.update(
            SubscriptionManager.CONTENT_URI,
            value,
            SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID
                + "="
                + Integer.toString(temp.getSubscriptionId()),
            null);
      }
    }

    // Ensure the modems are mapped correctly
    mSubscriptionManager.setDefaultDataSubId(mSubscriptionManager.getDefaultDataSubId());

    SubscriptionController.getInstance().notifySubscriptionInfoChanged();
    logd("updateSubscriptionInfoByIccId:- SsubscriptionInfo update complete");
  }
  private void handleSimLoaded(int slotId) {
    logd("handleSimStateLoadedInternal: slotId: " + slotId);

    // The SIM should be loaded at this state, but it is possible in cases such as SIM being
    // removed or a refresh RESET that the IccRecords could be null. The right behavior is to
    // not broadcast the SIM loaded.
    IccRecords records = mPhone[slotId].getIccCard().getIccRecords();
    if (records == null) { // Possibly a race condition.
      logd("onRecieve: IccRecords null");
      return;
    }
    if (records.getIccId() == null) {
      logd("onRecieve: IccID null");
      return;
    }
    mIccId[slotId] = records.getIccId();

    if (isAllIccIdQueryDone()) {
      updateSubscriptionInfoByIccId();
    }

    int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
    int[] subIds = SubscriptionController.getInstance().getSubId(slotId);
    if (subIds != null) { // Why an array?
      subId = subIds[0];
    }

    if (SubscriptionManager.isValidSubscriptionId(subId)) {
      String operator = records.getOperatorNumeric();
      if (operator != null) {
        if (subId == SubscriptionController.getInstance().getDefaultSubId()) {
          MccTable.updateMccMncConfiguration(mContext, operator, false);
        }
        SubscriptionController.getInstance().setMccMnc(operator, subId);
      } else {
        logd("EVENT_RECORDS_LOADED Operator name is null");
      }
      TelephonyManager tm = TelephonyManager.getDefault();
      String msisdn = tm.getLine1NumberForSubscriber(subId);
      ContentResolver contentResolver = mContext.getContentResolver();

      if (msisdn != null) {
        ContentValues number = new ContentValues(1);
        number.put(SubscriptionManager.NUMBER, msisdn);
        contentResolver.update(
            SubscriptionManager.CONTENT_URI,
            number,
            SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + Long.toString(subId),
            null);
      }

      SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(subId);
      String nameToSet;
      String simCarrierName = tm.getSimOperatorNameForSubscription(subId);
      ContentValues name = new ContentValues(1);

      if (subInfo != null
          && subInfo.getNameSource() != SubscriptionManager.NAME_SOURCE_USER_INPUT) {
        if (!TextUtils.isEmpty(simCarrierName)) {
          nameToSet = simCarrierName;
        } else {
          nameToSet = "CARD " + Integer.toString(slotId + 1);
        }
        name.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
        logd("sim name = " + nameToSet);
        contentResolver.update(
            SubscriptionManager.CONTENT_URI,
            name,
            SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + Long.toString(subId),
            null);
      }

      /* Update preferred network type and network selection mode on SIM change.
       * Storing last subId in SharedPreference for now to detect SIM change. */
      SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
      int storedSubId = sp.getInt(CURR_SUBID + slotId, -1);

      if (storedSubId != subId) {
        int networkType = RILConstants.PREFERRED_NETWORK_MODE;

        // Set the modem network mode
        mPhone[slotId].setPreferredNetworkType(networkType, null);
        Settings.Global.putInt(
            mPhone[slotId].getContext().getContentResolver(),
            Settings.Global.PREFERRED_NETWORK_MODE + subId,
            networkType);

        // Only support automatic selection mode on SIM change.
        mPhone[slotId].getNetworkSelectionMode(
            obtainMessage(EVENT_GET_NETWORK_SELECTION_MODE_DONE, new Integer(slotId)));

        // Update stored subId
        SharedPreferences.Editor editor = sp.edit();
        editor.putInt(CURR_SUBID + slotId, subId);
        editor.apply();
      }
    } else {
      logd("Invalid subId, could not update ContentResolver");
    }

    // Update set of enabled carrier apps now that the privilege rules may have changed.
    CarrierAppUtils.disableCarrierAppsUntilPrivileged(
        mContext.getOpPackageName(),
        mPackageManager,
        TelephonyManager.getDefault(),
        mCurrentlyActiveUserId);

    broadcastSimStateChanged(slotId, IccCardConstants.INTENT_VALUE_ICC_LOADED, null);
    updateCarrierServices(slotId, IccCardConstants.INTENT_VALUE_ICC_LOADED);
  }