blob: 64b3bf1e0aa003a9a75f916c7e6a278bf8735b03 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package android.net;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Objects;
import java.util.Set;
/**
* A network identifier along with a score for the quality of that network.
*
* @hide
*/
@SystemApi
public class ScoredNetwork implements Parcelable {
/**
* Key used with the {@link #attributes} bundle to define the badging curve.
*
* <p>The badging curve is a {@link RssiCurve} used to map different RSSI values to {@link
* NetworkBadging.Badging} enums.
*/
public static final String ATTRIBUTES_KEY_BADGING_CURVE =
"android.net.attributes.key.BADGING_CURVE";
/**
* Extra used with {@link #attributes} to specify whether the
* network is believed to have a captive portal.
* <p>
* This data may be used, for example, to display a visual indicator
* in a network selection list.
* <p>
* Note that the this extra conveys the possible presence of a
* captive portal, not its state or the user's ability to open
* the portal.
* <p>
* If no value is associated with this key then it's unknown.
*/
public static final String ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL =
"android.net.attributes.key.HAS_CAPTIVE_PORTAL";
/**
* Key used with the {@link #attributes} bundle to define the rankingScoreOffset int value.
*
* <p>The rankingScoreOffset is used when calculating the ranking score used to rank networks
* against one another. See {@link #calculateRankingScore} for more information.
*/
public static final String ATTRIBUTES_KEY_RANKING_SCORE_OFFSET =
"android.net.attributes.key.RANKING_SCORE_OFFSET";
/** A {@link NetworkKey} uniquely identifying this network. */
public final NetworkKey networkKey;
/**
* The {@link RssiCurve} representing the scores for this network based on the RSSI.
*
* <p>This field is optional and may be set to null to indicate that no score is available for
* this network at this time. Such networks, along with networks for which the scorer has not
* responded, are always prioritized below scored networks, regardless of the score.
*/
public final RssiCurve rssiCurve;
/**
* A boolean value that indicates whether or not the network is believed to be metered.
*
* <p>A network can be classified as metered if the user would be
* sensitive to heavy data usage on that connection due to monetary costs,
* data limitations or battery/performance issues. A typical example would
* be a wifi connection where the user would be charged for usage.
*/
public final boolean meteredHint;
/**
* An additional collection of optional attributes set by
* the Network Recommendation Provider.
*
* @see #ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL
* @see #ATTRIBUTES_KEY_RANKING_SCORE_OFFSET
*/
@Nullable
public final Bundle attributes;
/**
* Construct a new {@link ScoredNetwork}.
*
* @param networkKey the {@link NetworkKey} uniquely identifying this network.
* @param rssiCurve the {@link RssiCurve} representing the scores for this network based on the
* RSSI. This field is optional, and may be skipped to represent a network which the scorer
* has opted not to score at this time. Passing a null value here is strongly preferred to
* not returning any {@link ScoredNetwork} for a given {@link NetworkKey} because it
* indicates to the system not to request scores for this network in the future, although
* the scorer may choose to issue an out-of-band update at any time.
*/
public ScoredNetwork(NetworkKey networkKey, RssiCurve rssiCurve) {
this(networkKey, rssiCurve, false /* meteredHint */);
}
/**
* Construct a new {@link ScoredNetwork}.
*
* @param networkKey the {@link NetworkKey} uniquely identifying this network.
* @param rssiCurve the {@link RssiCurve} representing the scores for this network based on the
* RSSI. This field is optional, and may be skipped to represent a network which the scorer
* has opted not to score at this time. Passing a null value here is strongly preferred to
* not returning any {@link ScoredNetwork} for a given {@link NetworkKey} because it
* indicates to the system not to request scores for this network in the future, although
* the scorer may choose to issue an out-of-band update at any time.
* @param meteredHint A boolean value indicating whether or not the network is believed to be
* metered.
*/
public ScoredNetwork(NetworkKey networkKey, RssiCurve rssiCurve, boolean meteredHint) {
this(networkKey, rssiCurve, meteredHint, null /* attributes */);
}
/**
* Construct a new {@link ScoredNetwork}.
*
* @param networkKey the {@link NetworkKey} uniquely identifying this network
* @param rssiCurve the {@link RssiCurve} representing the scores for this network based on the
* RSSI. This field is optional, and may be skipped to represent a network which the scorer
* has opted not to score at this time. Passing a null value here is strongly preferred to
* not returning any {@link ScoredNetwork} for a given {@link NetworkKey} because it
* indicates to the system not to request scores for this network in the future, although
* the scorer may choose to issue an out-of-band update at any time.
* @param meteredHint a boolean value indicating whether or not the network is believed to be
* metered
* @param attributes optional provider specific attributes
*/
public ScoredNetwork(NetworkKey networkKey, RssiCurve rssiCurve, boolean meteredHint,
@Nullable Bundle attributes) {
this.networkKey = networkKey;
this.rssiCurve = rssiCurve;
this.meteredHint = meteredHint;
this.attributes = attributes;
}
private ScoredNetwork(Parcel in) {
networkKey = NetworkKey.CREATOR.createFromParcel(in);
if (in.readByte() == 1) {
rssiCurve = RssiCurve.CREATOR.createFromParcel(in);
} else {
rssiCurve = null;
}
meteredHint = (in.readByte() == 1);
attributes = in.readBundle();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags) {
networkKey.writeToParcel(out, flags);
if (rssiCurve != null) {
out.writeByte((byte) 1);
rssiCurve.writeToParcel(out, flags);
} else {
out.writeByte((byte) 0);
}
out.writeByte((byte) (meteredHint ? 1 : 0));
out.writeBundle(attributes);
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ScoredNetwork that = (ScoredNetwork) o;
return Objects.equals(networkKey, that.networkKey)
&& Objects.equals(rssiCurve, that.rssiCurve)
&& Objects.equals(meteredHint, that.meteredHint)
&& bundleEquals(attributes, that.attributes);
}
private boolean bundleEquals(Bundle bundle1, Bundle bundle2) {
if (bundle1 == bundle2) {
return true;
}
if (bundle1 == null || bundle2 == null) {
return false;
}
if (bundle1.size() != bundle2.size()) {
return false;
}
Set<String> keys = bundle1.keySet();
for (String key : keys) {
Object value1 = bundle1.get(key);
Object value2 = bundle2.get(key);
if (!Objects.equals(value1, value2)) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
return Objects.hash(networkKey, rssiCurve, meteredHint, attributes);
}
@NonNull
@Override
public String toString() {
StringBuilder out = new StringBuilder(
"ScoredNetwork{" +
"networkKey=" + networkKey +
", rssiCurve=" + rssiCurve +
", meteredHint=" + meteredHint);
// calling isEmpty will unparcel the bundle so its contents can be converted to a string
if (attributes != null && !attributes.isEmpty()) {
out.append(", attributes=" + attributes);
}
out.append('}');
return out.toString();
}
/**
* Returns true if a ranking score can be calculated for this network.
*
* @hide
*/
public boolean hasRankingScore() {
return (rssiCurve != null)
|| (attributes != null
&& attributes.containsKey(ATTRIBUTES_KEY_RANKING_SCORE_OFFSET));
}
/**
* Returns a ranking score for a given RSSI which can be used to comparatively
* rank networks.
*
* <p>The score obtained by the rssiCurve is bitshifted left by 8 bits to expand it to an
* integer and then the offset is added. If the addition operation overflows or underflows,
* Integer.MAX_VALUE and Integer.MIN_VALUE will be returned respectively.
*
* <p>{@link #hasRankingScore} should be called first to ensure this network is capable
* of returning a ranking score.
*
* @throws UnsupportedOperationException if there is no RssiCurve and no rankingScoreOffset
* for this network (hasRankingScore returns false).
*
* @hide
*/
public int calculateRankingScore(int rssi) throws UnsupportedOperationException {
if (!hasRankingScore()) {
throw new UnsupportedOperationException(
"Either rssiCurve or rankingScoreOffset is required to calculate the "
+ "ranking score");
}
int offset = 0;
if (attributes != null) {
offset += attributes.getInt(ATTRIBUTES_KEY_RANKING_SCORE_OFFSET, 0 /* default */);
}
int score = (rssiCurve == null) ? 0 : rssiCurve.lookupScore(rssi) << Byte.SIZE;
try {
return Math.addExact(score, offset);
} catch (ArithmeticException e) {
return (score < 0) ? Integer.MIN_VALUE : Integer.MAX_VALUE;
}
}
/**
* Return the {@link NetworkBadging.Badging} enum for this network for the given RSSI, derived from the
* badging curve.
*
* <p>If no badging curve is present, {@link #BADGE_NONE} will be returned.
*
* @param rssi The rssi level for which the badge should be calculated
*/
@NetworkBadging.Badging
public int calculateBadge(int rssi) {
if (attributes != null && attributes.containsKey(ATTRIBUTES_KEY_BADGING_CURVE)) {
RssiCurve badgingCurve =
attributes.getParcelable(ATTRIBUTES_KEY_BADGING_CURVE);
return badgingCurve.lookupScore(rssi);
}
return NetworkBadging.BADGING_NONE;
}
public static final @android.annotation.NonNull Parcelable.Creator<ScoredNetwork> CREATOR =
new Parcelable.Creator<ScoredNetwork>() {
@Override
public ScoredNetwork createFromParcel(Parcel in) {
return new ScoredNetwork(in);
}
@Override
public ScoredNetwork[] newArray(int size) {
return new ScoredNetwork[size];
}
};
}