content: /*
   * Copyright (C) 2018 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.server.wifi;
  
  import static com.android.internal.util.Preconditions.checkNotNull;
  import static com.android.server.wifi.util.NativeUtil.addEnclosingQuotes;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.app.ActivityManager;
  import android.app.AlarmManager;
  import android.app.AppOpsManager;
  import android.content.Context;
  import android.content.Intent;
  import android.content.pm.ApplicationInfo;
  import android.content.pm.PackageManager;
  import android.net.MacAddress;
  import android.net.NetworkCapabilities;
  import android.net.NetworkFactory;
  import android.net.NetworkRequest;
  import android.net.NetworkSpecifier;
  import android.net.wifi.INetworkRequestMatchCallback;
  import android.net.wifi.INetworkRequestUserSelectionCallback;
  import android.net.wifi.ScanResult;
  import android.net.wifi.WifiConfiguration;
  import android.net.wifi.WifiConfiguration.SecurityType;
  import android.net.wifi.WifiManager;
  import android.net.wifi.WifiNetworkSpecifier;
  import android.net.wifi.WifiScanner;
  import android.os.Handler;
  import android.os.IBinder;
  import android.os.Looper;
  import android.os.Message;
  import android.os.Messenger;
  import android.os.PatternMatcher;
  import android.os.Process;
  import android.os.RemoteException;
  import android.os.UserHandle;
  import android.os.WorkSource;
  import android.text.TextUtils;
  import android.util.Log;
  import android.util.Pair;
  
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.server.wifi.nano.WifiMetricsProto;
  import com.android.server.wifi.util.ExternalCallbackTracker;
  import com.android.server.wifi.util.ScanResultUtil;
  import com.android.server.wifi.util.WifiPermissionsUtil;
  
  import java.io.FileDescriptor;
  import java.io.PrintWriter;
  import java.util.ArrayList;
  import java.util.Comparator;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.LinkedHashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Objects;
  import java.util.Set;
  
  /**
   * Network factory to handle trusted wifi network requests.
   */
  public class WifiNetworkFactory extends NetworkFactory {
      private static final String TAG = "WifiNetworkFactory";
      @VisibleForTesting
      private static final int SCORE_FILTER = 60;
      @VisibleForTesting
      public static final int CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS = 20 * 1000;  // 20 seconds
      @VisibleForTesting
      public static final int PERIODIC_SCAN_INTERVAL_MS = 10 * 1000; // 10 seconds
      @VisibleForTesting
      public static final int NETWORK_CONNECTION_TIMEOUT_MS = 30 * 1000; // 30 seconds
      @VisibleForTesting
      public static final int USER_SELECTED_NETWORK_CONNECT_RETRY_MAX = 3; // max of 3 retries.
      @VisibleForTesting
      public static final String UI_START_INTENT_ACTION =
              "com.android.settings.wifi.action.NETWORK_REQUEST";
      @VisibleForTesting
      public static final String UI_START_INTENT_CATEGORY = "android.intent.category.DEFAULT";
      @VisibleForTesting
      public static final String UI_START_INTENT_EXTRA_APP_NAME =
              "com.android.settings.wifi.extra.APP_NAME";
      @VisibleForTesting
      public static final String UI_START_INTENT_EXTRA_REQUEST_IS_FOR_SINGLE_NETWORK =
              "com.android.settings.wifi.extra.REQUEST_IS_FOR_SINGLE_NETWORK";
      // Capacity limit of approved Access Point per App
      @VisibleForTesting
      public static final int NUM_OF_ACCESS_POINT_LIMIT_PER_APP = 50;
  
      private final Context mContext;
      private final ActivityManager mActivityManager;
      private final AlarmManager mAlarmManager;
      private final AppOpsManager mAppOpsManager;
      private final Clock mClock;
      private final Handler mHandler;
      private final WifiInjector mWifiInjector;
      private final WifiConnectivityManager mWifiConnectivityManager;
      private final WifiConfigManager mWifiConfigManager;
      private final WifiConfigStore mWifiConfigStore;
      private final WifiPermissionsUtil mWifiPermissionsUtil;
      private final WifiMetrics mWifiMetrics;
      private final WifiScanner.ScanSettings mScanSettings;
      private final NetworkFactoryScanListener mScanListener;
      private final PeriodicScanAlarmListener mPeriodicScanTimerListener;
      private final ConnectionTimeoutAlarmListener mConnectionTimeoutAlarmListener;
      private final ExternalCallbackTracker<INetworkRequestMatchCallback> mRegisteredCallbacks;
      private final Messenger mSrcMessenger;
      // Store all user approved access points for apps.
      @VisibleForTesting
      public final Map<String, LinkedHashSet<AccessPoint>> mUserApprovedAccessPointMap;
      private WifiScanner mWifiScanner;
  
      private int mGenericConnectionReqCount = 0;
      // Request that is being actively processed. All new requests start out as an "active" request
      // because we're processing it & handling all the user interactions associated with it. Once we
      // successfully connect to the network, we transition that request to "connected".
      private NetworkRequest mActiveSpecificNetworkRequest;
      private WifiNetworkSpecifier mActiveSpecificNetworkRequestSpecifier;
      // Request corresponding to the the network that the device is currently connected to.
      private NetworkRequest mConnectedSpecificNetworkRequest;
      private WifiNetworkSpecifier mConnectedSpecificNetworkRequestSpecifier;
      private WifiConfiguration mUserSelectedNetwork;
      private int mUserSelectedNetworkConnectRetryCount;
      private List<ScanResult> mActiveMatchedScanResults;
      // Verbose logging flag.
      private boolean mVerboseLoggingEnabled = false;
      private boolean mPeriodicScanTimerSet = false;
      private boolean mConnectionTimeoutSet = false;
      private boolean mIsPeriodicScanPaused = false;
      // We sent a new connection request and are waiting for connection success.
      private boolean mPendingConnectionSuccess = false;
      private boolean mWifiEnabled = false;
      /**
       * Indicates that we have new data to serialize.
       */
      private boolean mHasNewDataToSerialize = false;
  
      /**
       * Helper class to store an access point that the user previously approved for a specific app.
       * TODO(b/123014687): Move to a common util class.
       */
      public static class AccessPoint {
          public final String ssid;
          public final MacAddress bssid;
          public final @SecurityType int networkType;
  
          AccessPoint(@NonNull String ssid, @NonNull MacAddress bssid,
                      @SecurityType int networkType) {
              this.ssid = ssid;
              this.bssid = bssid;
              this.networkType = networkType;
          }
  
          @Override
          public int hashCode() {
              return Objects.hash(ssid, bssid, networkType);
          }
  
          @Override
          public boolean equals(Object obj) {
              if (this == obj) {
                  return true;
              }
              if (!(obj instanceof AccessPoint)) {
                  return false;
              }
              AccessPoint other = (AccessPoint) obj;
              return TextUtils.equals(this.ssid, other.ssid)
                      && Objects.equals(this.bssid, other.bssid)
                      && this.networkType == other.networkType;
          }
  
          @Override
          public String toString() {
              StringBuilder sb = new StringBuilder("AccessPoint: ");
              return sb.append(ssid)
                      .append(", ")
                      .append(bssid)
                      .append(", ")
                      .append(networkType)
                      .toString();
          }
      }
  
      // Scan listener for scan requests.
      private class NetworkFactoryScanListener implements WifiScanner.ScanListener {
          @Override
          public void onSuccess() {
              // Scan request succeeded, wait for results to report to external clients.
              if (mVerboseLoggingEnabled) {
                  Log.d(TAG, "Scan request succeeded");
              }
          }
  
          @Override
          public void onFailure(int reason, String description) {
              Log.e(TAG, "Scan failure received. reason: " + reason
                      + ", description: " + description);
              // TODO(b/113878056): Retry scan to workaround any transient scan failures.
              scheduleNextPeriodicScan();
          }
  
          @Override
          public void onResults(WifiScanner.ScanData[] scanDatas) {
              if (mVerboseLoggingEnabled) {
                  Log.d(TAG, "Scan results received");
              }
              // For single scans, the array size should always be 1.
              if (scanDatas.length != 1) {
                  Log.wtf(TAG, "Found more than 1 batch of scan results, Ignoring...");
                  return;
              }
              WifiScanner.ScanData scanData = scanDatas[0];
              ScanResult[] scanResults = scanData.getResults();
              if (mVerboseLoggingEnabled) {
                  Log.v(TAG, "Received " + scanResults.length + " scan results");
              }
              if (!handleScanResultsAndTriggerConnectIfUserApprovedMatchFound(scanResults)) {
                  // Didn't find an approved match, send the matching results to UI and schedule the
                  // next scan.
                  sendNetworkRequestMatchCallbacksForActiveRequest(mActiveMatchedScanResults);
                  scheduleNextPeriodicScan();
              }
          }
  
          @Override
          public void onFullResult(ScanResult fullScanResult) {
              // Ignore for single scans.
          }
  
          @Override
          public void onPeriodChanged(int periodInMs) {
              // Ignore for single scans.
          }
      };
  
      private class PeriodicScanAlarmListener implements AlarmManager.OnAlarmListener {
          @Override
          public void onAlarm() {
              // Trigger the next scan.
              startScan();
          }
      }
  
      private class ConnectionTimeoutAlarmListener implements AlarmManager.OnAlarmListener {
          @Override
          public void onAlarm() {
              Log.e(TAG, "Timed-out connecting to network");
              handleNetworkConnectionFailure(mUserSelectedNetwork);
          }
      }
  
      // Callback result from settings UI.
      private class NetworkFactoryUserSelectionCallback extends
              INetworkRequestUserSelectionCallback.Stub {
          private final NetworkRequest mNetworkRequest;
  
          NetworkFactoryUserSelectionCallback(NetworkRequest networkRequest) {
              mNetworkRequest = networkRequest;
          }
  
          @Override
          public void select(WifiConfiguration wifiConfiguration) {
              mHandler.post(() -> {
                  if (mActiveSpecificNetworkRequest != mNetworkRequest) {
                      Log.e(TAG, "Stale callback select received");
                      return;
                  }
                  handleConnectToNetworkUserSelection(wifiConfiguration);
              });
          }
  
          @Override
          public void reject() {
              mHandler.post(() -> {
                  if (mActiveSpecificNetworkRequest != mNetworkRequest) {
                      Log.e(TAG, "Stale callback reject received");
                      return;
                  }
                  handleRejectUserSelection();
              });
          }
      }
  
      private final Handler.Callback mNetworkConnectionTriggerCallback = (Message msg) -> {
          switch (msg.what) {
              // Success here means that an attempt to connect to the network has been initiated.
              case WifiManager.CONNECT_NETWORK_SUCCEEDED:
                  if (mVerboseLoggingEnabled) {
                      Log.v(TAG, "Triggered network connection");
                  }
                  break;
              case WifiManager.CONNECT_NETWORK_FAILED:
                  Log.e(TAG, "Failed to trigger network connection");
                  handleNetworkConnectionFailure(mUserSelectedNetwork);
                  break;
              default:
                  Log.e(TAG, "Unknown message " + msg.what);
          }
          return true;
      };
  
      /**
       * Module to interact with the wifi config store.
       */
      private class NetworkRequestDataSource implements NetworkRequestStoreData.DataSource {
          @Override
          public Map<String, Set<AccessPoint>> toSerialize() {
              // Clear the flag after writing to disk.
              mHasNewDataToSerialize = false;
              return new HashMap<>(mUserApprovedAccessPointMap);
          }
  
          @Override
          public void fromDeserialized(Map<String, Set<AccessPoint>> approvedAccessPointMap) {
              approvedAccessPointMap.forEach((key, value) ->
                      mUserApprovedAccessPointMap.put(key, new LinkedHashSet<>(value)));
          }
  
          @Override
          public void reset() {
              mUserApprovedAccessPointMap.clear();
          }
  
          @Override
          public boolean hasNewDataToSerialize() {
              return mHasNewDataToSerialize;
          }
      }
  
      public WifiNetworkFactory(Looper looper, Context context, NetworkCapabilities nc,
                                ActivityManager activityManager, AlarmManager alarmManager,
                                AppOpsManager appOpsManager,
                                Clock clock, WifiInjector wifiInjector,
                                WifiConnectivityManager connectivityManager,
                                WifiConfigManager configManager,
                                WifiConfigStore configStore,
                                WifiPermissionsUtil wifiPermissionsUtil,
                                WifiMetrics wifiMetrics) {
          super(looper, context, TAG, nc);
          mContext = context;
          mActivityManager = activityManager;
          mAlarmManager = alarmManager;
          mAppOpsManager = appOpsManager;
          mClock = clock;
          mHandler = new Handler(looper);
          mWifiInjector = wifiInjector;
          mWifiConnectivityManager = connectivityManager;
          mWifiConfigManager = configManager;
          mWifiConfigStore = configStore;
          mWifiPermissionsUtil = wifiPermissionsUtil;
          mWifiMetrics = wifiMetrics;
          // Create the scan settings.
          mScanSettings = new WifiScanner.ScanSettings();
          mScanSettings.type = WifiScanner.TYPE_HIGH_ACCURACY;
          mScanSettings.band = WifiScanner.WIFI_BAND_BOTH_WITH_DFS;
          mScanSettings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
          mScanListener = new NetworkFactoryScanListener();
          mPeriodicScanTimerListener = new PeriodicScanAlarmListener();
          mConnectionTimeoutAlarmListener = new ConnectionTimeoutAlarmListener();
          mRegisteredCallbacks = new ExternalCallbackTracker<INetworkRequestMatchCallback>(mHandler);
          mSrcMessenger = new Messenger(new Handler(looper, mNetworkConnectionTriggerCallback));
          mUserApprovedAccessPointMap = new HashMap<>();
  
          // register the data store for serializing/deserializing data.
          configStore.registerStoreData(
                  wifiInjector.makeNetworkRequestStoreData(new NetworkRequestDataSource()));
  
          setScoreFilter(SCORE_FILTER);
      }
  
      private void saveToStore() {
          // Set the flag to let WifiConfigStore that we have new data to write.
          mHasNewDataToSerialize = true;
          if (!mWifiConfigManager.saveToStore(true)) {
              Log.w(TAG, "Failed to save to store");
          }
      }
  
      /**
       * Enable verbose logging.
       */
      public void enableVerboseLogging(int verbose) {
          mVerboseLoggingEnabled = (verbose > 0);
      }
  
      /**
       * Add a new callback for network request match handling.
       */
      public void addCallback(IBinder binder, INetworkRequestMatchCallback callback,
                              int callbackIdentifier) {
          if (mActiveSpecificNetworkRequest == null) {
              Log.wtf(TAG, "No valid network request. Ignoring callback registration");
              try {
                  callback.onAbort();
              } catch (RemoteException e) {
                  Log.e(TAG, "Unable to invoke network request abort callback " + callback, e);
              }
              return;
          }
          if (!mRegisteredCallbacks.add(binder, callback, callbackIdentifier)) {
              Log.e(TAG, "Failed to add callback");
              return;
          }
          if (mVerboseLoggingEnabled) {
              Log.v(TAG, "Adding callback. Num callbacks: " + mRegisteredCallbacks.getNumCallbacks());
          }
          // Register our user selection callback.
          try {
              callback.onUserSelectionCallbackRegistration(
                      new NetworkFactoryUserSelectionCallback(mActiveSpecificNetworkRequest));
          } catch (RemoteException e) {
              Log.e(TAG, "Unable to invoke user selection registration callback " + callback, e);
              return;
          }
  
          // If we are already in the midst of processing a request, send matching callbacks
          // immediately on registering the callback.
          sendNetworkRequestMatchCallbacksForActiveRequest(mActiveMatchedScanResults);
      }
  
      /**
       * Remove an existing callback for network request match handling.
       */
      public void removeCallback(int callbackIdentifier) {
          mRegisteredCallbacks.remove(callbackIdentifier);
          if (mVerboseLoggingEnabled) {
              Log.v(TAG, "Removing callback. Num callbacks: "
                      + mRegisteredCallbacks.getNumCallbacks());
          }
      }
  
      private boolean canNewRequestOverrideExistingRequest(
              WifiNetworkSpecifier newRequest, WifiNetworkSpecifier existingRequest) {
          if (existingRequest == null) return true;
          // Request from app with NETWORK_SETTINGS can override any existing requests.
          if (mWifiPermissionsUtil.checkNetworkSettingsPermission(newRequest.requestorUid)) {
              return true;
          }
          // Request from fg app can override any existing requests.
          if (isRequestFromForegroundApp(newRequest.requestorPackageName)) return true;
          // Request from fg service can override only if the existing request is not from a fg app.
          if (!isRequestFromForegroundApp(existingRequest.requestorPackageName)) return true;
          Log.e(TAG, "Already processing request from a foreground app "
                  + existingRequest.requestorPackageName + ". Rejecting request from "
                  + newRequest.requestorPackageName);
          return false;
      }
  
      boolean isRequestWithNetworkSpecifierValid(NetworkRequest networkRequest) {
          NetworkSpecifier ns = networkRequest.networkCapabilities.getNetworkSpecifier();
          // Invalid network specifier.
          if (!(ns instanceof WifiNetworkSpecifier)) {
              Log.e(TAG, "Invalid network specifier mentioned. Rejecting");
              return false;
          }
          // Request cannot have internet capability since such a request can never be fulfilled.
          // (NetworkAgent for connection with WifiNetworkSpecifier will not have internet capability)
          if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
              Log.e(TAG, "Request with wifi network specifier cannot contain "
                      + "NET_CAPABILITY_INTERNET. Rejecting");
              return false;
          }
          return true;
      }
  
      /**
       * Check whether to accept the new network connection request.
       *
       * All the validation of the incoming request is done in this method.
       */
      @Override
      public boolean acceptRequest(NetworkRequest networkRequest, int score) {
          NetworkSpecifier ns = networkRequest.networkCapabilities.getNetworkSpecifier();
          if (ns == null) {
              // Generic wifi request. Always accept.
          } else {
              // Invalid request with network specifier.
              if (!isRequestWithNetworkSpecifierValid(networkRequest)) {
                  releaseRequestAsUnfulfillableByAnyFactory(networkRequest);
                  return false;
              }
              if (!mWifiEnabled) {
                  // Will re-evaluate when wifi is turned on.
                  Log.e(TAG, "Wifi off. Rejecting");
                  return false;
              }
              WifiNetworkSpecifier wns = (WifiNetworkSpecifier) ns;
              if (!WifiConfigurationUtil.validateNetworkSpecifier(wns)) {
                  Log.e(TAG, "Invalid network specifier."
                          + " Rejecting request from " + wns.requestorPackageName);
                  releaseRequestAsUnfulfillableByAnyFactory(networkRequest);
                  return false;
              }
              try {
                  mAppOpsManager.checkPackage(wns.requestorUid, wns.requestorPackageName);
              } catch (SecurityException e) {
                  Log.e(TAG, "Invalid uid/package name " + wns.requestorPackageName + ", "
                          + wns.requestorPackageName, e);
                  releaseRequestAsUnfulfillableByAnyFactory(networkRequest);
                  return false;
              }
              // Only allow specific wifi network request from foreground app/service.
              if (!mWifiPermissionsUtil.checkNetworkSettingsPermission(wns.requestorUid)
                      && !isRequestFromForegroundAppOrService(wns.requestorPackageName)) {
                  Log.e(TAG, "Request not from foreground app or service."
                          + " Rejecting request from " + wns.requestorPackageName);
                  releaseRequestAsUnfulfillableByAnyFactory(networkRequest);
                  return false;
              }
              // If there is an active request, only proceed if the new request is from a foreground
              // app.
              if (!canNewRequestOverrideExistingRequest(
                      wns, mActiveSpecificNetworkRequestSpecifier)) {
                  Log.e(TAG, "Request cannot override active request."
                          + " Rejecting request from " + wns.requestorPackageName);
                  releaseRequestAsUnfulfillableByAnyFactory(networkRequest);
                  return false;
              }
              // If there is a connected request, only proceed if the new request is from a foreground
              // app.
              if (!canNewRequestOverrideExistingRequest(
                      wns, mConnectedSpecificNetworkRequestSpecifier)) {
                  Log.e(TAG, "Request cannot override connected request."
                          + " Rejecting request from " + wns.requestorPackageName);
                  releaseRequestAsUnfulfillableByAnyFactory(networkRequest);
                  return false;
              }
              if (mVerboseLoggingEnabled) {
                  Log.v(TAG, "Accepted network request with specifier from fg "
                          + (isRequestFromForegroundApp(wns.requestorPackageName)
                          ? "app" : "service"));
              }
          }
          if (mVerboseLoggingEnabled) {
              Log.v(TAG, "Accepted network request " + networkRequest);
          }
          return true;
      }
  
      /**
       * Handle new network connection requests.
       *
       * The assumption here is that {@link #acceptRequest(NetworkRequest, int)} has already sanitized
       * the incoming request.
       */
      @Override
      protected void needNetworkFor(NetworkRequest networkRequest, int score) {
          NetworkSpecifier ns = networkRequest.networkCapabilities.getNetworkSpecifier();
          if (ns == null) {
              // Generic wifi request. Turn on auto-join if necessary.
              if (++mGenericConnectionReqCount == 1) {
                  mWifiConnectivityManager.setTrustedConnectionAllowed(true);
              }
          } else {
              // Invalid request with network specifier.
              if (!isRequestWithNetworkSpecifierValid(networkRequest)) {
                  releaseRequestAsUnfulfillableByAnyFactory(networkRequest);
                  return;
              }
              if (!mWifiEnabled) {
                  // Will re-evaluate when wifi is turned on.
                  Log.e(TAG, "Wifi off. Rejecting");
                  return;
              }
              retrieveWifiScanner();
              // Reset state from any previous request.
              setupForActiveRequest();
  
              // Store the active network request.
              mActiveSpecificNetworkRequest = new NetworkRequest(networkRequest);
              WifiNetworkSpecifier wns = (WifiNetworkSpecifier) ns;
              mActiveSpecificNetworkRequestSpecifier = new WifiNetworkSpecifier(
                      wns.ssidPatternMatcher, wns.bssidPatternMatcher, wns.wifiConfiguration,
                      wns.requestorUid, wns.requestorPackageName);
              mWifiMetrics.incrementNetworkRequestApiNumRequest();
  
              // Fetch the latest cached scan results to speed up network matching.
              ScanResult[] cachedScanResults = getFilteredCachedScanResults();
              if (mVerboseLoggingEnabled) {
                  Log.v(TAG, "Using cached " + cachedScanResults.length + " scan results");
              }
              if (!handleScanResultsAndTriggerConnectIfUserApprovedMatchFound(cachedScanResults)) {
                  // Start UI to let the user grant/disallow this request from the app.
                  startUi();
                  // Didn't find an approved match, send the matching results to UI and trigger
                  // periodic scans for finding a network in the request.
                  sendNetworkRequestMatchCallbacksForActiveRequest(mActiveMatchedScanResults);
                  startPeriodicScans();
              }
          }
      }
  
      @Override
      protected void releaseNetworkFor(NetworkRequest networkRequest) {
          NetworkSpecifier ns = networkRequest.networkCapabilities.getNetworkSpecifier();
          if (ns == null) {
              // Generic wifi request. Turn off auto-join if necessary.
              if (mGenericConnectionReqCount == 0) {
                  Log.e(TAG, "No valid network request to release");
                  return;
              }
              if (--mGenericConnectionReqCount == 0) {
                  mWifiConnectivityManager.setTrustedConnectionAllowed(false);
              }
          } else {
              // Invalid network specifier.
              if (!(ns instanceof WifiNetworkSpecifier)) {
                  Log.e(TAG, "Invalid network specifier mentioned. Ignoring");
                  return;
              }
              if (!mWifiEnabled) {
                  Log.e(TAG, "Wifi off. Ignoring");
                  return;
              }
              if (mActiveSpecificNetworkRequest == null && mConnectedSpecificNetworkRequest == null) {
                  Log.e(TAG, "Network release received with no active/connected request."
                          + " Ignoring");
                  return;
              }
              if (Objects.equals(mActiveSpecificNetworkRequest, networkRequest)) {
                  Log.i(TAG, "App released request, cancelling "
                          + mActiveSpecificNetworkRequest);
                  teardownForActiveRequest();
              } else if (Objects.equals(mConnectedSpecificNetworkRequest, networkRequest)) {
                  Log.i(TAG, "App released request, cancelling "
                          + mConnectedSpecificNetworkRequest);
                  teardownForConnectedNetwork();
              } else {
                  Log.e(TAG, "Network specifier does not match the active/connected request."
                          + " Ignoring");
              }
          }
      }
  
      @Override
      public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
          super.dump(fd, pw, args);
          pw.println(TAG + ": mGenericConnectionReqCount " + mGenericConnectionReqCount);
          pw.println(TAG + ": mActiveSpecificNetworkRequest " + mActiveSpecificNetworkRequest);
          pw.println(TAG + ": mUserApprovedAccessPointMap " + mUserApprovedAccessPointMap);
      }
  
      /**
       * Check if there is at least one connection request.
       */
      public boolean hasConnectionRequests() {
          return mGenericConnectionReqCount > 0 || mActiveSpecificNetworkRequest != null
                  || mConnectedSpecificNetworkRequest != null;
      }
  
      /**
       * Return the uid of the specific network request being processed if connected to the requested
       * network.
       *
       * @param connectedNetwork WifiConfiguration corresponding to the connected network.
       * @return Pair of uid & package name of the specific request (if any), else <-1, "">.
       */
      public Pair<Integer, String> getSpecificNetworkRequestUidAndPackageName(
              @NonNull WifiConfiguration connectedNetwork) {
          if (mUserSelectedNetwork == null || connectedNetwork == null) {
              return Pair.create(Process.INVALID_UID, "");
          }
          if (!isUserSelectedNetwork(connectedNetwork)) {
              Log.w(TAG, "Connected to unknown network " + connectedNetwork + ". Ignoring...");
              return Pair.create(Process.INVALID_UID, "");
          }
          if (mConnectedSpecificNetworkRequestSpecifier != null) {
              return Pair.create(mConnectedSpecificNetworkRequestSpecifier.requestorUid,
                      mConnectedSpecificNetworkRequestSpecifier.requestorPackageName);
          }
          if (mActiveSpecificNetworkRequestSpecifier != null) {
              return Pair.create(mActiveSpecificNetworkRequestSpecifier.requestorUid,
                      mActiveSpecificNetworkRequestSpecifier.requestorPackageName);
          }
          return Pair.create(Process.INVALID_UID, "");
      }
  
      // Helper method to add the provided network configuration to WifiConfigManager, if it does not
      // already exist & return the allocated network ID. This ID will be used in the CONNECT_NETWORK
      // request to ClientModeImpl.
      // If the network already exists, just return the network ID of the existing network.
      private int addNetworkToWifiConfigManager(@NonNull WifiConfiguration network) {
          WifiConfiguration existingSavedNetwork =
                  mWifiConfigManager.getConfiguredNetwork(network.configKey());
          if (existingSavedNetwork != null) {
              if (WifiConfigurationUtil.hasCredentialChanged(existingSavedNetwork, network)) {
                  // TODO (b/142035508): What if the user has a saved network with different
                  // credentials?
                  Log.w(TAG, "Network config already present in config manager, reusing");
              }
              return existingSavedNetwork.networkId;
          }
          NetworkUpdateResult networkUpdateResult =
                  mWifiConfigManager.addOrUpdateNetwork(
                          network, mActiveSpecificNetworkRequestSpecifier.requestorUid,
                          mActiveSpecificNetworkRequestSpecifier.requestorPackageName);
          if (mVerboseLoggingEnabled) {
              Log.v(TAG, "Added network to config manager " + networkUpdateResult.netId);
          }
          return networkUpdateResult.netId;
      }
  
      // Helper method to remove the provided network configuration from WifiConfigManager, if it was
      // added by an app's specifier request.
      private void disconnectAndRemoveNetworkFromWifiConfigManager(
              @Nullable WifiConfiguration network) {
          // Trigger a disconnect first.
          mWifiInjector.getClientModeImpl().disconnectCommand();
  
          if (network == null) return;
          WifiConfiguration wcmNetwork =
                  mWifiConfigManager.getConfiguredNetwork(network.configKey());
          if (wcmNetwork == null) {
              Log.e(TAG, "Network not present in config manager");
              return;
          }
          // Remove the network if it was added previously by an app's specifier request.
          if (wcmNetwork.ephemeral && wcmNetwork.fromWifiNetworkSpecifier) {
              boolean success =
                      mWifiConfigManager.removeNetwork(wcmNetwork.networkId, wcmNetwork.creatorUid);
              if (!success) {
                  Log.e(TAG, "Failed to remove network from config manager");
              } else if (mVerboseLoggingEnabled) {
                  Log.v(TAG, "Removed network from config manager " + wcmNetwork.networkId);
              }
          }
      }
  
      // Helper method to trigger a connection request & schedule a timeout alarm to track the
      // connection request.
      private void connectToNetwork(@NonNull WifiConfiguration network) {
          // Cancel connection timeout alarm for any previous connection attempts.
          cancelConnectionTimeout();
  
          // First add the network to WifiConfigManager and then use the obtained networkId
          // in the CONNECT_NETWORK request.
          // Note: We don't do any error checks on the networkId because ClientModeImpl will do the
          // necessary checks when processing CONNECT_NETWORK.
          int networkId = addNetworkToWifiConfigManager(network);
  
          mWifiMetrics.setNominatorForNetwork(networkId,
                  WifiMetricsProto.ConnectionEvent.NOMINATOR_SPECIFIER);
  
          // Send the connect request to ClientModeImpl.
          // TODO(b/117601161): Refactor this.
          Message msg = Message.obtain();
          msg.what = WifiManager.CONNECT_NETWORK;
          msg.arg1 = networkId;
          msg.replyTo = mSrcMessenger;
          mWifiInjector.getClientModeImpl().sendMessage(msg);
  
          // Post an alarm to handle connection timeout.
          scheduleConnectionTimeout();
      }
  
      private void handleConnectToNetworkUserSelectionInternal(WifiConfiguration network) {
          // Disable Auto-join so that NetworkFactory can take control of the network connection.
          mWifiConnectivityManager.setSpecificNetworkRequestInProgress(true);
  
          // Copy over the credentials from the app's request and then copy the ssid from user
          // selection.
          WifiConfiguration networkToConnect =
                  new WifiConfiguration(mActiveSpecificNetworkRequestSpecifier.wifiConfiguration);
          networkToConnect.SSID = network.SSID;
          // Set the WifiConfiguration.BSSID field to prevent roaming.
          networkToConnect.BSSID = findBestBssidFromActiveMatchedScanResultsForNetwork(network);
          networkToConnect.ephemeral = true;
          networkToConnect.fromWifiNetworkSpecifier = true;
  
          // Store the user selected network.
          mUserSelectedNetwork = networkToConnect;
  
          // Disconnect from the current network before issuing a new connect request.
          disconnectAndRemoveNetworkFromWifiConfigManager(mUserSelectedNetwork);
  
          // Trigger connection to the network.
          connectToNetwork(networkToConnect);
          // Triggered connection to network, now wait for the connection status.
          mPendingConnectionSuccess = true;
      }
  
      private void handleConnectToNetworkUserSelection(WifiConfiguration network) {
          Log.d(TAG, "User initiated connect to network: " + network.SSID);
  
          // Cancel the ongoing scans after user selection.
          cancelPeriodicScans();
  
          // Trigger connection attempts.
          handleConnectToNetworkUserSelectionInternal(network);
  
          // Add the network to the approved access point map for the app.
          addNetworkToUserApprovedAccessPointMap(mUserSelectedNetwork);
      }
  
      private void handleRejectUserSelection() {
          Log.w(TAG, "User dismissed notification, cancelling " + mActiveSpecificNetworkRequest);
          teardownForActiveRequest();
          mWifiMetrics.incrementNetworkRequestApiNumUserReject();
      }
  
      private boolean isUserSelectedNetwork(WifiConfiguration config) {
          if (!TextUtils.equals(mUserSelectedNetwork.SSID, config.SSID)) {
              return false;
          }
          if (!Objects.equals(
                  mUserSelectedNetwork.allowedKeyManagement, config.allowedKeyManagement)) {
              return false;
          }
          return true;
      }
  
      /**
       * Invoked by {@link ClientModeImpl} on end of connection attempt to a network.
       */
      public void handleConnectionAttemptEnded(
              int failureCode, @NonNull WifiConfiguration network) {
          if (failureCode == WifiMetrics.ConnectionEvent.FAILURE_NONE) {
              handleNetworkConnectionSuccess(network);
          } else {
              handleNetworkConnectionFailure(network);
          }
      }
  
      /**
       * Invoked by {@link ClientModeImpl} on successful connection to a network.
       */
      private void handleNetworkConnectionSuccess(@NonNull WifiConfiguration connectedNetwork) {
          if (mUserSelectedNetwork == null || connectedNetwork == null
                  || !mPendingConnectionSuccess) {
              return;
          }
          if (!isUserSelectedNetwork(connectedNetwork)) {
              Log.w(TAG, "Connected to unknown network " + connectedNetwork + ". Ignoring...");
              return;
          }
          Log.d(TAG, "Connected to network " + mUserSelectedNetwork);
          for (INetworkRequestMatchCallback callback : mRegisteredCallbacks.getCallbacks()) {
              try {
                  callback.onUserSelectionConnectSuccess(mUserSelectedNetwork);
              } catch (RemoteException e) {
                  Log.e(TAG, "Unable to invoke network request connect failure callback "
                          + callback, e);
              }
          }
          // transition the request from "active" to "connected".
          setupForConnectedRequest();
          mWifiMetrics.incrementNetworkRequestApiNumConnectSuccess();
      }
  
      /**
       * Invoked by {@link ClientModeImpl} on failure to connect to a network.
       */
      private void handleNetworkConnectionFailure(@NonNull WifiConfiguration failedNetwork) {
          if (mUserSelectedNetwork == null || failedNetwork == null || !mPendingConnectionSuccess) {
              return;
          }
          if (!isUserSelectedNetwork(failedNetwork)) {
              Log.w(TAG, "Connection failed to unknown network " + failedNetwork + ". Ignoring...");
              return;
          }
          Log.w(TAG, "Failed to connect to network " + mUserSelectedNetwork);
          if (mUserSelectedNetworkConnectRetryCount++ < USER_SELECTED_NETWORK_CONNECT_RETRY_MAX) {
              Log.i(TAG, "Retrying connection attempt, attempt# "
                      + mUserSelectedNetworkConnectRetryCount);
              connectToNetwork(mUserSelectedNetwork);
              return;
          }
          Log.e(TAG, "Connection failures, cancelling " + mUserSelectedNetwork);
          for (INetworkRequestMatchCallback callback : mRegisteredCallbacks.getCallbacks()) {
              try {
                  callback.onUserSelectionConnectFailure(mUserSelectedNetwork);
              } catch (RemoteException e) {
                  Log.e(TAG, "Unable to invoke network request connect failure callback "
                          + callback, e);
              }
          }
          teardownForActiveRequest();
      }
  
      /**
       * Invoked by {@link ClientModeImpl} to indicate screen state changes.
       */
      public void handleScreenStateChanged(boolean screenOn) {
          // If there is no active request or if the user has already selected a network,
          // ignore screen state changes.
          if (mActiveSpecificNetworkRequest == null || mUserSelectedNetwork != null) return;
  
          // Pause periodic scans when the screen is off & resume when the screen is on.
          if (screenOn) {
              if (mVerboseLoggingEnabled) Log.v(TAG, "Resuming scans on screen on");
              startScan();
              mIsPeriodicScanPaused = false;
          } else {
              if (mVerboseLoggingEnabled) Log.v(TAG, "Pausing scans on screen off");
              cancelPeriodicScans();
              mIsPeriodicScanPaused = true;
          }
      }
  
      /**
       * Invoked by {@link ClientModeImpl} to indicate wifi state toggle.
       */
      public void setWifiState(boolean enabled) {
          if (mVerboseLoggingEnabled) Log.v(TAG, "setWifiState " + enabled);
          if (enabled) {
              reevaluateAllRequests(); // Re-evaluate any pending requests.
          } else {
              if (mActiveSpecificNetworkRequest != null) {
                  Log.w(TAG, "Wifi off, cancelling " + mActiveSpecificNetworkRequest);
                  teardownForActiveRequest();
              }
              if (mConnectedSpecificNetworkRequest != null) {
                  Log.w(TAG, "Wifi off, cancelling " + mConnectedSpecificNetworkRequest);
                  teardownForConnectedNetwork();
              }
          }
          mWifiEnabled = enabled;
      }
  
      // Common helper method for start/end of active request processing.
      private void cleanupActiveRequest() {
          // Send the abort to the UI for the current active request.
          for (INetworkRequestMatchCallback callback : mRegisteredCallbacks.getCallbacks()) {
              try {
                  callback.onAbort();
              } catch (RemoteException e) {
                  Log.e(TAG, "Unable to invoke network request abort callback " + callback, e);
              }
          }
          // Force-release the network request to let the app know early that the attempt failed.
          if (mActiveSpecificNetworkRequest != null) {
              releaseRequestAsUnfulfillableByAnyFactory(mActiveSpecificNetworkRequest);
          }
          // Reset the active network request.
          mActiveSpecificNetworkRequest = null;
          mActiveSpecificNetworkRequestSpecifier = null;
          mUserSelectedNetwork = null;
          mUserSelectedNetworkConnectRetryCount = 0;
          mIsPeriodicScanPaused = false;
          mActiveMatchedScanResults = null;
          mPendingConnectionSuccess = false;
          // Cancel periodic scan, connection timeout alarm.
          cancelPeriodicScans();
          cancelConnectionTimeout();
          // Remove any callbacks registered for the request.
          mRegisteredCallbacks.clear();
      }
  
      // Invoked at the start of new active request processing.
      private void setupForActiveRequest() {
          if (mActiveSpecificNetworkRequest != null) {
              cleanupActiveRequest();
          }
      }
  
      // Invoked at the termination of current active request processing.
      private void teardownForActiveRequest() {
          cleanupActiveRequest();
          // ensure there is no connected request in progress.
          if (mConnectedSpecificNetworkRequest == null) {
              mWifiConnectivityManager.setSpecificNetworkRequestInProgress(false);
          }
      }
  
      // Invoked at the start of new connected request processing.
      private void setupForConnectedRequest() {
          mConnectedSpecificNetworkRequest = mActiveSpecificNetworkRequest;
          mConnectedSpecificNetworkRequestSpecifier = mActiveSpecificNetworkRequestSpecifier;
          mActiveSpecificNetworkRequest = null;
          mActiveSpecificNetworkRequestSpecifier = null;
          mActiveMatchedScanResults = null;
          mPendingConnectionSuccess = false;
          // Cancel connection timeout alarm.
          cancelConnectionTimeout();
      }
  
      // Invoked at the termination of current connected request processing.
      private void teardownForConnectedNetwork() {
          Log.i(TAG, "Disconnecting from network on reset");
          disconnectAndRemoveNetworkFromWifiConfigManager(mUserSelectedNetwork);
          mConnectedSpecificNetworkRequest = null;
          mConnectedSpecificNetworkRequestSpecifier = null;
          // ensure there is no active request in progress.
          if (mActiveSpecificNetworkRequest == null) {
              mWifiConnectivityManager.setSpecificNetworkRequestInProgress(false);
          }
      }
  
      /**
       * Check if the request comes from foreground app/service.
       */
      private boolean isRequestFromForegroundAppOrService(@NonNull String requestorPackageName) {
          try {
              return mActivityManager.getPackageImportance(requestorPackageName)
                      <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
          } catch (SecurityException e) {
              Log.e(TAG, "Failed to check the app state", e);
              return false;
          }
      }
  
      /**
       * Check if the request comes from foreground app.
       */
      private boolean isRequestFromForegroundApp(@NonNull String requestorPackageName) {
          try {
              return mActivityManager.getPackageImportance(requestorPackageName)
                      <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
          } catch (SecurityException e) {
              Log.e(TAG, "Failed to check the app state", e);
              return false;
          }
      }
  
      /**
       * Helper method to populate WifiScanner handle. This is done lazily because
       * WifiScanningService is started after WifiService.
       */
      private void retrieveWifiScanner() {
          if (mWifiScanner != null) return;
          mWifiScanner = mWifiInjector.getWifiScanner();
          checkNotNull(mWifiScanner);
      }
  
      private void startPeriodicScans() {
          if (mActiveSpecificNetworkRequestSpecifier == null) {
              Log.e(TAG, "Periodic scan triggered when there is no active network request. "
                      + "Ignoring...");
              return;
          }
          WifiNetworkSpecifier wns = mActiveSpecificNetworkRequestSpecifier;
          WifiConfiguration wifiConfiguration = wns.wifiConfiguration;
          if (wifiConfiguration.hiddenSSID) {
              mScanSettings.hiddenNetworks = new WifiScanner.ScanSettings.HiddenNetwork[1];
              // Can't search for SSID pattern in hidden networks.
              mScanSettings.hiddenNetworks[0] =
                      new WifiScanner.ScanSettings.HiddenNetwork(
                              addEnclosingQuotes(wns.ssidPatternMatcher.getPath()));
          }
          startScan();
      }
  
      private void cancelPeriodicScans() {
          if (mPeriodicScanTimerSet) {
              mAlarmManager.cancel(mPeriodicScanTimerListener);
              mPeriodicScanTimerSet = false;
          }
          // Clear the hidden networks field after each request.
          mScanSettings.hiddenNetworks = null;
      }
  
      private void scheduleNextPeriodicScan() {
          if (mIsPeriodicScanPaused) {
              Log.e(TAG, "Scan triggered when periodic scanning paused. Ignoring...");
              return;
          }
          mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                  mClock.getElapsedSinceBootMillis() + PERIODIC_SCAN_INTERVAL_MS,
                  TAG, mPeriodicScanTimerListener, mHandler);
          mPeriodicScanTimerSet = true;
      }
  
      private void startScan() {
          if (mActiveSpecificNetworkRequestSpecifier == null) {
              Log.e(TAG, "Scan triggered when there is no active network request. Ignoring...");
              return;
          }
          if (mVerboseLoggingEnabled) {
              Log.v(TAG, "Starting the next scan for " + mActiveSpecificNetworkRequestSpecifier);
          }
          // Create a worksource using the caller's UID.
          WorkSource workSource = new WorkSource(mActiveSpecificNetworkRequestSpecifier.requestorUid);
          mWifiScanner.startScan(mScanSettings, mScanListener, workSource);
      }
  
      private boolean doesScanResultMatchWifiNetworkSpecifier(
              WifiNetworkSpecifier wns, ScanResult scanResult) {
          if (!wns.ssidPatternMatcher.match(scanResult.SSID)) {
              return false;
          }
          MacAddress bssid = MacAddress.fromString(scanResult.BSSID);
          MacAddress matchBaseAddress = wns.bssidPatternMatcher.first;
          MacAddress matchMask = wns.bssidPatternMatcher.second;
          if (!bssid.matches(matchBaseAddress, matchMask)) {
              return false;
          }
          if (ScanResultMatchInfo.getNetworkType(wns.wifiConfiguration)
                  != ScanResultMatchInfo.getNetworkType(scanResult)) {
              return false;
          }
          return true;
      }
  
      // Loops through the scan results and finds scan results matching the active network
      // request.
      private List<ScanResult> getNetworksMatchingActiveNetworkRequest(
              ScanResult[] scanResults) {
          if (mActiveSpecificNetworkRequestSpecifier == null) {
              Log.e(TAG, "Scan results received with no active network request. Ignoring...");
              return new ArrayList<>();
          }
          List<ScanResult> matchedScanResults = new ArrayList<>();
          WifiNetworkSpecifier wns = mActiveSpecificNetworkRequestSpecifier;
  
          for (ScanResult scanResult : scanResults) {
              if (doesScanResultMatchWifiNetworkSpecifier(wns, scanResult)) {
                  matchedScanResults.add(scanResult);
              }
          }
          if (mVerboseLoggingEnabled) {
              Log.v(TAG, "List of scan results matching the active request "
                      + matchedScanResults);
          }
          return matchedScanResults;
      }
  
      private void sendNetworkRequestMatchCallbacksForActiveRequest(
              @Nullable List<ScanResult> matchedScanResults) {
          if (matchedScanResults == null || matchedScanResults.isEmpty()) return;
          if (mRegisteredCallbacks.getNumCallbacks() == 0) {
              Log.e(TAG, "No callback registered for sending network request matches. "
                      + "Ignoring...");
              return;
          }
          for (INetworkRequestMatchCallback callback : mRegisteredCallbacks.getCallbacks()) {
              try {
                  callback.onMatch(matchedScanResults);
              } catch (RemoteException e) {
                  Log.e(TAG, "Unable to invoke network request match callback " + callback, e);
              }
          }
      }
  
      private void cancelConnectionTimeout() {
          if (mConnectionTimeoutSet) {
              mAlarmManager.cancel(mConnectionTimeoutAlarmListener);
              mConnectionTimeoutSet = false;
          }
      }
  
      private void scheduleConnectionTimeout() {
          mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                  mClock.getElapsedSinceBootMillis() + NETWORK_CONNECTION_TIMEOUT_MS,
                  TAG, mConnectionTimeoutAlarmListener, mHandler);
          mConnectionTimeoutSet = true;
      }
  
      private @NonNull CharSequence getAppName(@NonNull String packageName, int uid) {
          ApplicationInfo applicationInfo = null;
          try {
              applicationInfo = mContext.getPackageManager().getApplicationInfoAsUser(
                  packageName, 0, UserHandle.getUserId(uid));
          } catch (PackageManager.NameNotFoundException e) {
              Log.e(TAG, "Failed to find app name for " + packageName);
              return "";
          }
          CharSequence appName = mContext.getPackageManager().getApplicationLabel(applicationInfo);
          return (appName != null) ? appName : "";
      }
  
      private void startUi() {
          Intent intent = new Intent();
          intent.setAction(UI_START_INTENT_ACTION);
          intent.addCategory(UI_START_INTENT_CATEGORY);
          intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
          intent.putExtra(UI_START_INTENT_EXTRA_APP_NAME,
                  getAppName(mActiveSpecificNetworkRequestSpecifier.requestorPackageName,
                             mActiveSpecificNetworkRequestSpecifier.requestorUid));
          intent.putExtra(UI_START_INTENT_EXTRA_REQUEST_IS_FOR_SINGLE_NETWORK,
                  isActiveRequestForSingleNetwork());
          mContext.startActivityAsUser(intent, UserHandle.getUserHandleForUid(
                  mActiveSpecificNetworkRequestSpecifier.requestorUid));
      }
  
      // Helper method to determine if the specifier does not contain any patterns and matches
      // a single access point.
      private boolean isActiveRequestForSingleAccessPoint() {
          if (mActiveSpecificNetworkRequestSpecifier == null) return false;
  
          if (mActiveSpecificNetworkRequestSpecifier.ssidPatternMatcher.getType()
                  != PatternMatcher.PATTERN_LITERAL) {
              return false;
          }
          if (!Objects.equals(
                  mActiveSpecificNetworkRequestSpecifier.bssidPatternMatcher.second,
                  MacAddress.BROADCAST_ADDRESS)) {
              return false;
          }
          return true;
      }
  
      // Helper method to determine if the specifier does not contain any patterns and matches
      // a single network.
      private boolean isActiveRequestForSingleNetwork() {
          if (mActiveSpecificNetworkRequestSpecifier == null) return false;
  
          if (mActiveSpecificNetworkRequestSpecifier.ssidPatternMatcher.getType()
                  == PatternMatcher.PATTERN_LITERAL) {
              return true;
          }
          if (Objects.equals(
                  mActiveSpecificNetworkRequestSpecifier.bssidPatternMatcher.second,
                  MacAddress.BROADCAST_ADDRESS)) {
              return true;
          }
          return false;
      }
  
      // Will return the best bssid to use for the current request's connection.
      //
      // Note: This will never return null, unless there is some internal error.
      // For ex:
      // i) The latest scan results were empty.
      // ii) The latest scan result did not contain any BSSID for the SSID user chose.
      private @Nullable String findBestBssidFromActiveMatchedScanResultsForNetwork(
              @NonNull WifiConfiguration network) {
          if (mActiveSpecificNetworkRequestSpecifier == null
                  || mActiveMatchedScanResults == null) return null;
          ScanResult selectedScanResult = mActiveMatchedScanResults
                  .stream()
                  .filter(scanResult -> Objects.equals(
                          ScanResultMatchInfo.fromScanResult(scanResult),
                          ScanResultMatchInfo.fromWifiConfiguration(network)))
                  .max(Comparator.comparing(scanResult -> scanResult.level))
                  .orElse(null);
          if (selectedScanResult == null) { // Should never happen.
              Log.wtf(TAG, "Expected to find at least one matching scan result");
              return null;
          }
          if (mVerboseLoggingEnabled) {
              Log.v(TAG, "Best bssid selected for the request " + selectedScanResult);
          }
          return selectedScanResult.BSSID;
      }
  
      private @Nullable ScanResult
              findUserApprovedAccessPointForActiveRequestFromActiveMatchedScanResults() {
          if (mActiveSpecificNetworkRequestSpecifier == null
                  || mActiveMatchedScanResults == null) return null;
          String requestorPackageName = mActiveSpecificNetworkRequestSpecifier.requestorPackageName;
          Set<AccessPoint> approvedAccessPoints =
                  mUserApprovedAccessPointMap.get(requestorPackageName);
          if (approvedAccessPoints == null) return null;
          for (ScanResult scanResult : mActiveMatchedScanResults) {
              ScanResultMatchInfo fromScanResult = ScanResultMatchInfo.fromScanResult(scanResult);
              AccessPoint accessPoint =
                      new AccessPoint(scanResult.SSID,
                              MacAddress.fromString(scanResult.BSSID), fromScanResult.networkType);
              if (approvedAccessPoints.contains(accessPoint)) {
                  // keep the most recently used AP in the end
                  approvedAccessPoints.remove(accessPoint);
                  approvedAccessPoints.add(accessPoint);
                  if (mVerboseLoggingEnabled) {
                      Log.v(TAG, "Found " + accessPoint
                              + " in user approved access point for " + requestorPackageName);
                  }
                  return scanResult;
              }
          }
          return null;
      }
  
      // Helper method to store the all the BSSIDs matching the network from the matched scan results
      private void addNetworkToUserApprovedAccessPointMap(@NonNull WifiConfiguration network) {
          if (mActiveSpecificNetworkRequestSpecifier == null
                  || mActiveMatchedScanResults == null) return;
          // Note: This hopefully is a list of size 1, because we want to store a 1:1 mapping
          // from user selection and the AP that was approved. But, since we get a WifiConfiguration
          // object representing an entire network from UI, we need to ensure that all the visible
          // BSSIDs matching the original request and the selected network are stored.
          Set<AccessPoint> newUserApprovedAccessPoints = new HashSet<>();
  
          ScanResultMatchInfo fromWifiConfiguration =
                  ScanResultMatchInfo.fromWifiConfiguration(network);
          for (ScanResult scanResult : mActiveMatchedScanResults) {
              ScanResultMatchInfo fromScanResult = ScanResultMatchInfo.fromScanResult(scanResult);
              if (fromScanResult.equals(fromWifiConfiguration)) {
                  AccessPoint approvedAccessPoint =
                          new AccessPoint(scanResult.SSID, MacAddress.fromString(scanResult.BSSID),
                                  fromScanResult.networkType);
                  newUserApprovedAccessPoints.add(approvedAccessPoint);
              }
          }
          if (newUserApprovedAccessPoints.isEmpty()) return;
  
          String requestorPackageName = mActiveSpecificNetworkRequestSpecifier.requestorPackageName;
          LinkedHashSet<AccessPoint> approvedAccessPoints =
                  mUserApprovedAccessPointMap.get(requestorPackageName);
          if (approvedAccessPoints == null) {
              approvedAccessPoints = new LinkedHashSet<>();
              mUserApprovedAccessPointMap.put(requestorPackageName, approvedAccessPoints);
              // Note the new app in metrics.
              mWifiMetrics.incrementNetworkRequestApiNumApps();
          }
          if (mVerboseLoggingEnabled) {
              Log.v(TAG, "Adding " + newUserApprovedAccessPoints
                      + " to user approved access point for " + requestorPackageName);
          }
          // keep the most recently added APs in the end
          approvedAccessPoints.removeAll(newUserApprovedAccessPoints);
          approvedAccessPoints.addAll(newUserApprovedAccessPoints);
          cleanUpLRUAccessPoints(approvedAccessPoints);
          saveToStore();
      }
  
      /**
       * Handle scan results
       * a) Find all scan results matching the active network request.
       * b) If the request is for a single bssid, check if the matching ScanResult was pre-approved
       * by the user.
       * c) If yes to (b), trigger a connect immediately and returns true. Else, returns false.
       *
       * @param scanResults Array of {@link ScanResult} to be processed.
       * @return true if a pre-approved network was found for connection, false otherwise.
       */
      private boolean handleScanResultsAndTriggerConnectIfUserApprovedMatchFound(
              ScanResult[] scanResults) {
          List<ScanResult> matchedScanResults =
                  getNetworksMatchingActiveNetworkRequest(scanResults);
          if ((mActiveMatchedScanResults == null || mActiveMatchedScanResults.isEmpty())
                  && !matchedScanResults.isEmpty()) {
              // only note the first match size in metrics (chances of this changing in further
              // scans is pretty low)
              mWifiMetrics.incrementNetworkRequestApiMatchSizeHistogram(
                      matchedScanResults.size());
          }
          mActiveMatchedScanResults = matchedScanResults;
  
          ScanResult approvedScanResult = null;
          if (isActiveRequestForSingleAccessPoint()) {
              approvedScanResult =
                      findUserApprovedAccessPointForActiveRequestFromActiveMatchedScanResults();
          }
          if (approvedScanResult != null
                  && !mWifiConfigManager.wasEphemeralNetworkDeleted(
                  ScanResultUtil.createQuotedSSID(approvedScanResult.SSID))) {
              Log.v(TAG, "Approved access point found in matching scan results. "
                      + "Triggering connect " + approvedScanResult);
              handleConnectToNetworkUserSelectionInternal(
                      ScanResultUtil.createNetworkFromScanResult(approvedScanResult));
              mWifiMetrics.incrementNetworkRequestApiNumUserApprovalBypass();
              return true;
          }
          if (mVerboseLoggingEnabled) {
              Log.v(TAG, "No approved access points found in matching scan results");
          }
          return false;
      }
  
      /**
       * Retrieve the latest cached scan results from wifi scanner and filter out any
       * {@link ScanResult} older than {@link #CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS}.
       */
      private @NonNull ScanResult[] getFilteredCachedScanResults() {
          List<ScanResult> cachedScanResults = mWifiScanner.getSingleScanResults();
          if (cachedScanResults == null || cachedScanResults.isEmpty()) return new ScanResult[0];
          long currentTimeInMillis = mClock.getElapsedSinceBootMillis();
          return cachedScanResults.stream()
                  .filter(scanResult
                          -> ((currentTimeInMillis - (scanResult.timestamp / 1000))
                          < CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS))
                  .toArray(ScanResult[]::new);
      }
  
      /**
       * Clean up least recently used Access Points if specified app reach the limit.
       */
      private static void cleanUpLRUAccessPoints(Set<AccessPoint> approvedAccessPoints) {
          if (approvedAccessPoints.size() <= NUM_OF_ACCESS_POINT_LIMIT_PER_APP) {
              return;
          }
          Iterator iter = approvedAccessPoints.iterator();
          while (iter.hasNext() && approvedAccessPoints.size() > NUM_OF_ACCESS_POINT_LIMIT_PER_APP) {
              iter.next();
              iter.remove();
          }
      }
  
      /**
       * Remove all user approved access points for the specified app.
       */
      public void removeUserApprovedAccessPointsForApp(@NonNull String packageName) {
          if (mUserApprovedAccessPointMap.remove(packageName) != null) {
              Log.i(TAG, "Removing all approved access points for " + packageName);
          }
          saveToStore();
      }
  
      /**
       * Clear all internal state (for network settings reset).
       */
      public void clear() {
          mUserApprovedAccessPointMap.clear();
          Log.i(TAG, "Cleared all internal state");
          saveToStore();
      }
  }
  
  