From beb41b54577c14a1ccd736758d9b9b32f2340768 Mon Sep 17 00:00:00 2001 From: Cody Kesting Date: Tue, 17 Dec 2019 08:51:32 -0800 Subject: [PATCH 1/3] Make ConnectivityReport Parcelable. ConnectivityReport is defined inside ConnectivityDiagnosticsManager. In order for ConnectivityReport to be used in aidl interfaces, it must implement the Parcelable interface. Bug: 143187964 Test: compiles Test: atest FrameworksNetTests Change-Id: I8e862c78bd84b5da14203f61ce44beb1ca4a623d --- .../net/ConnectivityDiagnosticsManager.java | 149 +++++++++++++++-- .../ConnectivityDiagnosticsManagerTest.java | 158 ++++++++++++++++++ 2 files changed, 296 insertions(+), 11 deletions(-) create mode 100644 tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java index 6afdb5ef1b..af35ccf386 100644 --- a/core/java/android/net/ConnectivityDiagnosticsManager.java +++ b/core/java/android/net/ConnectivityDiagnosticsManager.java @@ -18,10 +18,16 @@ package android.net; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; import android.os.PersistableBundle; +import com.android.internal.annotations.VisibleForTesting; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -60,25 +66,38 @@ public class ConnectivityDiagnosticsManager { /** @hide */ public ConnectivityDiagnosticsManager() {} + /** @hide */ + @VisibleForTesting + public static boolean persistableBundleEquals( + @Nullable PersistableBundle a, @Nullable PersistableBundle b) { + if (a == b) return true; + if (a == null || b == null) return false; + if (!Objects.equals(a.keySet(), b.keySet())) return false; + for (String key : a.keySet()) { + if (!Objects.equals(a.get(key), b.get(key))) return false; + } + return true; + } + /** Class that includes connectivity information for a specific Network at a specific time. */ - public static class ConnectivityReport { + public static final class ConnectivityReport implements Parcelable { /** The Network for which this ConnectivityReport applied */ - @NonNull public final Network network; + @NonNull private final Network mNetwork; /** * The timestamp for the report. The timestamp is taken from {@link * System#currentTimeMillis}. */ - public final long reportTimestamp; + private final long mReportTimestamp; /** LinkProperties available on the Network at the reported timestamp */ - @NonNull public final LinkProperties linkProperties; + @NonNull private final LinkProperties mLinkProperties; /** NetworkCapabilities available on the Network at the reported timestamp */ - @NonNull public final NetworkCapabilities networkCapabilities; + @NonNull private final NetworkCapabilities mNetworkCapabilities; /** PersistableBundle that may contain additional info about the report */ - @NonNull public final PersistableBundle additionalInfo; + @NonNull private final PersistableBundle mAdditionalInfo; /** * Constructor for ConnectivityReport. @@ -101,12 +120,120 @@ public class ConnectivityDiagnosticsManager { @NonNull LinkProperties linkProperties, @NonNull NetworkCapabilities networkCapabilities, @NonNull PersistableBundle additionalInfo) { - this.network = network; - this.reportTimestamp = reportTimestamp; - this.linkProperties = linkProperties; - this.networkCapabilities = networkCapabilities; - this.additionalInfo = additionalInfo; + mNetwork = network; + mReportTimestamp = reportTimestamp; + mLinkProperties = linkProperties; + mNetworkCapabilities = networkCapabilities; + mAdditionalInfo = additionalInfo; } + + /** + * Returns the Network for this ConnectivityReport. + * + * @return The Network for which this ConnectivityReport applied + */ + @NonNull + public Network getNetwork() { + return mNetwork; + } + + /** + * Returns the epoch timestamp (milliseconds) for when this report was taken. + * + * @return The timestamp for the report. Taken from {@link System#currentTimeMillis}. + */ + public long getReportTimestamp() { + return mReportTimestamp; + } + + /** + * Returns the LinkProperties available when this report was taken. + * + * @return LinkProperties available on the Network at the reported timestamp + */ + @NonNull + public LinkProperties getLinkProperties() { + return new LinkProperties(mLinkProperties); + } + + /** + * Returns the NetworkCapabilities when this report was taken. + * + * @return NetworkCapabilities available on the Network at the reported timestamp + */ + @NonNull + public NetworkCapabilities getNetworkCapabilities() { + return new NetworkCapabilities(mNetworkCapabilities); + } + + /** + * Returns a PersistableBundle with additional info for this report. + * + * @return PersistableBundle that may contain additional info about the report + */ + @NonNull + public PersistableBundle getAdditionalInfo() { + return new PersistableBundle(mAdditionalInfo); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (!(o instanceof ConnectivityReport)) return false; + final ConnectivityReport that = (ConnectivityReport) o; + + // PersistableBundle is optimized to avoid unparcelling data unless fields are + // referenced. Because of this, use {@link ConnectivityDiagnosticsManager#equals} over + // {@link PersistableBundle#kindofEquals}. + return mReportTimestamp == that.mReportTimestamp + && mNetwork.equals(that.mNetwork) + && mLinkProperties.equals(that.mLinkProperties) + && mNetworkCapabilities.equals(that.mNetworkCapabilities) + && persistableBundleEquals(mAdditionalInfo, that.mAdditionalInfo); + } + + @Override + public int hashCode() { + return Objects.hash( + mNetwork, + mReportTimestamp, + mLinkProperties, + mNetworkCapabilities, + mAdditionalInfo); + } + + /** {@inheritDoc} */ + @Override + public int describeContents() { + return 0; + } + + /** {@inheritDoc} */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(mNetwork, flags); + dest.writeLong(mReportTimestamp); + dest.writeParcelable(mLinkProperties, flags); + dest.writeParcelable(mNetworkCapabilities, flags); + dest.writeParcelable(mAdditionalInfo, flags); + } + + /** Implement the Parcelable interface */ + public static final @NonNull Creator CREATOR = + new Creator<>() { + public ConnectivityReport createFromParcel(Parcel in) { + return new ConnectivityReport( + in.readParcelable(null), + in.readLong(), + in.readParcelable(null), + in.readParcelable(null), + in.readParcelable(null)); + } + + public ConnectivityReport[] newArray(int size) { + return new ConnectivityReport[size]; + } + }; } /** Class that includes information for a suspected data stall on a specific Network */ diff --git a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java new file mode 100644 index 0000000000..5267f1f227 --- /dev/null +++ b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2020 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 android.net.ConnectivityDiagnosticsManager.ConnectivityReport; + +import static com.android.testutils.ParcelUtilsKt.assertParcelSane; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import android.os.PersistableBundle; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ConnectivityDiagnosticsManagerTest { + private static final int NET_ID = 1; + private static final long TIMESTAMP = 10L; + private static final String INTERFACE_NAME = "interface"; + private static final String BUNDLE_KEY = "key"; + private static final String BUNDLE_VALUE = "value"; + + private ConnectivityReport createSampleConnectivityReport() { + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setInterfaceName(INTERFACE_NAME); + + final NetworkCapabilities networkCapabilities = new NetworkCapabilities(); + networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS); + + final PersistableBundle bundle = new PersistableBundle(); + bundle.putString(BUNDLE_KEY, BUNDLE_VALUE); + + return new ConnectivityReport( + new Network(NET_ID), TIMESTAMP, linkProperties, networkCapabilities, bundle); + } + + private ConnectivityReport createDefaultConnectivityReport() { + return new ConnectivityReport( + new Network(0), + 0L, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY); + } + + @Test + public void testPersistableBundleEquals() { + assertFalse( + ConnectivityDiagnosticsManager.persistableBundleEquals( + null, PersistableBundle.EMPTY)); + assertFalse( + ConnectivityDiagnosticsManager.persistableBundleEquals( + PersistableBundle.EMPTY, null)); + assertTrue( + ConnectivityDiagnosticsManager.persistableBundleEquals( + PersistableBundle.EMPTY, PersistableBundle.EMPTY)); + + final PersistableBundle a = new PersistableBundle(); + a.putString(BUNDLE_KEY, BUNDLE_VALUE); + + final PersistableBundle b = new PersistableBundle(); + b.putString(BUNDLE_KEY, BUNDLE_VALUE); + + final PersistableBundle c = new PersistableBundle(); + c.putString(BUNDLE_KEY, null); + + assertFalse( + ConnectivityDiagnosticsManager.persistableBundleEquals(PersistableBundle.EMPTY, a)); + assertFalse( + ConnectivityDiagnosticsManager.persistableBundleEquals(a, PersistableBundle.EMPTY)); + + assertTrue(ConnectivityDiagnosticsManager.persistableBundleEquals(a, b)); + assertTrue(ConnectivityDiagnosticsManager.persistableBundleEquals(b, a)); + + assertFalse(ConnectivityDiagnosticsManager.persistableBundleEquals(a, c)); + assertFalse(ConnectivityDiagnosticsManager.persistableBundleEquals(c, a)); + } + + @Test + public void testConnectivityReportEquals() { + assertEquals(createSampleConnectivityReport(), createSampleConnectivityReport()); + assertEquals(createDefaultConnectivityReport(), createDefaultConnectivityReport()); + + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setInterfaceName(INTERFACE_NAME); + + final NetworkCapabilities networkCapabilities = new NetworkCapabilities(); + networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS); + + final PersistableBundle bundle = new PersistableBundle(); + bundle.putString(BUNDLE_KEY, BUNDLE_VALUE); + + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(NET_ID), + 0L, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(0), + TIMESTAMP, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(0), + 0L, + linkProperties, + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(0), + TIMESTAMP, + new LinkProperties(), + networkCapabilities, + PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(0), + TIMESTAMP, + new LinkProperties(), + new NetworkCapabilities(), + bundle)); + } + + @Test + public void testConnectivityReportParcelUnparcel() { + assertParcelSane(createSampleConnectivityReport(), 5); + } +} From 3d97b5e6352dc08de0c88ad5135e51f576195dd0 Mon Sep 17 00:00:00 2001 From: Cody Kesting Date: Tue, 17 Dec 2019 09:28:06 -0800 Subject: [PATCH 2/3] Make DataStallReport Parcelable. DataStallReport is defined inside ConnectivityDiagnosticsManager. In order for DataStallReport to be used in aidls, it must implement the Parcelable interface. Bug: 143187964 Test: compiles Test: atest FrameworksNetTests Change-Id: Idbb4885e2f67fb3f85d406a622ae45d34492dca4 --- .../net/ConnectivityDiagnosticsManager.java | 128 +++++++++++++++--- .../ConnectivityDiagnosticsManagerTest.java | 38 ++++++ 2 files changed, 147 insertions(+), 19 deletions(-) diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java index af35ccf386..13e97d8daa 100644 --- a/core/java/android/net/ConnectivityDiagnosticsManager.java +++ b/core/java/android/net/ConnectivityDiagnosticsManager.java @@ -53,16 +53,6 @@ import java.util.concurrent.Executor; * */ public class ConnectivityDiagnosticsManager { - public static final int DETECTION_METHOD_DNS_EVENTS = 1; - public static final int DETECTION_METHOD_TCP_METRICS = 2; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - prefix = {"DETECTION_METHOD_"}, - value = {DETECTION_METHOD_DNS_EVENTS, DETECTION_METHOD_TCP_METRICS}) - public @interface DetectionMethod {} - /** @hide */ public ConnectivityDiagnosticsManager() {} @@ -237,21 +227,31 @@ public class ConnectivityDiagnosticsManager { } /** Class that includes information for a suspected data stall on a specific Network */ - public static class DataStallReport { + public static final class DataStallReport implements Parcelable { + public static final int DETECTION_METHOD_DNS_EVENTS = 1; + public static final int DETECTION_METHOD_TCP_METRICS = 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + prefix = {"DETECTION_METHOD_"}, + value = {DETECTION_METHOD_DNS_EVENTS, DETECTION_METHOD_TCP_METRICS}) + public @interface DetectionMethod {} + /** The Network for which this DataStallReport applied */ - @NonNull public final Network network; + @NonNull private final Network mNetwork; /** * The timestamp for the report. The timestamp is taken from {@link * System#currentTimeMillis}. */ - public final long reportTimestamp; + private long mReportTimestamp; /** The detection method used to identify the suspected data stall */ - @DetectionMethod public final int detectionMethod; + @DetectionMethod private final int mDetectionMethod; /** PersistableBundle that may contain additional information on the suspected data stall */ - @NonNull public final PersistableBundle stallDetails; + @NonNull private final PersistableBundle mStallDetails; /** * Constructor for DataStallReport. @@ -270,11 +270,101 @@ public class ConnectivityDiagnosticsManager { long reportTimestamp, @DetectionMethod int detectionMethod, @NonNull PersistableBundle stallDetails) { - this.network = network; - this.reportTimestamp = reportTimestamp; - this.detectionMethod = detectionMethod; - this.stallDetails = stallDetails; + mNetwork = network; + mReportTimestamp = reportTimestamp; + mDetectionMethod = detectionMethod; + mStallDetails = stallDetails; } + + /** + * Returns the Network for this DataStallReport. + * + * @return The Network for which this DataStallReport applied + */ + @NonNull + public Network getNetwork() { + return mNetwork; + } + + /** + * Returns the epoch timestamp (milliseconds) for when this report was taken. + * + * @return The timestamp for the report. Taken from {@link System#currentTimeMillis}. + */ + public long getReportTimestamp() { + return mReportTimestamp; + } + + /** + * Returns the detection method used to identify this suspected data stall. + * + * @return The detection method used to identify the suspected data stall + */ + public int getDetectionMethod() { + return mDetectionMethod; + } + + /** + * Returns a PersistableBundle with additional info for this report. + * + * @return PersistableBundle that may contain additional information on the suspected data + * stall + */ + @NonNull + public PersistableBundle getStallDetails() { + return new PersistableBundle(mStallDetails); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (!(o instanceof DataStallReport)) return false; + final DataStallReport that = (DataStallReport) o; + + // PersistableBundle is optimized to avoid unparcelling data unless fields are + // referenced. Because of this, use {@link ConnectivityDiagnosticsManager#equals} over + // {@link PersistableBundle#kindofEquals}. + return mReportTimestamp == that.mReportTimestamp + && mDetectionMethod == that.mDetectionMethod + && mNetwork.equals(that.mNetwork) + && persistableBundleEquals(mStallDetails, that.mStallDetails); + } + + @Override + public int hashCode() { + return Objects.hash(mNetwork, mReportTimestamp, mDetectionMethod, mStallDetails); + } + + /** {@inheritDoc} */ + @Override + public int describeContents() { + return 0; + } + + /** {@inheritDoc} */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(mNetwork, flags); + dest.writeLong(mReportTimestamp); + dest.writeInt(mDetectionMethod); + dest.writeParcelable(mStallDetails, flags); + } + + /** Implement the Parcelable interface */ + public static final @NonNull Creator CREATOR = + new Creator() { + public DataStallReport createFromParcel(Parcel in) { + return new DataStallReport( + in.readParcelable(null), + in.readLong(), + in.readInt(), + in.readParcelable(null)); + } + + public DataStallReport[] newArray(int size) { + return new DataStallReport[size]; + } + }; } /** diff --git a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java index 5267f1f227..065add4fc2 100644 --- a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java +++ b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java @@ -17,6 +17,7 @@ package android.net; import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport; +import static android.net.ConnectivityDiagnosticsManager.DataStallReport; import static com.android.testutils.ParcelUtilsKt.assertParcelSane; @@ -34,6 +35,7 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class ConnectivityDiagnosticsManagerTest { private static final int NET_ID = 1; + private static final int DETECTION_METHOD = 2; private static final long TIMESTAMP = 10L; private static final String INTERFACE_NAME = "interface"; private static final String BUNDLE_KEY = "key"; @@ -155,4 +157,40 @@ public class ConnectivityDiagnosticsManagerTest { public void testConnectivityReportParcelUnparcel() { assertParcelSane(createSampleConnectivityReport(), 5); } + + private DataStallReport createSampleDataStallReport() { + final PersistableBundle bundle = new PersistableBundle(); + bundle.putString(BUNDLE_KEY, BUNDLE_VALUE); + return new DataStallReport(new Network(NET_ID), TIMESTAMP, DETECTION_METHOD, bundle); + } + + private DataStallReport createDefaultDataStallReport() { + return new DataStallReport(new Network(0), 0L, 0, PersistableBundle.EMPTY); + } + + @Test + public void testDataStallReportEquals() { + assertEquals(createSampleDataStallReport(), createSampleDataStallReport()); + assertEquals(createDefaultDataStallReport(), createDefaultDataStallReport()); + + final PersistableBundle bundle = new PersistableBundle(); + bundle.putString(BUNDLE_KEY, BUNDLE_VALUE); + + assertNotEquals( + createDefaultDataStallReport(), + new DataStallReport(new Network(NET_ID), 0L, 0, PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultDataStallReport(), + new DataStallReport(new Network(0), TIMESTAMP, 0, PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultDataStallReport(), + new DataStallReport(new Network(0), 0L, DETECTION_METHOD, PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultDataStallReport(), new DataStallReport(new Network(0), 0L, 0, bundle)); + } + + @Test + public void testDataStallReportParcelUnparcel() { + assertParcelSane(createSampleDataStallReport(), 4); + } } From fa7693d4f911428131c56e8d019e5b3ab5e0fc61 Mon Sep 17 00:00:00 2001 From: Cody Kesting Date: Tue, 17 Dec 2019 17:21:40 -0800 Subject: [PATCH 3/3] Add ConnectivityDiagnosticsManager to System Server. ConnectivityDiagnosticsManager should be accessed through Context#getService. In order for this to be possible, it needs to be defined as a service inside SystemServiceRegistry. Bug: 146444622 Test: compiles. Test: CTS testing in aosp/1211164 Change-Id: I6fe29441ecc9967a04ceb394b3bbe54830bef079 --- .../android/net/ConnectivityDiagnosticsManager.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java index 13e97d8daa..a6f9b96269 100644 --- a/core/java/android/net/ConnectivityDiagnosticsManager.java +++ b/core/java/android/net/ConnectivityDiagnosticsManager.java @@ -19,11 +19,13 @@ package android.net; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.Context; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -53,8 +55,14 @@ import java.util.concurrent.Executor; * */ public class ConnectivityDiagnosticsManager { + private final Context mContext; + private final IConnectivityManager mService; + /** @hide */ - public ConnectivityDiagnosticsManager() {} + public ConnectivityDiagnosticsManager(Context context, IConnectivityManager service) { + mContext = Preconditions.checkNotNull(context, "missing context"); + mService = Preconditions.checkNotNull(service, "missing IConnectivityManager"); + } /** @hide */ @VisibleForTesting