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 */ ScoredNetwork(NetworkKey networkKey, RssiCurve rssiCurve)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 */ ScoredNetwork(NetworkKey networkKey, RssiCurve rssiCurve, boolean meteredHint)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 */ ScoredNetwork(NetworkKey networkKey, RssiCurve rssiCurve, boolean meteredHint, @Nullable Bundle attributes)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 ScoredNetwork(Parcel in)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 describeContents()167 public int describeContents() { 168 return 0; 169 } 170 171 @Override writeToParcel(Parcel out, int flags)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 equals(Object o)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 bundleEquals(Bundle bundle1, Bundle bundle2)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 hashCode()219 public int hashCode() { 220 return Objects.hash(networkKey, rssiCurve, meteredHint, attributes); 221 } 222 223 @Override toString()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 */ hasRankingScore()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 */ calculateRankingScore(int rssi)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 calculateBadge(int rssi)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 @android.annotation.NonNull 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