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