/* * Copyright (C) 2019 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.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; import android.content.Context; import android.os.Binder; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; import android.os.RemoteException; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; /** * Class that provides utilities for collecting network connectivity diagnostics information. * Connectivity information is made available through triggerable diagnostics tools and by listening * to System validations. Some diagnostics information may be permissions-restricted. * *
ConnectivityDiagnosticsManager is intended for use by applications offering network * connectivity on a user device. These tools will provide several mechanisms for these applications * to be alerted to network conditions as well as diagnose potential network issues themselves. * *
The primary responsibilities of this class are to: * *
The possible values for this key are: * {@link #NETWORK_VALIDATION_RESULT_INVALID}, * {@link #NETWORK_VALIDATION_RESULT_VALID}, * {@link #NETWORK_VALIDATION_RESULT_PARTIALLY_VALID}, * {@link #NETWORK_VALIDATION_RESULT_SKIPPED}. * * @see android.net.NetworkCapabilities#NET_CAPABILITY_VALIDATED */ @NetworkValidationResult public static final String KEY_NETWORK_VALIDATION_RESULT = "networkValidationResult"; /** DNS probe. */ // TODO: link to INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS public static final int NETWORK_PROBE_DNS = 0x04; /** HTTP probe. */ // TODO: link to INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP public static final int NETWORK_PROBE_HTTP = 0x08; /** HTTPS probe. */ // TODO: link to INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS; public static final int NETWORK_PROBE_HTTPS = 0x10; /** Captive portal fallback probe. */ // TODO: link to INetworkMonitor.NETWORK_VALIDATION_FALLBACK public static final int NETWORK_PROBE_FALLBACK = 0x20; /** Private DNS (DNS over TLS) probd. */ // TODO: link to INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS public static final int NETWORK_PROBE_PRIVATE_DNS = 0x40; /** @hide */ @IntDef( prefix = {"NETWORK_PROBE_"}, value = { NETWORK_PROBE_DNS, NETWORK_PROBE_HTTP, NETWORK_PROBE_HTTPS, NETWORK_PROBE_FALLBACK, NETWORK_PROBE_PRIVATE_DNS }) @Retention(RetentionPolicy.SOURCE) public @interface NetworkProbe {} /** * A bitmask of network validation probes that succeeded. * *
The possible bits values reported by this key are: * {@link #NETWORK_PROBE_DNS}, * {@link #NETWORK_PROBE_HTTP}, * {@link #NETWORK_PROBE_HTTPS}, * {@link #NETWORK_PROBE_FALLBACK}, * {@link #NETWORK_PROBE_PRIVATE_DNS}. */ @NetworkProbe public static final String KEY_NETWORK_PROBES_SUCCEEDED_BITMASK = "networkProbesSucceeded"; /** * A bitmask of network validation probes that were attempted. * *
These probes may have failed or may be incomplete at the time of this report. * *
The possible bits values reported by this key are: * {@link #NETWORK_PROBE_DNS}, * {@link #NETWORK_PROBE_HTTP}, * {@link #NETWORK_PROBE_HTTPS}, * {@link #NETWORK_PROBE_FALLBACK}, * {@link #NETWORK_PROBE_PRIVATE_DNS}. */ @NetworkProbe public static final String KEY_NETWORK_PROBES_ATTEMPTED_BITMASK = "networkProbesAttempted"; /** @hide */ @StringDef(prefix = {"KEY_"}, value = { KEY_NETWORK_VALIDATION_RESULT, KEY_NETWORK_PROBES_SUCCEEDED_BITMASK, KEY_NETWORK_PROBES_ATTEMPTED_BITMASK}) @Retention(RetentionPolicy.SOURCE) public @interface ConnectivityReportBundleKeys {} /** The Network for which this ConnectivityReport applied */ @NonNull private final Network mNetwork; /** * The timestamp for the report. The timestamp is taken from {@link * System#currentTimeMillis}. */ private final long mReportTimestamp; /** LinkProperties available on the Network at the reported timestamp */ @NonNull private final LinkProperties mLinkProperties; /** NetworkCapabilities available on the Network at the reported timestamp */ @NonNull private final NetworkCapabilities mNetworkCapabilities; /** PersistableBundle that may contain additional info about the report */ @NonNull private final PersistableBundle mAdditionalInfo; /** * Constructor for ConnectivityReport. * *
Apps should obtain instances through {@link
* ConnectivityDiagnosticsCallback#onConnectivityReportAvailable} instead of instantiating
* their own instances (unless for testing purposes).
*
* @param network The Network for which this ConnectivityReport applies
* @param reportTimestamp The timestamp for the report
* @param linkProperties The LinkProperties available on network at reportTimestamp
* @param networkCapabilities The NetworkCapabilities available on network at
* reportTimestamp
* @param additionalInfo A PersistableBundle that may contain additional info about the
* report
*/
public ConnectivityReport(
@NonNull Network network,
long reportTimestamp,
@NonNull LinkProperties linkProperties,
@NonNull NetworkCapabilities networkCapabilities,
@NonNull PersistableBundle additionalInfo) {
mNetwork = network;
mReportTimestamp = reportTimestamp;
mLinkProperties = new LinkProperties(linkProperties);
mNetworkCapabilities = new NetworkCapabilities(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 This key will be included if the data stall detection method is
* {@link #DETECTION_METHOD_TCP_METRICS}.
*
* This value is an int.
*/
public static final String KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS =
"tcpMetricsCollectionPeriodMillis";
/**
* This key represents the fail rate of TCP packets when the suspected data stall was
* detected.
*
* This key will be included if the data stall detection method is
* {@link #DETECTION_METHOD_TCP_METRICS}.
*
* This value is an int percentage between 0 and 100.
*/
public static final String KEY_TCP_PACKET_FAIL_RATE = "tcpPacketFailRate";
/**
* This key represents the consecutive number of DNS timeouts that have occurred.
*
* The consecutive count will be reset any time a DNS response is received.
*
* This key will be included if the data stall detection method is
* {@link #DETECTION_METHOD_DNS_EVENTS}.
*
* This value is an int.
*/
public static final String KEY_DNS_CONSECUTIVE_TIMEOUTS = "dnsConsecutiveTimeouts";
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@StringDef(prefix = {"KEY_"}, value = {
KEY_TCP_PACKET_FAIL_RATE,
KEY_DNS_CONSECUTIVE_TIMEOUTS
})
public @interface DataStallReportBundleKeys {}
/** The Network for which this DataStallReport applied */
@NonNull private final Network mNetwork;
/**
* The timestamp for the report. The timestamp is taken from {@link
* System#currentTimeMillis}.
*/
private long mReportTimestamp;
/** A bitmask of the detection methods used to identify the suspected data stall */
@DetectionMethod private final int mDetectionMethod;
/** LinkProperties available on the Network at the reported timestamp */
@NonNull private final LinkProperties mLinkProperties;
/** NetworkCapabilities available on the Network at the reported timestamp */
@NonNull private final NetworkCapabilities mNetworkCapabilities;
/** PersistableBundle that may contain additional information on the suspected data stall */
@NonNull private final PersistableBundle mStallDetails;
/**
* Constructor for DataStallReport.
*
* Apps should obtain instances through {@link
* ConnectivityDiagnosticsCallback#onDataStallSuspected} instead of instantiating their own
* instances (unless for testing purposes).
*
* @param network The Network for which this DataStallReport applies
* @param reportTimestamp The timestamp for the report
* @param detectionMethod The detection method used to identify this data stall
* @param linkProperties The LinkProperties available on network at reportTimestamp
* @param networkCapabilities The NetworkCapabilities available on network at
* reportTimestamp
* @param stallDetails A PersistableBundle that may contain additional info about the report
*/
public DataStallReport(
@NonNull Network network,
long reportTimestamp,
@DetectionMethod int detectionMethod,
@NonNull LinkProperties linkProperties,
@NonNull NetworkCapabilities networkCapabilities,
@NonNull PersistableBundle stallDetails) {
mNetwork = network;
mReportTimestamp = reportTimestamp;
mDetectionMethod = detectionMethod;
mLinkProperties = new LinkProperties(linkProperties);
mNetworkCapabilities = new NetworkCapabilities(networkCapabilities);
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 bitmask of detection methods used to identify this suspected data stall.
*
* @return The bitmask of detection methods used to identify the suspected data stall
*/
public int getDetectionMethod() {
return mDetectionMethod;
}
/**
* 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.
*
* Gets a bundle with details about the suspected data stall including information
* specific to the monitoring method that detected the data stall.
*
* @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)
&& mLinkProperties.equals(that.mLinkProperties)
&& mNetworkCapabilities.equals(that.mNetworkCapabilities)
&& persistableBundleEquals(mStallDetails, that.mStallDetails);
}
@Override
public int hashCode() {
return Objects.hash(
mNetwork,
mReportTimestamp,
mDetectionMethod,
mLinkProperties,
mNetworkCapabilities,
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(mLinkProperties, flags);
dest.writeParcelable(mNetworkCapabilities, flags);
dest.writeParcelable(mStallDetails, flags);
}
/** Implement the Parcelable interface */
public static final @NonNull Creator The Network specified in the ConnectivityReport may not be active any more when this
* method is invoked.
*
* @param report The ConnectivityReport containing information about a connectivity check
*/
public void onConnectivityReportAvailable(@NonNull ConnectivityReport report) {}
/**
* Called when the platform suspects a data stall on some Network.
*
* The Network specified in the DataStallReport may not be active any more when this
* method is invoked.
*
* @param report The DataStallReport containing information about the suspected data stall
*/
public void onDataStallSuspected(@NonNull DataStallReport report) {}
/**
* Called when any app reports connectivity to the System.
*
* @param network The Network for which connectivity has been reported
* @param hasConnectivity The connectivity reported to the System
*/
public void onNetworkConnectivityReported(
@NonNull Network network, boolean hasConnectivity) {}
}
/**
* Registers a ConnectivityDiagnosticsCallback with the System.
*
* Only apps that offer network connectivity to the user should be registering callbacks.
* These are the only apps whose callbacks will be invoked by the system. Apps considered to
* meet these conditions include:
*
* Callbacks registered by apps not meeting the above criteria will not be invoked.
*
* If a registering app loses its relevant permissions, any callbacks it registered will
* silently stop receiving callbacks.
*
* Each register() call MUST use a ConnectivityDiagnosticsCallback instance that is
* not currently registered. If a ConnectivityDiagnosticsCallback instance is registered with
* multiple NetworkRequests, an IllegalArgumentException will be thrown.
*
* To avoid performance issues due to apps leaking callbacks, the system will limit the
* number of outstanding requests to 100 per app (identified by their UID), shared with
* callbacks in {@link ConnectivityManager}. Registering a callback with this method will count
* toward this limit. If this limit is exceeded, an exception will be thrown. To avoid hitting
* this issue and to conserve resources, make sure to unregister the callbacks with
* {@link #unregisterConnectivityDiagnosticsCallback}.
*
* @param request The NetworkRequest that will be used to match with Networks for which
* callbacks will be fired
* @param e The Executor to be used for running the callback method invocations
* @param callback The ConnectivityDiagnosticsCallback that the caller wants registered with the
* System
* @throws IllegalArgumentException if the same callback instance is registered with multiple
* NetworkRequests
* @throws RuntimeException if the app already has too many callbacks registered.
*/
public void registerConnectivityDiagnosticsCallback(
@NonNull NetworkRequest request,
@NonNull Executor e,
@NonNull ConnectivityDiagnosticsCallback callback) {
final ConnectivityDiagnosticsBinder binder = new ConnectivityDiagnosticsBinder(callback, e);
if (sCallbacks.putIfAbsent(callback, binder) != null) {
throw new IllegalArgumentException("Callback is currently registered");
}
try {
mService.registerConnectivityDiagnosticsCallback(
binder, request, mContext.getOpPackageName());
} catch (RemoteException exception) {
exception.rethrowFromSystemServer();
}
}
/**
* Unregisters a ConnectivityDiagnosticsCallback with the System.
*
* If the given callback is not currently registered with the System, this operation will be
* a no-op.
*
* @param callback The ConnectivityDiagnosticsCallback to be unregistered from the System.
*/
public void unregisterConnectivityDiagnosticsCallback(
@NonNull ConnectivityDiagnosticsCallback callback) {
// unconditionally removing from sCallbacks prevents race conditions here, since remove() is
// atomic.
final ConnectivityDiagnosticsBinder binder = sCallbacks.remove(callback);
if (binder == null) return;
try {
mService.unregisterConnectivityDiagnosticsCallback(binder);
} catch (RemoteException exception) {
exception.rethrowFromSystemServer();
}
}
}
*
*
*