Merge "Add API for CaptivePortalData" am: 1c42b174ed am: 12ad1433c1 am: f807f17797
Change-Id: I951f2eb2a0e2550f65ac87a84b76ea68c2449423
This commit is contained in:
281
core/java/android/net/CaptivePortalData.java
Normal file
281
core/java/android/net/CaptivePortalData.java
Normal file
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* 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.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.TestApi;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Metadata sent by captive portals, see https://www.ietf.org/id/draft-ietf-capport-api-03.txt.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@TestApi
|
||||
public final class CaptivePortalData implements Parcelable {
|
||||
private final long mRefreshTimeMillis;
|
||||
@Nullable
|
||||
private final Uri mUserPortalUrl;
|
||||
@Nullable
|
||||
private final Uri mVenueInfoUrl;
|
||||
private final boolean mIsSessionExtendable;
|
||||
private final long mByteLimit;
|
||||
private final long mExpiryTimeMillis;
|
||||
private final boolean mCaptive;
|
||||
|
||||
private CaptivePortalData(long refreshTimeMillis, Uri userPortalUrl, Uri venueInfoUrl,
|
||||
boolean isSessionExtendable, long byteLimit, long expiryTimeMillis, boolean captive) {
|
||||
mRefreshTimeMillis = refreshTimeMillis;
|
||||
mUserPortalUrl = userPortalUrl;
|
||||
mVenueInfoUrl = venueInfoUrl;
|
||||
mIsSessionExtendable = isSessionExtendable;
|
||||
mByteLimit = byteLimit;
|
||||
mExpiryTimeMillis = expiryTimeMillis;
|
||||
mCaptive = captive;
|
||||
}
|
||||
|
||||
private CaptivePortalData(Parcel p) {
|
||||
this(p.readLong(), p.readParcelable(null), p.readParcelable(null), p.readBoolean(),
|
||||
p.readLong(), p.readLong(), p.readBoolean());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeLong(mRefreshTimeMillis);
|
||||
dest.writeParcelable(mUserPortalUrl, 0);
|
||||
dest.writeParcelable(mVenueInfoUrl, 0);
|
||||
dest.writeBoolean(mIsSessionExtendable);
|
||||
dest.writeLong(mByteLimit);
|
||||
dest.writeLong(mExpiryTimeMillis);
|
||||
dest.writeBoolean(mCaptive);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder to create new {@link CaptivePortalData}.
|
||||
*/
|
||||
public static class Builder {
|
||||
private long mRefreshTime;
|
||||
private Uri mUserPortalUrl;
|
||||
private Uri mVenueInfoUrl;
|
||||
private boolean mIsSessionExtendable;
|
||||
private long mBytesRemaining = -1;
|
||||
private long mExpiryTime = -1;
|
||||
private boolean mCaptive;
|
||||
|
||||
/**
|
||||
* Create an empty builder.
|
||||
*/
|
||||
public Builder() {}
|
||||
|
||||
/**
|
||||
* Create a builder copying all data from existing {@link CaptivePortalData}.
|
||||
*/
|
||||
public Builder(@Nullable CaptivePortalData data) {
|
||||
if (data == null) return;
|
||||
setRefreshTime(data.mRefreshTimeMillis)
|
||||
.setUserPortalUrl(data.mUserPortalUrl)
|
||||
.setVenueInfoUrl(data.mVenueInfoUrl)
|
||||
.setSessionExtendable(data.mIsSessionExtendable)
|
||||
.setBytesRemaining(data.mByteLimit)
|
||||
.setExpiryTime(data.mExpiryTimeMillis)
|
||||
.setCaptive(data.mCaptive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time at which data was last refreshed, as per {@link System#currentTimeMillis()}.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setRefreshTime(long refreshTime) {
|
||||
mRefreshTime = refreshTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the URL to be used for users to login to the portal, if captive.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setUserPortalUrl(@Nullable Uri userPortalUrl) {
|
||||
mUserPortalUrl = userPortalUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the URL that can be used by users to view information about the network venue.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setVenueInfoUrl(@Nullable Uri venueInfoUrl) {
|
||||
mVenueInfoUrl = venueInfoUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the portal supports extending a user session on the portal URL page.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setSessionExtendable(boolean sessionExtendable) {
|
||||
mIsSessionExtendable = sessionExtendable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of bytes remaining on the network before the portal closes.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setBytesRemaining(long bytesRemaining) {
|
||||
mBytesRemaining = bytesRemaining;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time at the session will expire, as per {@link System#currentTimeMillis()}.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setExpiryTime(long expiryTime) {
|
||||
mExpiryTime = expiryTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the network is captive (portal closed).
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setCaptive(boolean captive) {
|
||||
mCaptive = captive;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link CaptivePortalData}.
|
||||
*/
|
||||
@NonNull
|
||||
public CaptivePortalData build() {
|
||||
return new CaptivePortalData(mRefreshTime, mUserPortalUrl, mVenueInfoUrl,
|
||||
mIsSessionExtendable, mBytesRemaining, mExpiryTime, mCaptive);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time at which data was last refreshed, as per {@link System#currentTimeMillis()}.
|
||||
*/
|
||||
public long getRefreshTimeMillis() {
|
||||
return mRefreshTimeMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL to be used for users to login to the portal, or extend their session if
|
||||
* {@link #isSessionExtendable()} is true.
|
||||
*/
|
||||
@Nullable
|
||||
public Uri getUserPortalUrl() {
|
||||
return mUserPortalUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL that can be used by users to view information about the network venue.
|
||||
*/
|
||||
@Nullable
|
||||
public Uri getVenueInfoUrl() {
|
||||
return mVenueInfoUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the user portal URL can be used to extend sessions, when the user is logged
|
||||
* in and the session has a time or byte limit.
|
||||
*/
|
||||
public boolean isSessionExtendable() {
|
||||
return mIsSessionExtendable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the remaining bytes on the captive portal session, at the time {@link CaptivePortalData}
|
||||
* was refreshed. This may be different from the limit currently enforced by the portal.
|
||||
* @return The byte limit, or -1 if not set.
|
||||
*/
|
||||
public long getByteLimit() {
|
||||
return mByteLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time at the session will expire, as per {@link System#currentTimeMillis()}.
|
||||
* @return The expiry time, or -1 if unset.
|
||||
*/
|
||||
public long getExpiryTimeMillis() {
|
||||
return mExpiryTimeMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether the network is captive (portal closed).
|
||||
*/
|
||||
public boolean isCaptive() {
|
||||
return mCaptive;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static final Creator<CaptivePortalData> CREATOR = new Creator<CaptivePortalData>() {
|
||||
@Override
|
||||
public CaptivePortalData createFromParcel(Parcel source) {
|
||||
return new CaptivePortalData(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CaptivePortalData[] newArray(int size) {
|
||||
return new CaptivePortalData[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mRefreshTimeMillis, mUserPortalUrl, mVenueInfoUrl,
|
||||
mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof CaptivePortalData)) return false;
|
||||
final CaptivePortalData other = (CaptivePortalData) obj;
|
||||
return mRefreshTimeMillis == other.mRefreshTimeMillis
|
||||
&& Objects.equals(mUserPortalUrl, other.mUserPortalUrl)
|
||||
&& Objects.equals(mVenueInfoUrl, other.mVenueInfoUrl)
|
||||
&& mIsSessionExtendable == other.mIsSessionExtendable
|
||||
&& mByteLimit == other.mByteLimit
|
||||
&& mExpiryTimeMillis == other.mExpiryTimeMillis
|
||||
&& mCaptive == other.mCaptive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CaptivePortalData {"
|
||||
+ "refreshTime: " + mRefreshTimeMillis
|
||||
+ ", userPortalUrl: " + mUserPortalUrl
|
||||
+ ", venueInfoUrl: " + mVenueInfoUrl
|
||||
+ ", isSessionExtendable: " + mIsSessionExtendable
|
||||
+ ", byteLimit: " + mByteLimit
|
||||
+ ", expiryTime: " + mExpiryTimeMillis
|
||||
+ ", captive: " + mCaptive
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
@@ -72,6 +72,14 @@ public final class LinkProperties implements Parcelable {
|
||||
private String mTcpBufferSizes;
|
||||
private IpPrefix mNat64Prefix;
|
||||
private boolean mWakeOnLanSupported;
|
||||
private Uri mCaptivePortalApiUrl;
|
||||
private CaptivePortalData mCaptivePortalData;
|
||||
|
||||
/**
|
||||
* Indicates whether parceling should preserve fields that are set based on permissions of
|
||||
* the process receiving the {@link LinkProperties}.
|
||||
*/
|
||||
private final transient boolean mParcelSensitiveFields;
|
||||
|
||||
private static final int MIN_MTU = 68;
|
||||
private static final int MIN_MTU_V6 = 1280;
|
||||
@@ -146,6 +154,7 @@ public final class LinkProperties implements Parcelable {
|
||||
* Constructs a new {@code LinkProperties} with default values.
|
||||
*/
|
||||
public LinkProperties() {
|
||||
mParcelSensitiveFields = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,7 +163,12 @@ public final class LinkProperties implements Parcelable {
|
||||
@SystemApi
|
||||
@TestApi
|
||||
public LinkProperties(@Nullable LinkProperties source) {
|
||||
if (source != null) {
|
||||
this(source, false /* parcelSensitiveFields */);
|
||||
}
|
||||
|
||||
private LinkProperties(@Nullable LinkProperties source, boolean parcelSensitiveFields) {
|
||||
mParcelSensitiveFields = parcelSensitiveFields;
|
||||
if (source == null) return;
|
||||
mIfaceName = source.mIfaceName;
|
||||
mLinkAddresses.addAll(source.mLinkAddresses);
|
||||
mDnses.addAll(source.mDnses);
|
||||
@@ -173,7 +187,8 @@ public final class LinkProperties implements Parcelable {
|
||||
mTcpBufferSizes = source.mTcpBufferSizes;
|
||||
mNat64Prefix = source.mNat64Prefix;
|
||||
mWakeOnLanSupported = source.mWakeOnLanSupported;
|
||||
}
|
||||
mCaptivePortalApiUrl = source.mCaptivePortalApiUrl;
|
||||
mCaptivePortalData = source.mCaptivePortalData;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -832,6 +847,11 @@ public final class LinkProperties implements Parcelable {
|
||||
* Clears this object to its initial state.
|
||||
*/
|
||||
public void clear() {
|
||||
if (mParcelSensitiveFields) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Cannot clear LinkProperties when parcelSensitiveFields is set");
|
||||
}
|
||||
|
||||
mIfaceName = null;
|
||||
mLinkAddresses.clear();
|
||||
mDnses.clear();
|
||||
@@ -847,6 +867,8 @@ public final class LinkProperties implements Parcelable {
|
||||
mTcpBufferSizes = null;
|
||||
mNat64Prefix = null;
|
||||
mWakeOnLanSupported = false;
|
||||
mCaptivePortalApiUrl = null;
|
||||
mCaptivePortalData = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -917,6 +939,14 @@ public final class LinkProperties implements Parcelable {
|
||||
resultJoiner.add(mDhcpServerAddress.toString());
|
||||
}
|
||||
|
||||
if (mCaptivePortalApiUrl != null) {
|
||||
resultJoiner.add("CaptivePortalApiUrl: " + mCaptivePortalApiUrl);
|
||||
}
|
||||
|
||||
if (mCaptivePortalData != null) {
|
||||
resultJoiner.add("CaptivePortalData: " + mCaptivePortalData);
|
||||
}
|
||||
|
||||
if (mTcpBufferSizes != null) {
|
||||
resultJoiner.add("TcpBufferSizes:");
|
||||
resultJoiner.add(mTcpBufferSizes);
|
||||
@@ -1436,6 +1466,28 @@ public final class LinkProperties implements Parcelable {
|
||||
return isWakeOnLanSupported() == target.isWakeOnLanSupported();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this {@code LinkProperties}'s CaptivePortalApiUrl against the target.
|
||||
*
|
||||
* @param target LinkProperties to compare.
|
||||
* @return {@code true} if both are identical, {@code false} otherwise.
|
||||
* @hide
|
||||
*/
|
||||
public boolean isIdenticalCaptivePortalApiUrl(LinkProperties target) {
|
||||
return Objects.equals(mCaptivePortalApiUrl, target.mCaptivePortalApiUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this {@code LinkProperties}'s CaptivePortalData against the target.
|
||||
*
|
||||
* @param target LinkProperties to compare.
|
||||
* @return {@code true} if both are identical, {@code false} otherwise.
|
||||
* @hide
|
||||
*/
|
||||
public boolean isIdenticalCaptivePortalData(LinkProperties target) {
|
||||
return Objects.equals(mCaptivePortalData, target.mCaptivePortalData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the network interface supports WakeOnLAN
|
||||
*
|
||||
@@ -1456,6 +1508,73 @@ public final class LinkProperties implements Parcelable {
|
||||
return mWakeOnLanSupported;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the URL of the captive portal API endpoint to get more information about the network.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@TestApi
|
||||
public void setCaptivePortalApiUrl(@Nullable Uri url) {
|
||||
mCaptivePortalApiUrl = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL of the captive portal API endpoint to get more information about the network.
|
||||
*
|
||||
* <p>This is null unless the application has
|
||||
* {@link android.Manifest.permission.NETWORK_SETTINGS} or
|
||||
* {@link NetworkStack#PERMISSION_MAINLINE_NETWORK_STACK} permissions, and the network provided
|
||||
* the URL.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@TestApi
|
||||
@Nullable
|
||||
public Uri getCaptivePortalApiUrl() {
|
||||
return mCaptivePortalApiUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the CaptivePortalData obtained from the captive portal API (RFC7710bis).
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@TestApi
|
||||
public void setCaptivePortalData(@Nullable CaptivePortalData data) {
|
||||
mCaptivePortalData = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CaptivePortalData obtained from the captive portal API (RFC7710bis).
|
||||
*
|
||||
* <p>This is null unless the application has
|
||||
* {@link android.Manifest.permission.NETWORK_SETTINGS} or
|
||||
* {@link NetworkStack#PERMISSION_MAINLINE_NETWORK_STACK} permissions.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@TestApi
|
||||
@Nullable
|
||||
public CaptivePortalData getCaptivePortalData() {
|
||||
return mCaptivePortalData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a copy of this {@link LinkProperties} that will preserve fields that were set
|
||||
* based on the permissions of the process that received this {@link LinkProperties}.
|
||||
*
|
||||
* <p>By default {@link LinkProperties} does not preserve such fields during parceling, as
|
||||
* they should not be shared outside of the process that receives them without appropriate
|
||||
* checks.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@TestApi
|
||||
@NonNull
|
||||
public LinkProperties makeSensitiveFieldsParcelingCopy() {
|
||||
return new LinkProperties(this, true /* parcelSensitiveFields */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this {@code LinkProperties} instance against the target
|
||||
* LinkProperties in {@code obj}. Two LinkPropertieses are equal if
|
||||
@@ -1495,7 +1614,9 @@ public final class LinkProperties implements Parcelable {
|
||||
&& isIdenticalMtu(target)
|
||||
&& isIdenticalTcpBufferSizes(target)
|
||||
&& isIdenticalNat64Prefix(target)
|
||||
&& isIdenticalWakeOnLan(target);
|
||||
&& isIdenticalWakeOnLan(target)
|
||||
&& isIdenticalCaptivePortalApiUrl(target)
|
||||
&& isIdenticalCaptivePortalData(target);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1593,7 +1714,8 @@ public final class LinkProperties implements Parcelable {
|
||||
+ mPcscfs.size() * 67
|
||||
+ ((null == mPrivateDnsServerName) ? 0 : mPrivateDnsServerName.hashCode())
|
||||
+ Objects.hash(mNat64Prefix)
|
||||
+ (mWakeOnLanSupported ? 71 : 0);
|
||||
+ (mWakeOnLanSupported ? 71 : 0)
|
||||
+ Objects.hash(mCaptivePortalApiUrl, mCaptivePortalData);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1632,6 +1754,8 @@ public final class LinkProperties implements Parcelable {
|
||||
dest.writeList(stackedLinks);
|
||||
|
||||
dest.writeBoolean(mWakeOnLanSupported);
|
||||
dest.writeParcelable(mParcelSensitiveFields ? mCaptivePortalApiUrl : null, 0);
|
||||
dest.writeParcelable(mParcelSensitiveFields ? mCaptivePortalData : null, 0);
|
||||
}
|
||||
|
||||
private static void writeAddresses(@NonNull Parcel dest, @NonNull List<InetAddress> list) {
|
||||
@@ -1723,6 +1847,9 @@ public final class LinkProperties implements Parcelable {
|
||||
netProp.addStackedLink(stackedLink);
|
||||
}
|
||||
netProp.setWakeOnLanSupported(in.readBoolean());
|
||||
|
||||
netProp.setCaptivePortalApiUrl(in.readParcelable(null));
|
||||
netProp.setCaptivePortalData(in.readParcelable(null));
|
||||
return netProp;
|
||||
}
|
||||
|
||||
|
||||
@@ -1571,49 +1571,50 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
enforceAccessPermission();
|
||||
final int uid = Binder.getCallingUid();
|
||||
NetworkState state = getUnfilteredActiveNetworkState(uid);
|
||||
return state.linkProperties;
|
||||
if (state.linkProperties == null) return null;
|
||||
return linkPropertiesRestrictedForCallerPermissions(state.linkProperties,
|
||||
Binder.getCallingPid(), uid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LinkProperties getLinkPropertiesForType(int networkType) {
|
||||
enforceAccessPermission();
|
||||
NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
|
||||
if (nai != null) {
|
||||
synchronized (nai) {
|
||||
return new LinkProperties(nai.linkProperties);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
final LinkProperties lp = getLinkProperties(nai);
|
||||
if (lp == null) return null;
|
||||
return linkPropertiesRestrictedForCallerPermissions(
|
||||
lp, Binder.getCallingPid(), Binder.getCallingUid());
|
||||
}
|
||||
|
||||
// TODO - this should be ALL networks
|
||||
@Override
|
||||
public LinkProperties getLinkProperties(Network network) {
|
||||
enforceAccessPermission();
|
||||
return getLinkProperties(getNetworkAgentInfoForNetwork(network));
|
||||
final LinkProperties lp = getLinkProperties(getNetworkAgentInfoForNetwork(network));
|
||||
if (lp == null) return null;
|
||||
return linkPropertiesRestrictedForCallerPermissions(
|
||||
lp, Binder.getCallingPid(), Binder.getCallingUid());
|
||||
}
|
||||
|
||||
private LinkProperties getLinkProperties(NetworkAgentInfo nai) {
|
||||
@Nullable
|
||||
private LinkProperties getLinkProperties(@Nullable NetworkAgentInfo nai) {
|
||||
if (nai == null) {
|
||||
return null;
|
||||
}
|
||||
synchronized (nai) {
|
||||
return new LinkProperties(nai.linkProperties);
|
||||
return nai.linkProperties;
|
||||
}
|
||||
}
|
||||
|
||||
private NetworkCapabilities getNetworkCapabilitiesInternal(NetworkAgentInfo nai) {
|
||||
if (nai != null) {
|
||||
if (nai == null) return null;
|
||||
synchronized (nai) {
|
||||
if (nai.networkCapabilities != null) {
|
||||
if (nai.networkCapabilities == null) return null;
|
||||
return networkCapabilitiesRestrictedForCallerPermissions(
|
||||
nai.networkCapabilities,
|
||||
Binder.getCallingPid(), Binder.getCallingUid());
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NetworkCapabilities getNetworkCapabilities(Network network) {
|
||||
@@ -1634,6 +1635,29 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
return newNc;
|
||||
}
|
||||
|
||||
private LinkProperties linkPropertiesRestrictedForCallerPermissions(
|
||||
LinkProperties lp, int callerPid, int callerUid) {
|
||||
if (lp == null) return new LinkProperties();
|
||||
|
||||
// Only do a permission check if sanitization is needed, to avoid unnecessary binder calls.
|
||||
final boolean needsSanitization =
|
||||
(lp.getCaptivePortalApiUrl() != null || lp.getCaptivePortalData() != null);
|
||||
if (!needsSanitization) {
|
||||
return new LinkProperties(lp);
|
||||
}
|
||||
|
||||
if (checkSettingsPermission(callerPid, callerUid)) {
|
||||
return lp.makeSensitiveFieldsParcelingCopy();
|
||||
}
|
||||
|
||||
final LinkProperties newLp = new LinkProperties(lp);
|
||||
// Sensitive fields would not be parceled anyway, but sanitize for consistency before the
|
||||
// object gets parceled.
|
||||
newLp.setCaptivePortalApiUrl(null);
|
||||
newLp.setCaptivePortalData(null);
|
||||
return newLp;
|
||||
}
|
||||
|
||||
private void restrictRequestUidsForCaller(NetworkCapabilities nc) {
|
||||
if (!checkSettingsPermission()) {
|
||||
nc.setSingleUid(Binder.getCallingUid());
|
||||
@@ -6145,7 +6169,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
case ConnectivityManager.CALLBACK_AVAILABLE: {
|
||||
putParcelable(bundle, networkCapabilitiesRestrictedForCallerPermissions(
|
||||
networkAgent.networkCapabilities, nri.mPid, nri.mUid));
|
||||
putParcelable(bundle, new LinkProperties(networkAgent.linkProperties));
|
||||
putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions(
|
||||
networkAgent.linkProperties, nri.mPid, nri.mUid));
|
||||
// For this notification, arg1 contains the blocked status.
|
||||
msg.arg1 = arg1;
|
||||
break;
|
||||
@@ -6162,7 +6187,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
break;
|
||||
}
|
||||
case ConnectivityManager.CALLBACK_IP_CHANGED: {
|
||||
putParcelable(bundle, new LinkProperties(networkAgent.linkProperties));
|
||||
putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions(
|
||||
networkAgent.linkProperties, nri.mPid, nri.mUid));
|
||||
break;
|
||||
}
|
||||
case ConnectivityManager.CALLBACK_BLK_CHANGED: {
|
||||
|
||||
@@ -75,6 +75,9 @@ public class LinkPropertiesTest {
|
||||
private static final LinkAddress LINKADDRV4 = new LinkAddress(ADDRV4, 32);
|
||||
private static final LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128);
|
||||
private static final LinkAddress LINKADDRV6LINKLOCAL = new LinkAddress("fe80::1/64");
|
||||
private static final Uri CAPPORT_API_URL = Uri.parse("https://test.example.com/capportapi");
|
||||
private static final CaptivePortalData CAPPORT_DATA = new CaptivePortalData.Builder()
|
||||
.setVenueInfoUrl(Uri.parse("https://test.example.com/venue")).build();
|
||||
|
||||
private static InetAddress address(String addrString) {
|
||||
return InetAddresses.parseNumericAddress(addrString);
|
||||
@@ -101,6 +104,8 @@ public class LinkPropertiesTest {
|
||||
assertFalse(lp.isIpv6Provisioned());
|
||||
assertFalse(lp.isPrivateDnsActive());
|
||||
assertFalse(lp.isWakeOnLanSupported());
|
||||
assertNull(lp.getCaptivePortalApiUrl());
|
||||
assertNull(lp.getCaptivePortalData());
|
||||
}
|
||||
|
||||
private LinkProperties makeTestObject() {
|
||||
@@ -124,6 +129,8 @@ public class LinkPropertiesTest {
|
||||
lp.setNat64Prefix(new IpPrefix("2001:db8:0:64::/96"));
|
||||
lp.setDhcpServerAddress(DHCPSERVER);
|
||||
lp.setWakeOnLanSupported(true);
|
||||
lp.setCaptivePortalApiUrl(CAPPORT_API_URL);
|
||||
lp.setCaptivePortalData(CAPPORT_DATA);
|
||||
return lp;
|
||||
}
|
||||
|
||||
@@ -165,6 +172,12 @@ public class LinkPropertiesTest {
|
||||
assertTrue(source.isIdenticalWakeOnLan(target));
|
||||
assertTrue(target.isIdenticalWakeOnLan(source));
|
||||
|
||||
assertTrue(source.isIdenticalCaptivePortalApiUrl(target));
|
||||
assertTrue(target.isIdenticalCaptivePortalApiUrl(source));
|
||||
|
||||
assertTrue(source.isIdenticalCaptivePortalData(target));
|
||||
assertTrue(target.isIdenticalCaptivePortalData(source));
|
||||
|
||||
// Check result of equals().
|
||||
assertTrue(source.equals(target));
|
||||
assertTrue(target.equals(source));
|
||||
@@ -963,6 +976,8 @@ public class LinkPropertiesTest {
|
||||
source.setNat64Prefix(new IpPrefix("2001:db8:1:2:64:64::/96"));
|
||||
|
||||
source.setWakeOnLanSupported(true);
|
||||
source.setCaptivePortalApiUrl(CAPPORT_API_URL);
|
||||
source.setCaptivePortalData(CAPPORT_DATA);
|
||||
|
||||
source.setDhcpServerAddress((Inet4Address) GATEWAY1);
|
||||
|
||||
@@ -970,7 +985,13 @@ public class LinkPropertiesTest {
|
||||
stacked.setInterfaceName("test-stacked");
|
||||
source.addStackedLink(stacked);
|
||||
|
||||
assertParcelSane(source, 16 /* fieldCount */);
|
||||
assertParcelSane(source.makeSensitiveFieldsParcelingCopy(), 18 /* fieldCount */);
|
||||
|
||||
// Verify that without using a sensitiveFieldsParcelingCopy, sensitive fields are cleared.
|
||||
final LinkProperties sanitized = new LinkProperties(source);
|
||||
sanitized.setCaptivePortalApiUrl(null);
|
||||
sanitized.setCaptivePortalData(null);
|
||||
assertEquals(sanitized, parcelingRoundTrip(source));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1113,4 +1134,22 @@ public class LinkPropertiesTest {
|
||||
lp.clear();
|
||||
assertFalse(lp.isWakeOnLanSupported());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCaptivePortalApiUrl() {
|
||||
final LinkProperties lp = makeTestObject();
|
||||
assertEquals(CAPPORT_API_URL, lp.getCaptivePortalApiUrl());
|
||||
|
||||
lp.clear();
|
||||
assertNull(lp.getCaptivePortalApiUrl());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCaptivePortalData() {
|
||||
final LinkProperties lp = makeTestObject();
|
||||
assertEquals(CAPPORT_DATA, lp.getCaptivePortalData());
|
||||
|
||||
lp.clear();
|
||||
assertNull(lp.getCaptivePortalData());
|
||||
}
|
||||
}
|
||||
|
||||
72
tests/net/java/android/net/CaptivePortalDataTest.kt
Normal file
72
tests/net/java/android/net/CaptivePortalDataTest.kt
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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 androidx.test.filters.SmallTest
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import com.android.testutils.assertParcelSane
|
||||
import com.android.testutils.assertParcelingIsLossless
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotEquals
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class CaptivePortalDataTest {
|
||||
private val data = CaptivePortalData.Builder()
|
||||
.setRefreshTime(123L)
|
||||
.setUserPortalUrl(Uri.parse("https://portal.example.com/test"))
|
||||
.setVenueInfoUrl(Uri.parse("https://venue.example.com/test"))
|
||||
.setSessionExtendable(true)
|
||||
.setBytesRemaining(456L)
|
||||
.setExpiryTime(789L)
|
||||
.setCaptive(true)
|
||||
.build()
|
||||
|
||||
private fun makeBuilder() = CaptivePortalData.Builder(data)
|
||||
|
||||
@Test
|
||||
fun testParcelUnparcel() {
|
||||
assertParcelSane(data, fieldCount = 7)
|
||||
|
||||
assertParcelingIsLossless(makeBuilder().setUserPortalUrl(null).build())
|
||||
assertParcelingIsLossless(makeBuilder().setVenueInfoUrl(null).build())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEquals() {
|
||||
assertEquals(data, makeBuilder().build())
|
||||
|
||||
assertNotEqualsAfterChange { it.setRefreshTime(456L) }
|
||||
assertNotEqualsAfterChange { it.setUserPortalUrl(Uri.parse("https://example.com/")) }
|
||||
assertNotEqualsAfterChange { it.setUserPortalUrl(null) }
|
||||
assertNotEqualsAfterChange { it.setVenueInfoUrl(Uri.parse("https://example.com/")) }
|
||||
assertNotEqualsAfterChange { it.setVenueInfoUrl(null) }
|
||||
assertNotEqualsAfterChange { it.setSessionExtendable(false) }
|
||||
assertNotEqualsAfterChange { it.setBytesRemaining(789L) }
|
||||
assertNotEqualsAfterChange { it.setExpiryTime(12L) }
|
||||
assertNotEqualsAfterChange { it.setCaptive(false) }
|
||||
}
|
||||
|
||||
private fun CaptivePortalData.mutate(mutator: (CaptivePortalData.Builder) -> Unit) =
|
||||
CaptivePortalData.Builder(this).apply { mutator(this) }.build()
|
||||
|
||||
private fun assertNotEqualsAfterChange(mutator: (CaptivePortalData.Builder) -> Unit) {
|
||||
assertNotEquals(data, data.mutate(mutator))
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,8 @@ import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
|
||||
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
|
||||
import static android.content.pm.PackageManager.GET_PERMISSIONS;
|
||||
import static android.content.pm.PackageManager.MATCH_ANY_USER;
|
||||
import static android.content.pm.PackageManager.PERMISSION_DENIED;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN;
|
||||
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
||||
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_SUPL;
|
||||
@@ -114,6 +116,7 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.NonNull;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.NotificationManager;
|
||||
@@ -129,6 +132,7 @@ import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.net.CaptivePortalData;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.ConnectivityManager.NetworkCallback;
|
||||
import android.net.ConnectivityManager.PacketKeepalive;
|
||||
@@ -165,6 +169,7 @@ import android.net.ResolverParamsParcel;
|
||||
import android.net.RouteInfo;
|
||||
import android.net.SocketKeepalive;
|
||||
import android.net.UidRange;
|
||||
import android.net.Uri;
|
||||
import android.net.metrics.IpConnectivityLog;
|
||||
import android.net.shared.NetworkMonitorUtils;
|
||||
import android.net.shared.PrivateDnsConfig;
|
||||
@@ -243,8 +248,10 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -347,6 +354,8 @@ public class ConnectivityServiceTest {
|
||||
|
||||
@Spy private Resources mResources;
|
||||
private final LinkedBlockingQueue<Intent> mStartedActivities = new LinkedBlockingQueue<>();
|
||||
// Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
|
||||
private final HashMap<String, Integer> mMockedPermissions = new HashMap<>();
|
||||
|
||||
MockContext(Context base, ContentProvider settingsProvider) {
|
||||
super(base);
|
||||
@@ -416,14 +425,40 @@ public class ConnectivityServiceTest {
|
||||
return mPackageManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int checkPermission(String permission, int pid, int uid) {
|
||||
final Integer granted = mMockedPermissions.get(permission);
|
||||
if (granted == null) {
|
||||
// All non-mocked permissions should be held by the test or unnecessary: check as
|
||||
// normal to make sure the code does not rely on unexpected permissions.
|
||||
return super.checkPermission(permission, pid, uid);
|
||||
}
|
||||
return granted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enforceCallingOrSelfPermission(String permission, String message) {
|
||||
// The mainline permission can only be held if signed with the network stack certificate
|
||||
// Skip testing for this permission.
|
||||
if (NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK.equals(permission)) return;
|
||||
// All other permissions should be held by the test or unnecessary: check as normal to
|
||||
// make sure the code does not rely on unexpected permissions.
|
||||
final Integer granted = mMockedPermissions.get(permission);
|
||||
if (granted == null) {
|
||||
super.enforceCallingOrSelfPermission(permission, message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!granted.equals(PERMISSION_GRANTED)) {
|
||||
throw new SecurityException("[Test] permission denied: " + permission);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock checks for the specified permission, and have them behave as per {@code granted}.
|
||||
*
|
||||
* <p>Passing null reverts to default behavior, which does a real permission check on the
|
||||
* test package.
|
||||
* @param granted One of {@link PackageManager#PERMISSION_GRANTED} or
|
||||
* {@link PackageManager#PERMISSION_DENIED}.
|
||||
*/
|
||||
public void setPermission(String permission, Integer granted) {
|
||||
mMockedPermissions.put(permission, granted);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1750,6 +1785,66 @@ public class ConnectivityServiceTest {
|
||||
assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
|
||||
}
|
||||
|
||||
private void doNetworkCallbacksSanitizationTest(boolean sanitized) throws Exception {
|
||||
final TestNetworkCallback callback = new TestNetworkCallback();
|
||||
final TestNetworkCallback defaultCallback = new TestNetworkCallback();
|
||||
final NetworkRequest wifiRequest = new NetworkRequest.Builder()
|
||||
.addTransportType(TRANSPORT_WIFI).build();
|
||||
mCm.registerNetworkCallback(wifiRequest, callback);
|
||||
mCm.registerDefaultNetworkCallback(defaultCallback);
|
||||
|
||||
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
|
||||
mWiFiNetworkAgent.connect(false);
|
||||
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
|
||||
defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
|
||||
|
||||
final LinkProperties newLp = new LinkProperties();
|
||||
final Uri capportUrl = Uri.parse("https://capport.example.com/api");
|
||||
final CaptivePortalData capportData = new CaptivePortalData.Builder()
|
||||
.setCaptive(true).build();
|
||||
newLp.setCaptivePortalApiUrl(capportUrl);
|
||||
newLp.setCaptivePortalData(capportData);
|
||||
mWiFiNetworkAgent.sendLinkProperties(newLp);
|
||||
|
||||
final Uri expectedCapportUrl = sanitized ? null : capportUrl;
|
||||
final CaptivePortalData expectedCapportData = sanitized ? null : capportData;
|
||||
callback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp ->
|
||||
Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl())
|
||||
&& Objects.equals(expectedCapportData, lp.getCaptivePortalData()));
|
||||
defaultCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp ->
|
||||
Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl())
|
||||
&& Objects.equals(expectedCapportData, lp.getCaptivePortalData()));
|
||||
|
||||
final LinkProperties lp = mCm.getLinkProperties(mWiFiNetworkAgent.getNetwork());
|
||||
assertEquals(expectedCapportUrl, lp.getCaptivePortalApiUrl());
|
||||
assertEquals(expectedCapportData, lp.getCaptivePortalData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void networkCallbacksSanitizationTest_Sanitize() throws Exception {
|
||||
mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
|
||||
PERMISSION_DENIED);
|
||||
mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
|
||||
PERMISSION_DENIED);
|
||||
doNetworkCallbacksSanitizationTest(true /* sanitized */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void networkCallbacksSanitizationTest_NoSanitize_NetworkStack() throws Exception {
|
||||
mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
|
||||
PERMISSION_GRANTED);
|
||||
mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, PERMISSION_DENIED);
|
||||
doNetworkCallbacksSanitizationTest(false /* sanitized */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void networkCallbacksSanitizationTest_NoSanitize_Settings() throws Exception {
|
||||
mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
|
||||
PERMISSION_DENIED);
|
||||
mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
|
||||
doNetworkCallbacksSanitizationTest(false /* sanitized */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleLingering() throws Exception {
|
||||
// This test would be flaky with the default 120ms timer: that is short enough that
|
||||
@@ -2628,6 +2723,8 @@ public class ConnectivityServiceTest {
|
||||
final String testKey = "testkey";
|
||||
final String testValue = "testvalue";
|
||||
testBundle.putString(testKey, testValue);
|
||||
mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
|
||||
PERMISSION_GRANTED);
|
||||
mCm.startCaptivePortalApp(wifiNetwork, testBundle);
|
||||
final Intent signInIntent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS);
|
||||
assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, signInIntent.getAction());
|
||||
|
||||
Reference in New Issue
Block a user