blob: effc1aa547352adb575806a4c0256f1c3f5e418c [file] [log] [blame]
Jeff Davidsondc960e22014-04-07 15:19:44 -07001/*
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
17package android.net;
18
Sundeep Ghumanc63d5b02016-12-07 13:32:33 -080019import android.annotation.Nullable;
Jeff Davidson7be8e972014-07-16 17:24:46 -070020import android.annotation.SystemApi;
Jeremy Joslind1daf6d2016-11-28 17:47:35 -080021import android.os.Bundle;
Jeff Davidsondc960e22014-04-07 15:19:44 -070022import android.os.Parcel;
23import android.os.Parcelable;
24
Jeff Davidson6a4b2202014-04-16 17:29:40 -070025import java.util.Objects;
Stephen Chen4efd1ca2017-08-18 15:49:58 -070026import java.util.Set;
Jeff Davidson6a4b2202014-04-16 17:29:40 -070027
Jeff Davidsondc960e22014-04-07 15:19:44 -070028/**
29 * A network identifier along with a score for the quality of that network.
30 *
31 * @hide
32 */
Jeff Davidson7be8e972014-07-16 17:24:46 -070033@SystemApi
Jeff Davidsondc960e22014-04-07 15:19:44 -070034public class ScoredNetwork implements Parcelable {
Sundeep Ghuman6c929622016-12-16 13:30:45 -080035
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
Sundeep Ghuman699deaf2017-02-13 15:32:13 -080040 * NetworkBadging.Badging} enums.
Sundeep Ghuman6c929622016-12-16 13:30:45 -080041 */
42 public static final String ATTRIBUTES_KEY_BADGING_CURVE =
43 "android.net.attributes.key.BADGING_CURVE";
Jeremy Joslind1daf6d2016-11-28 17:47:35 -080044 /**
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 */
Sundeep Ghumanc63d5b02016-12-07 13:32:33 -080057 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";
Jeff Davidsondc960e22014-04-07 15:19:44 -070068
Sundeep Ghuman699deaf2017-02-13 15:32:13 -080069 /** A {@link NetworkKey} uniquely identifying this network. */
70 public final NetworkKey networkKey;
71
Jeff Davidsondc960e22014-04-07 15:19:44 -070072 /**
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 /**
Jeremy Joslin85870d62016-03-16 20:07:30 -070082 * 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 /**
Jeremy Joslind1daf6d2016-11-28 17:47:35 -080092 * An additional collection of optional attributes set by
93 * the Network Recommendation Provider.
94 *
Sundeep Ghumanc63d5b02016-12-07 13:32:33 -080095 * @see #ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL
Sundeep Ghumanc3cd62f2017-02-06 14:54:09 -080096 * @see #ATTRIBUTES_KEY_RANKING_SCORE_OFFSET
Jeremy Joslind1daf6d2016-11-28 17:47:35 -080097 */
Sundeep Ghumanc63d5b02016-12-07 13:32:33 -080098 @Nullable
Jeremy Joslind1daf6d2016-11-28 17:47:35 -080099 public final Bundle attributes;
100
101 /**
Jeff Davidsondc960e22014-04-07 15:19:44 -0700102 * 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) {
Jeremy Joslin85870d62016-03-16 20:07:30 -0700113 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) {
Sundeep Ghuman23f98eb2016-12-12 14:36:03 -0800130 this(networkKey, rssiCurve, meteredHint, null /* attributes */);
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800131 }
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,
Sundeep Ghumanc63d5b02016-12-07 13:32:33 -0800148 @Nullable Bundle attributes) {
Jeff Davidsondc960e22014-04-07 15:19:44 -0700149 this.networkKey = networkKey;
150 this.rssiCurve = rssiCurve;
Jeremy Joslin85870d62016-03-16 20:07:30 -0700151 this.meteredHint = meteredHint;
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800152 this.attributes = attributes;
Jeff Davidsondc960e22014-04-07 15:19:44 -0700153 }
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 }
Sundeep Ghumanc63d5b02016-12-07 13:32:33 -0800162 meteredHint = (in.readByte() == 1);
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800163 attributes = in.readBundle();
Jeff Davidsondc960e22014-04-07 15:19:44 -0700164 }
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 }
Jeremy Joslin85870d62016-03-16 20:07:30 -0700180 out.writeByte((byte) (meteredHint ? 1 : 0));
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800181 out.writeBundle(attributes);
Jeff Davidsondc960e22014-04-07 15:19:44 -0700182 }
183
184 @Override
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700185 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
Jeremy Joslin85870d62016-03-16 20:07:30 -0700191 return Objects.equals(networkKey, that.networkKey)
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800192 && Objects.equals(rssiCurve, that.rssiCurve)
193 && Objects.equals(meteredHint, that.meteredHint)
Stephen Chen4efd1ca2017-08-18 15:49:58 -0700194 && 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;
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700216 }
217
218 @Override
219 public int hashCode() {
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800220 return Objects.hash(networkKey, rssiCurve, meteredHint, attributes);
Jeff Davidson6a4b2202014-04-16 17:29:40 -0700221 }
222
223 @Override
Jeff Davidsondc960e22014-04-07 15:19:44 -0700224 public String toString() {
Sundeep Ghuman23ccbcd2017-01-11 16:54:08 -0800225 StringBuilder out = new StringBuilder(
226 "ScoredNetwork{" +
Jeremy Joslind1daf6d2016-11-28 17:47:35 -0800227 "networkKey=" + networkKey +
228 ", rssiCurve=" + rssiCurve +
Sundeep Ghuman23ccbcd2017-01-11 16:54:08 -0800229 ", meteredHint=" + meteredHint);
Sundeep Ghuman7a9612f2017-01-12 13:48:51 -0800230 // calling isEmpty will unparcel the bundle so its contents can be converted to a string
231 if (attributes != null && !attributes.isEmpty()) {
Sundeep Ghuman23ccbcd2017-01-11 16:54:08 -0800232 out.append(", attributes=" + attributes);
233 }
234 out.append('}');
235 return out.toString();
Jeff Davidsondc960e22014-04-07 15:19:44 -0700236 }
237
Sundeep Ghumanc63d5b02016-12-07 13:32:33 -0800238 /**
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
Sundeep Ghuman6c929622016-12-16 13:30:45 -0800286 /**
Sundeep Ghuman699deaf2017-02-13 15:32:13 -0800287 * Return the {@link NetworkBadging.Badging} enum for this network for the given RSSI, derived from the
Sundeep Ghuman6c929622016-12-16 13:30:45 -0800288 * 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 */
Sundeep Ghuman699deaf2017-02-13 15:32:13 -0800294 @NetworkBadging.Badging
Sundeep Ghuman6c929622016-12-16 13:30:45 -0800295 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
Sundeep Ghuman699deaf2017-02-13 15:32:13 -0800302 return NetworkBadging.BADGING_NONE;
Sundeep Ghuman6c929622016-12-16 13:30:45 -0800303 }
304
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700305 public static final @android.annotation.NonNull Parcelable.Creator<ScoredNetwork> CREATOR =
Jeff Davidsondc960e22014-04-07 15:19:44 -0700306 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}