Home | History | Annotate | Download | only in net
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package android.net;
     18 
     19 import android.annotation.Nullable;
     20 import android.annotation.SystemApi;
     21 import android.os.Bundle;
     22 import android.os.Parcel;
     23 import android.os.Parcelable;
     24 
     25 import java.util.Objects;
     26 import java.util.Set;
     27 
     28 /**
     29  * A network identifier along with a score for the quality of that network.
     30  *
     31  * @hide
     32  */
     33 @SystemApi
     34 public class ScoredNetwork implements Parcelable {
     35 
     36   /**
     37      * Key used with the {@link #attributes} bundle to define the badging curve.
     38      *
     39      * <p>The badging curve is a {@link RssiCurve} used to map different RSSI values to {@link
     40      * NetworkBadging.Badging} enums.
     41      */
     42     public static final String ATTRIBUTES_KEY_BADGING_CURVE =
     43             "android.net.attributes.key.BADGING_CURVE";
     44     /**
     45      * Extra used with {@link #attributes} to specify whether the
     46      * network is believed to have a captive portal.
     47      * <p>
     48      * This data may be used, for example, to display a visual indicator
     49      * in a network selection list.
     50      * <p>
     51      * Note that the this extra conveys the possible presence of a
     52      * captive portal, not its state or the user's ability to open
     53      * the portal.
     54      * <p>
     55      * If no value is associated with this key then it's unknown.
     56      */
     57     public static final String ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL =
     58             "android.net.attributes.key.HAS_CAPTIVE_PORTAL";
     59 
     60     /**
     61      * Key used with the {@link #attributes} bundle to define the rankingScoreOffset int value.
     62      *
     63      * <p>The rankingScoreOffset is used when calculating the ranking score used to rank networks
     64      * against one another. See {@link #calculateRankingScore} for more information.
     65      */
     66     public static final String ATTRIBUTES_KEY_RANKING_SCORE_OFFSET =
     67             "android.net.attributes.key.RANKING_SCORE_OFFSET";
     68 
     69     /** A {@link NetworkKey} uniquely identifying this network. */
     70     public final NetworkKey networkKey;
     71 
     72     /**
     73      * The {@link RssiCurve} representing the scores for this network based on the RSSI.
     74      *
     75      * <p>This field is optional and may be set to null to indicate that no score is available for
     76      * this network at this time. Such networks, along with networks for which the scorer has not
     77      * responded, are always prioritized below scored networks, regardless of the score.
     78      */
     79     public final RssiCurve rssiCurve;
     80 
     81     /**
     82      * A boolean value that indicates whether or not the network is believed to be metered.
     83      *
     84      * <p>A network can be classified as metered if the user would be
     85      * sensitive to heavy data usage on that connection due to monetary costs,
     86      * data limitations or battery/performance issues. A typical example would
     87      * be a wifi connection where the user would be charged for usage.
     88      */
     89     public final boolean meteredHint;
     90 
     91     /**
     92      * An additional collection of optional attributes set by
     93      * the Network Recommendation Provider.
     94      *
     95      * @see #ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL
     96      * @see #ATTRIBUTES_KEY_RANKING_SCORE_OFFSET
     97      */
     98     @Nullable
     99     public final Bundle attributes;
    100 
    101     /**
    102      * Construct a new {@link ScoredNetwork}.
    103      *
    104      * @param networkKey the {@link NetworkKey} uniquely identifying this network.
    105      * @param rssiCurve the {@link RssiCurve} representing the scores for this network based on the
    106      *     RSSI. This field is optional, and may be skipped to represent a network which the scorer
    107      *     has opted not to score at this time. Passing a null value here is strongly preferred to
    108      *     not returning any {@link ScoredNetwork} for a given {@link NetworkKey} because it
    109      *     indicates to the system not to request scores for this network in the future, although
    110      *     the scorer may choose to issue an out-of-band update at any time.
    111      */
    112     public ScoredNetwork(NetworkKey networkKey, RssiCurve rssiCurve) {
    113         this(networkKey, rssiCurve, false /* meteredHint */);
    114     }
    115 
    116     /**
    117      * Construct a new {@link ScoredNetwork}.
    118      *
    119      * @param networkKey the {@link NetworkKey} uniquely identifying this network.
    120      * @param rssiCurve the {@link RssiCurve} representing the scores for this network based on the
    121      *     RSSI. This field is optional, and may be skipped to represent a network which the scorer
    122      *     has opted not to score at this time. Passing a null value here is strongly preferred to
    123      *     not returning any {@link ScoredNetwork} for a given {@link NetworkKey} because it
    124      *     indicates to the system not to request scores for this network in the future, although
    125      *     the scorer may choose to issue an out-of-band update at any time.
    126      * @param meteredHint A boolean value indicating whether or not the network is believed to be
    127      *     metered.
    128      */
    129     public ScoredNetwork(NetworkKey networkKey, RssiCurve rssiCurve, boolean meteredHint) {
    130         this(networkKey, rssiCurve, meteredHint, null /* attributes */);
    131     }
    132 
    133     /**
    134      * Construct a new {@link ScoredNetwork}.
    135      *
    136      * @param networkKey the {@link NetworkKey} uniquely identifying this network
    137      * @param rssiCurve the {@link RssiCurve} representing the scores for this network based on the
    138      *     RSSI. This field is optional, and may be skipped to represent a network which the scorer
    139      *     has opted not to score at this time. Passing a null value here is strongly preferred to
    140      *     not returning any {@link ScoredNetwork} for a given {@link NetworkKey} because it
    141      *     indicates to the system not to request scores for this network in the future, although
    142      *     the scorer may choose to issue an out-of-band update at any time.
    143      * @param meteredHint a boolean value indicating whether or not the network is believed to be
    144      *                    metered
    145      * @param attributes optional provider specific attributes
    146      */
    147     public ScoredNetwork(NetworkKey networkKey, RssiCurve rssiCurve, boolean meteredHint,
    148             @Nullable Bundle attributes) {
    149         this.networkKey = networkKey;
    150         this.rssiCurve = rssiCurve;
    151         this.meteredHint = meteredHint;
    152         this.attributes = attributes;
    153     }
    154 
    155     private ScoredNetwork(Parcel in) {
    156         networkKey = NetworkKey.CREATOR.createFromParcel(in);
    157         if (in.readByte() == 1) {
    158             rssiCurve = RssiCurve.CREATOR.createFromParcel(in);
    159         } else {
    160             rssiCurve = null;
    161         }
    162         meteredHint = (in.readByte() == 1);
    163         attributes = in.readBundle();
    164     }
    165 
    166     @Override
    167     public int describeContents() {
    168         return 0;
    169     }
    170 
    171     @Override
    172     public void writeToParcel(Parcel out, int flags) {
    173         networkKey.writeToParcel(out, flags);
    174         if (rssiCurve != null) {
    175             out.writeByte((byte) 1);
    176             rssiCurve.writeToParcel(out, flags);
    177         } else {
    178             out.writeByte((byte) 0);
    179         }
    180         out.writeByte((byte) (meteredHint ? 1 : 0));
    181         out.writeBundle(attributes);
    182     }
    183 
    184     @Override
    185     public boolean equals(Object o) {
    186         if (this == o) return true;
    187         if (o == null || getClass() != o.getClass()) return false;
    188 
    189         ScoredNetwork that = (ScoredNetwork) o;
    190 
    191         return Objects.equals(networkKey, that.networkKey)
    192                 && Objects.equals(rssiCurve, that.rssiCurve)
    193                 && Objects.equals(meteredHint, that.meteredHint)
    194                 && bundleEquals(attributes, that.attributes);
    195     }
    196 
    197     private boolean bundleEquals(Bundle bundle1, Bundle bundle2) {
    198         if (bundle1 == bundle2) {
    199             return true;
    200         }
    201         if (bundle1 == null || bundle2 == null) {
    202             return false;
    203         }
    204         if (bundle1.size() != bundle2.size()) {
    205             return false;
    206         }
    207         Set<String> keys = bundle1.keySet();
    208         for (String key : keys) {
    209             Object value1 = bundle1.get(key);
    210             Object value2 = bundle2.get(key);
    211             if (!Objects.equals(value1, value2)) {
    212                 return false;
    213             }
    214         }
    215         return true;
    216     }
    217 
    218     @Override
    219     public int hashCode() {
    220         return Objects.hash(networkKey, rssiCurve, meteredHint, attributes);
    221     }
    222 
    223     @Override
    224     public String toString() {
    225         StringBuilder out = new StringBuilder(
    226                 "ScoredNetwork{" +
    227                 "networkKey=" + networkKey +
    228                 ", rssiCurve=" + rssiCurve +
    229                 ", meteredHint=" + meteredHint);
    230         // calling isEmpty will unparcel the bundle so its contents can be converted to a string
    231         if (attributes != null && !attributes.isEmpty()) {
    232             out.append(", attributes=" + attributes);
    233         }
    234         out.append('}');
    235         return out.toString();
    236     }
    237 
    238     /**
    239      * Returns true if a ranking score can be calculated for this network.
    240      *
    241      * @hide
    242      */
    243     public boolean hasRankingScore() {
    244         return (rssiCurve != null)
    245                 || (attributes != null
    246                         && attributes.containsKey(ATTRIBUTES_KEY_RANKING_SCORE_OFFSET));
    247     }
    248 
    249     /**
    250      * Returns a ranking score for a given RSSI which can be used to comparatively
    251      * rank networks.
    252      *
    253      * <p>The score obtained by the rssiCurve is bitshifted left by 8 bits to expand it to an
    254      * integer and then the offset is added. If the addition operation overflows or underflows,
    255      * Integer.MAX_VALUE and Integer.MIN_VALUE will be returned respectively.
    256      *
    257      * <p>{@link #hasRankingScore} should be called first to ensure this network is capable
    258      * of returning a ranking score.
    259      *
    260      * @throws UnsupportedOperationException if there is no RssiCurve and no rankingScoreOffset
    261      * for this network (hasRankingScore returns false).
    262      *
    263      * @hide
    264      */
    265     public int calculateRankingScore(int rssi) throws UnsupportedOperationException {
    266         if (!hasRankingScore()) {
    267             throw new UnsupportedOperationException(
    268                     "Either rssiCurve or rankingScoreOffset is required to calculate the "
    269                             + "ranking score");
    270         }
    271 
    272         int offset = 0;
    273         if (attributes != null) {
    274              offset += attributes.getInt(ATTRIBUTES_KEY_RANKING_SCORE_OFFSET, 0 /* default */);
    275         }
    276 
    277         int score = (rssiCurve == null) ? 0 : rssiCurve.lookupScore(rssi) << Byte.SIZE;
    278 
    279         try {
    280             return Math.addExact(score, offset);
    281         } catch (ArithmeticException e) {
    282             return (score < 0) ? Integer.MIN_VALUE : Integer.MAX_VALUE;
    283         }
    284     }
    285 
    286     /**
    287      * Return the {@link NetworkBadging.Badging} enum for this network for the given RSSI, derived from the
    288      * badging curve.
    289      *
    290      * <p>If no badging curve is present, {@link #BADGE_NONE} will be returned.
    291      *
    292      * @param rssi The rssi level for which the badge should be calculated
    293      */
    294     @NetworkBadging.Badging
    295     public int calculateBadge(int rssi) {
    296         if (attributes != null && attributes.containsKey(ATTRIBUTES_KEY_BADGING_CURVE)) {
    297             RssiCurve badgingCurve =
    298                     attributes.getParcelable(ATTRIBUTES_KEY_BADGING_CURVE);
    299             return badgingCurve.lookupScore(rssi);
    300         }
    301 
    302         return NetworkBadging.BADGING_NONE;
    303     }
    304 
    305     public static final Parcelable.Creator<ScoredNetwork> CREATOR =
    306             new Parcelable.Creator<ScoredNetwork>() {
    307                 @Override
    308                 public ScoredNetwork createFromParcel(Parcel in) {
    309                     return new ScoredNetwork(in);
    310                 }
    311 
    312                 @Override
    313                 public ScoredNetwork[] newArray(int size) {
    314                     return new ScoredNetwork[size];
    315                 }
    316             };
    317 }
    318