content: /*
   * Copyright (C) 2016 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.hotspot2;
  
  import android.net.RssiCurve;
  
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.server.wifi.ScanDetail;
  import com.android.server.wifi.hotspot2.anqp.ANQPElement;
  import com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType;
  import com.android.server.wifi.hotspot2.anqp.HSWanMetricsElement;
  import com.android.server.wifi.hotspot2.anqp.IPAddressTypeAvailabilityElement;
  
  import java.util.HashMap;
  import java.util.Map;
  
  /**
   * This is an utility class for calculating score for Passpoint networks.
   */
  public class PasspointNetworkScore {
      /**
       * Award points for network that's a Passpoint home provider.
       */
      @VisibleForTesting
      public static final int HOME_PROVIDER_AWARD = 100;
  
      /**
       * Award points for network that provides Internet access.
       */
      @VisibleForTesting
      public static final int INTERNET_ACCESS_AWARD = 50;
  
      /**
       * Award points for public or private network.
       */
      @VisibleForTesting
      public static final int PUBLIC_OR_PRIVATE_NETWORK_AWARDS = 4;
  
      /**
       * Award points for personal or emergency network.
       */
      @VisibleForTesting
      public static final int PERSONAL_OR_EMERGENCY_NETWORK_AWARDS = 2;
  
      /**
       * Award points for network providing restricted or unknown IP address.
       */
      @VisibleForTesting
      public static final int RESTRICTED_OR_UNKNOWN_IP_AWARDS = 1;
  
      /**
       * Award points for network providing unrestricted IP address.
       */
      @VisibleForTesting
      public static final int UNRESTRICTED_IP_AWARDS = 2;
  
      /**
       * Penalty points for network with WAN port that's down or the load already reached the max.
       */
      @VisibleForTesting
      public static final int WAN_PORT_DOWN_OR_CAPPED_PENALTY = 50;
  
      // Award points for availability of IPv4 and IPv6 addresses.
      private static final Map<Integer, Integer> IPV4_SCORES = new HashMap<>();
      private static final Map<Integer, Integer> IPV6_SCORES = new HashMap<>();
  
      // Award points based on access network type.
      private static final Map<NetworkDetail.Ant, Integer> NETWORK_TYPE_SCORES = new HashMap<>();
  
      /**
       * Curve for calculating score for RSSI level.
       */
      @VisibleForTesting
      public static final RssiCurve RSSI_SCORE = new RssiCurve(-80 /* start */, 20 /* bucketWidth */,
              new byte[] {-10, 0, 10, 20, 30, 40} /* rssiBuckets */,
              20 /* activeNetworkRssiBoost */);
  
      static {
          // These are all arbitrarily chosen scores, subject to tuning.
  
          NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.FreePublic, PUBLIC_OR_PRIVATE_NETWORK_AWARDS);
          NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.ChargeablePublic,
                  PUBLIC_OR_PRIVATE_NETWORK_AWARDS);
          NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.PrivateWithGuest,
                  PUBLIC_OR_PRIVATE_NETWORK_AWARDS);
          NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.Private,
                  PUBLIC_OR_PRIVATE_NETWORK_AWARDS);
          NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.Personal, PERSONAL_OR_EMERGENCY_NETWORK_AWARDS);
          NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.EmergencyOnly,
                  PERSONAL_OR_EMERGENCY_NETWORK_AWARDS);
          NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.Wildcard, 0);
          NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.TestOrExperimental, 0);
  
          IPV4_SCORES.put(IPAddressTypeAvailabilityElement.IPV4_NOT_AVAILABLE, 0);
          IPV4_SCORES.put(IPAddressTypeAvailabilityElement.IPV4_PORT_RESTRICTED,
                  RESTRICTED_OR_UNKNOWN_IP_AWARDS);
          IPV4_SCORES.put(IPAddressTypeAvailabilityElement.IPV4_PORT_RESTRICTED_AND_SINGLE_NAT,
                  RESTRICTED_OR_UNKNOWN_IP_AWARDS);
          IPV4_SCORES.put(IPAddressTypeAvailabilityElement.IPV4_PORT_RESTRICTED_AND_DOUBLE_NAT,
                  RESTRICTED_OR_UNKNOWN_IP_AWARDS);
          IPV4_SCORES.put(IPAddressTypeAvailabilityElement.IPV4_UNKNOWN,
                  RESTRICTED_OR_UNKNOWN_IP_AWARDS);
          IPV4_SCORES.put(IPAddressTypeAvailabilityElement.IPV4_PUBLIC, UNRESTRICTED_IP_AWARDS);
          IPV4_SCORES.put(IPAddressTypeAvailabilityElement.IPV4_SINGLE_NAT, UNRESTRICTED_IP_AWARDS);
          IPV4_SCORES.put(IPAddressTypeAvailabilityElement.IPV4_DOUBLE_NAT, UNRESTRICTED_IP_AWARDS);
  
          IPV6_SCORES.put(IPAddressTypeAvailabilityElement.IPV6_NOT_AVAILABLE, 0);
          IPV6_SCORES.put(IPAddressTypeAvailabilityElement.IPV6_UNKNOWN,
                  RESTRICTED_OR_UNKNOWN_IP_AWARDS);
          IPV6_SCORES.put(IPAddressTypeAvailabilityElement.IPV6_AVAILABLE,
                  UNRESTRICTED_IP_AWARDS);
      }
  
  
      /**
       * Calculate and return a score associated with the given Passpoint network.
       * The score is calculated with the following preferences:
       * - Prefer home provider
       * - Prefer network that provides Internet access
       * - Prefer network with active WAN port with available load
       * - Prefer network that provides unrestricted IP address
       * - Prefer currently active network
       * - Prefer AP with higher RSSI
       *
       * This can be expanded for additional preference in the future (e.g. AP station count, link
       * speed, and etc).
       *
       * @param isHomeProvider Flag indicating home provider
       * @param scanDetail The ScanDetail associated with the AP
       * @param isActiveNetwork Flag indicating current active network
       * @return integer score
       */
      public static int calculateScore(boolean isHomeProvider, ScanDetail scanDetail,
              Map<ANQPElementType, ANQPElement> anqpElements, boolean isActiveNetwork) {
          NetworkDetail networkDetail = scanDetail.getNetworkDetail();
          int score = 0;
          if (isHomeProvider) {
              score += HOME_PROVIDER_AWARD;
          }
  
          // Adjust score based on Internet accessibility.
          score += (networkDetail.isInternet() ? 1 : -1) * INTERNET_ACCESS_AWARD;
  
          // Adjust score based on the network type.
          Integer ndScore = NETWORK_TYPE_SCORES.get(networkDetail.getAnt());
          if (ndScore != null) {
              score += ndScore;
          }
  
          if (anqpElements != null) {
              HSWanMetricsElement wm =
                      (HSWanMetricsElement) anqpElements.get(ANQPElementType.HSWANMetrics);
              if (wm != null) {
                  if (wm.getStatus() != HSWanMetricsElement.LINK_STATUS_UP || wm.isCapped()) {
                      score -= WAN_PORT_DOWN_OR_CAPPED_PENALTY;
                  }
              }
  
              IPAddressTypeAvailabilityElement ipa = (IPAddressTypeAvailabilityElement)
                      anqpElements.get(ANQPElementType.ANQPIPAddrAvailability);
  
              if (ipa != null) {
                  Integer v4Score = IPV4_SCORES.get(ipa.getV4Availability());
                  Integer v6Score = IPV6_SCORES.get(ipa.getV6Availability());
                  v4Score = v4Score != null ? v4Score : 0;
                  v6Score = v6Score != null ? v6Score : 0;
                  score += (v4Score + v6Score);
              }
          }
  
          score += RSSI_SCORE.lookupScore(scanDetail.getScanResult().level, isActiveNetwork);
          return score;
      }
  }
  