diff options
| -rw-r--r-- | api/system-current.txt | 3 | ||||
| -rw-r--r-- | core/java/android/net/ScoredNetwork.java | 72 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/net/ScoredNetworkTest.java | 169 |
3 files changed, 238 insertions, 6 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index f96a58b759a6..2066c9aeaceb 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -25824,8 +25824,9 @@ package android.net { ctor public ScoredNetwork(android.net.NetworkKey, android.net.RssiCurve, boolean, android.os.Bundle); method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); + field public static final java.lang.String ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL = "android.net.attributes.key.HAS_CAPTIVE_PORTAL"; + field public static final java.lang.String ATTRIBUTES_KEY_RANKING_SCORE_OFFSET = "android.net.attributes.key.RANKING_SCORE_OFFSET"; field public static final android.os.Parcelable.Creator<android.net.ScoredNetwork> CREATOR; - field public static final java.lang.String EXTRA_HAS_CAPTIVE_PORTAL = "android.net.extra.HAS_CAPTIVE_PORTAL"; field public final android.os.Bundle attributes; field public final boolean meteredHint; field public final android.net.NetworkKey networkKey; diff --git a/core/java/android/net/ScoredNetwork.java b/core/java/android/net/ScoredNetwork.java index cf81e9191cca..94e518707cf9 100644 --- a/core/java/android/net/ScoredNetwork.java +++ b/core/java/android/net/ScoredNetwork.java @@ -16,11 +16,14 @@ 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.lang.Math; +import java.lang.UnsupportedOperationException; import java.util.Objects; /** @@ -43,7 +46,17 @@ public class ScoredNetwork implements Parcelable { * <p> * If no value is associated with this key then it's unknown. */ - public static final String EXTRA_HAS_CAPTIVE_PORTAL = "android.net.extra.HAS_CAPTIVE_PORTAL"; + 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; @@ -71,8 +84,10 @@ public class ScoredNetwork implements Parcelable { * An additional collection of optional attributes set by * the Network Recommendation Provider. * - * @see #EXTRA_HAS_CAPTIVE_PORTAL + * @see #ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL + * @see #ATTRIBUTES_KEY_RANKING_SCORE_OFFSET_KEY */ + @Nullable public final Bundle attributes; /** @@ -122,7 +137,7 @@ public class ScoredNetwork implements Parcelable { * @param attributes optional provider specific attributes */ public ScoredNetwork(NetworkKey networkKey, RssiCurve rssiCurve, boolean meteredHint, - Bundle attributes) { + @Nullable Bundle attributes) { this.networkKey = networkKey; this.rssiCurve = rssiCurve; this.meteredHint = meteredHint; @@ -136,7 +151,7 @@ public class ScoredNetwork implements Parcelable { } else { rssiCurve = null; } - meteredHint = in.readByte() != 0; + meteredHint = (in.readByte() == 1); attributes = in.readBundle(); } @@ -156,7 +171,6 @@ public class ScoredNetwork implements Parcelable { } out.writeByte((byte) (meteredHint ? 1 : 0)); out.writeBundle(attributes); - } @Override @@ -187,6 +201,54 @@ public class ScoredNetwork implements Parcelable { '}'; } + /** + * 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; + } + } + public static final Parcelable.Creator<ScoredNetwork> CREATOR = new Parcelable.Creator<ScoredNetwork>() { @Override diff --git a/core/tests/coretests/src/android/net/ScoredNetworkTest.java b/core/tests/coretests/src/android/net/ScoredNetworkTest.java new file mode 100644 index 000000000000..9c3346e1238b --- /dev/null +++ b/core/tests/coretests/src/android/net/ScoredNetworkTest.java @@ -0,0 +1,169 @@ +/* + t Copyright (C) 2016 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 static org.junit.Assert.*; + +import android.os.Build; +import android.os.Bundle; +import android.os.Parcel; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; + +/** Unit tests for {@link ScoredNetwork}. */ +@RunWith(AndroidJUnit4.class) +public class ScoredNetworkTest { + + private static final int RSSI_START = -110; + private static final int TEST_RSSI = -50; + private static final byte TEST_SCORE = 5; + private static final RssiCurve CURVE = + new RssiCurve(RSSI_START, 10, new byte[] {-1, 0, 1, 2, 3, 4, TEST_SCORE, 6, 7}); + + private static final byte RANKING_SCORE_OFFSET = 13; + private static final Bundle ATTRIBUTES; + static { + ATTRIBUTES = new Bundle(); + ATTRIBUTES.putInt( + ScoredNetwork.ATTRIBUTES_KEY_RANKING_SCORE_OFFSET, RANKING_SCORE_OFFSET); + } + + private static final NetworkKey KEY + = new NetworkKey(new WifiKey("\"ssid\"", "00:00:00:00:00:00")); + + @Test + public void calculateRankingOffsetShouldThrowUnsupportedOperationException() { + // No curve or ranking score offset set in curve + ScoredNetwork scoredNetwork = new ScoredNetwork(KEY, null); + try { + scoredNetwork.calculateRankingScore(TEST_RSSI); + fail("Should have thrown UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + // expected + } + } + + @Test + public void calculateRankingOffsetWithRssiCurveShouldReturnExpectedScore() { + ScoredNetwork scoredNetwork = new ScoredNetwork(KEY, CURVE); + assertEquals(TEST_SCORE << Byte.SIZE, scoredNetwork.calculateRankingScore(TEST_RSSI)); + } + + @Test + public void rankingScoresShouldDifferByRankingScoreOffset() { + ScoredNetwork scoredNetwork1 = new ScoredNetwork(KEY, CURVE); + ScoredNetwork scoredNetwork2 + = new ScoredNetwork(KEY, CURVE, false /* meteredHint */, ATTRIBUTES); + int scoreDifference = + scoredNetwork2.calculateRankingScore(TEST_RSSI) + - scoredNetwork1.calculateRankingScore(TEST_RSSI); + assertEquals(RANKING_SCORE_OFFSET, scoreDifference); + } + + @Test + public void calculateRankingScoreShouldNotResultInIntegerOverflow() { + Bundle attr = new Bundle(); + attr.putInt(ScoredNetwork.ATTRIBUTES_KEY_RANKING_SCORE_OFFSET, Integer.MAX_VALUE); + ScoredNetwork scoredNetwork + = new ScoredNetwork(KEY, CURVE, false /* meteredHint */, attr); + assertEquals(Integer.MAX_VALUE, scoredNetwork.calculateRankingScore(TEST_RSSI)); + } + + @Test + public void calculateRankingScoreShouldNotResultInIntegerUnderflow() { + Bundle attr = new Bundle(); + attr.putInt(ScoredNetwork.ATTRIBUTES_KEY_RANKING_SCORE_OFFSET, Integer.MIN_VALUE); + ScoredNetwork scoredNetwork = + new ScoredNetwork(KEY, CURVE, false /* meteredHint */, attr); + assertEquals(Integer.MIN_VALUE, scoredNetwork.calculateRankingScore(RSSI_START)); + } + + @Test + public void hasRankingScoreShouldReturnFalse() { + ScoredNetwork network = new ScoredNetwork(KEY, null /* rssiCurve */); + assertFalse(network.hasRankingScore()); + } + + @Test + public void hasRankingScoreShouldReturnTrueWhenAttributesHasRankingScoreOffset() { + ScoredNetwork network = + new ScoredNetwork(KEY, null /* rssiCurve */, false /* meteredHint */, ATTRIBUTES); + assertTrue(network.hasRankingScore()); + } + + @Test + public void hasRankingScoreShouldReturnTrueWhenCurveIsPresent() { + ScoredNetwork network = + new ScoredNetwork(KEY, CURVE , false /* meteredHint */); + assertTrue(network.hasRankingScore()); + } + + @Test + public void shouldWriteAndReadFromParcelWhenAllFieldsSet() { + ScoredNetwork network = new ScoredNetwork(KEY, CURVE, true /* meteredHint */, ATTRIBUTES); + ScoredNetwork newNetwork; + + Parcel parcel = null; + try { + parcel = Parcel.obtain(); + network.writeToParcel(parcel, 0 /* flags */); + parcel.setDataPosition(0); + newNetwork = ScoredNetwork.CREATOR.createFromParcel(parcel); + } finally { + if (parcel != null) { + parcel.recycle(); + } + } + assertEquals(CURVE.start, newNetwork.rssiCurve.start); + assertEquals(CURVE.bucketWidth, newNetwork.rssiCurve.bucketWidth); + assertTrue(Arrays.equals(CURVE.rssiBuckets, newNetwork.rssiCurve.rssiBuckets)); + assertTrue(newNetwork.meteredHint); + assertNotNull(newNetwork.attributes); + assertEquals( + RANKING_SCORE_OFFSET, + newNetwork.attributes.getInt(ScoredNetwork.ATTRIBUTES_KEY_RANKING_SCORE_OFFSET)); + } + + @Test + public void shouldWriteAndReadFromParcelWithoutBundle() { + ScoredNetwork network = new ScoredNetwork(KEY, CURVE, true /* meteredHint */); + ScoredNetwork newNetwork; + + Parcel parcel = null; + try { + parcel = Parcel.obtain(); + network.writeToParcel(parcel, 0 /* flags */); + parcel.setDataPosition(0); + newNetwork = ScoredNetwork.CREATOR.createFromParcel(parcel); + } finally { + if (parcel != null) { + parcel.recycle(); + } + } + assertEquals(CURVE.start, newNetwork.rssiCurve.start); + assertEquals(CURVE.bucketWidth, newNetwork.rssiCurve.bucketWidth); + assertTrue(Arrays.equals(CURVE.rssiBuckets, newNetwork.rssiCurve.rssiBuckets)); + assertTrue(newNetwork.meteredHint); + assertNull(newNetwork.attributes); + } +} |