/*
|
* 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.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(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);
|
}
|
|
@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];
|
}
|
};
|
}
|