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