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 String mTcpBufferSizes;
|
||||||
private IpPrefix mNat64Prefix;
|
private IpPrefix mNat64Prefix;
|
||||||
private boolean mWakeOnLanSupported;
|
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 = 68;
|
||||||
private static final int MIN_MTU_V6 = 1280;
|
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.
|
* Constructs a new {@code LinkProperties} with default values.
|
||||||
*/
|
*/
|
||||||
public LinkProperties() {
|
public LinkProperties() {
|
||||||
|
mParcelSensitiveFields = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -154,26 +163,32 @@ public final class LinkProperties implements Parcelable {
|
|||||||
@SystemApi
|
@SystemApi
|
||||||
@TestApi
|
@TestApi
|
||||||
public LinkProperties(@Nullable LinkProperties source) {
|
public LinkProperties(@Nullable LinkProperties source) {
|
||||||
if (source != null) {
|
this(source, false /* parcelSensitiveFields */);
|
||||||
mIfaceName = source.mIfaceName;
|
}
|
||||||
mLinkAddresses.addAll(source.mLinkAddresses);
|
|
||||||
mDnses.addAll(source.mDnses);
|
private LinkProperties(@Nullable LinkProperties source, boolean parcelSensitiveFields) {
|
||||||
mValidatedPrivateDnses.addAll(source.mValidatedPrivateDnses);
|
mParcelSensitiveFields = parcelSensitiveFields;
|
||||||
mUsePrivateDns = source.mUsePrivateDns;
|
if (source == null) return;
|
||||||
mPrivateDnsServerName = source.mPrivateDnsServerName;
|
mIfaceName = source.mIfaceName;
|
||||||
mPcscfs.addAll(source.mPcscfs);
|
mLinkAddresses.addAll(source.mLinkAddresses);
|
||||||
mDomains = source.mDomains;
|
mDnses.addAll(source.mDnses);
|
||||||
mRoutes.addAll(source.mRoutes);
|
mValidatedPrivateDnses.addAll(source.mValidatedPrivateDnses);
|
||||||
mHttpProxy = (source.mHttpProxy == null) ? null : new ProxyInfo(source.mHttpProxy);
|
mUsePrivateDns = source.mUsePrivateDns;
|
||||||
for (LinkProperties l: source.mStackedLinks.values()) {
|
mPrivateDnsServerName = source.mPrivateDnsServerName;
|
||||||
addStackedLink(l);
|
mPcscfs.addAll(source.mPcscfs);
|
||||||
}
|
mDomains = source.mDomains;
|
||||||
setMtu(source.mMtu);
|
mRoutes.addAll(source.mRoutes);
|
||||||
setDhcpServerAddress(source.getDhcpServerAddress());
|
mHttpProxy = (source.mHttpProxy == null) ? null : new ProxyInfo(source.mHttpProxy);
|
||||||
mTcpBufferSizes = source.mTcpBufferSizes;
|
for (LinkProperties l: source.mStackedLinks.values()) {
|
||||||
mNat64Prefix = source.mNat64Prefix;
|
addStackedLink(l);
|
||||||
mWakeOnLanSupported = source.mWakeOnLanSupported;
|
|
||||||
}
|
}
|
||||||
|
setMtu(source.mMtu);
|
||||||
|
setDhcpServerAddress(source.getDhcpServerAddress());
|
||||||
|
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.
|
* Clears this object to its initial state.
|
||||||
*/
|
*/
|
||||||
public void clear() {
|
public void clear() {
|
||||||
|
if (mParcelSensitiveFields) {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"Cannot clear LinkProperties when parcelSensitiveFields is set");
|
||||||
|
}
|
||||||
|
|
||||||
mIfaceName = null;
|
mIfaceName = null;
|
||||||
mLinkAddresses.clear();
|
mLinkAddresses.clear();
|
||||||
mDnses.clear();
|
mDnses.clear();
|
||||||
@@ -847,6 +867,8 @@ public final class LinkProperties implements Parcelable {
|
|||||||
mTcpBufferSizes = null;
|
mTcpBufferSizes = null;
|
||||||
mNat64Prefix = null;
|
mNat64Prefix = null;
|
||||||
mWakeOnLanSupported = false;
|
mWakeOnLanSupported = false;
|
||||||
|
mCaptivePortalApiUrl = null;
|
||||||
|
mCaptivePortalData = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -917,6 +939,14 @@ public final class LinkProperties implements Parcelable {
|
|||||||
resultJoiner.add(mDhcpServerAddress.toString());
|
resultJoiner.add(mDhcpServerAddress.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mCaptivePortalApiUrl != null) {
|
||||||
|
resultJoiner.add("CaptivePortalApiUrl: " + mCaptivePortalApiUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mCaptivePortalData != null) {
|
||||||
|
resultJoiner.add("CaptivePortalData: " + mCaptivePortalData);
|
||||||
|
}
|
||||||
|
|
||||||
if (mTcpBufferSizes != null) {
|
if (mTcpBufferSizes != null) {
|
||||||
resultJoiner.add("TcpBufferSizes:");
|
resultJoiner.add("TcpBufferSizes:");
|
||||||
resultJoiner.add(mTcpBufferSizes);
|
resultJoiner.add(mTcpBufferSizes);
|
||||||
@@ -1436,6 +1466,28 @@ public final class LinkProperties implements Parcelable {
|
|||||||
return isWakeOnLanSupported() == target.isWakeOnLanSupported();
|
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
|
* Set whether the network interface supports WakeOnLAN
|
||||||
*
|
*
|
||||||
@@ -1456,6 +1508,73 @@ public final class LinkProperties implements Parcelable {
|
|||||||
return mWakeOnLanSupported;
|
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
|
* Compares this {@code LinkProperties} instance against the target
|
||||||
* LinkProperties in {@code obj}. Two LinkPropertieses are equal if
|
* LinkProperties in {@code obj}. Two LinkPropertieses are equal if
|
||||||
@@ -1495,7 +1614,9 @@ public final class LinkProperties implements Parcelable {
|
|||||||
&& isIdenticalMtu(target)
|
&& isIdenticalMtu(target)
|
||||||
&& isIdenticalTcpBufferSizes(target)
|
&& isIdenticalTcpBufferSizes(target)
|
||||||
&& isIdenticalNat64Prefix(target)
|
&& isIdenticalNat64Prefix(target)
|
||||||
&& isIdenticalWakeOnLan(target);
|
&& isIdenticalWakeOnLan(target)
|
||||||
|
&& isIdenticalCaptivePortalApiUrl(target)
|
||||||
|
&& isIdenticalCaptivePortalData(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1593,7 +1714,8 @@ public final class LinkProperties implements Parcelable {
|
|||||||
+ mPcscfs.size() * 67
|
+ mPcscfs.size() * 67
|
||||||
+ ((null == mPrivateDnsServerName) ? 0 : mPrivateDnsServerName.hashCode())
|
+ ((null == mPrivateDnsServerName) ? 0 : mPrivateDnsServerName.hashCode())
|
||||||
+ Objects.hash(mNat64Prefix)
|
+ 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.writeList(stackedLinks);
|
||||||
|
|
||||||
dest.writeBoolean(mWakeOnLanSupported);
|
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) {
|
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.addStackedLink(stackedLink);
|
||||||
}
|
}
|
||||||
netProp.setWakeOnLanSupported(in.readBoolean());
|
netProp.setWakeOnLanSupported(in.readBoolean());
|
||||||
|
|
||||||
|
netProp.setCaptivePortalApiUrl(in.readParcelable(null));
|
||||||
|
netProp.setCaptivePortalData(in.readParcelable(null));
|
||||||
return netProp;
|
return netProp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1571,48 +1571,49 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||||||
enforceAccessPermission();
|
enforceAccessPermission();
|
||||||
final int uid = Binder.getCallingUid();
|
final int uid = Binder.getCallingUid();
|
||||||
NetworkState state = getUnfilteredActiveNetworkState(uid);
|
NetworkState state = getUnfilteredActiveNetworkState(uid);
|
||||||
return state.linkProperties;
|
if (state.linkProperties == null) return null;
|
||||||
|
return linkPropertiesRestrictedForCallerPermissions(state.linkProperties,
|
||||||
|
Binder.getCallingPid(), uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LinkProperties getLinkPropertiesForType(int networkType) {
|
public LinkProperties getLinkPropertiesForType(int networkType) {
|
||||||
enforceAccessPermission();
|
enforceAccessPermission();
|
||||||
NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
|
NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
|
||||||
if (nai != null) {
|
final LinkProperties lp = getLinkProperties(nai);
|
||||||
synchronized (nai) {
|
if (lp == null) return null;
|
||||||
return new LinkProperties(nai.linkProperties);
|
return linkPropertiesRestrictedForCallerPermissions(
|
||||||
}
|
lp, Binder.getCallingPid(), Binder.getCallingUid());
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - this should be ALL networks
|
// TODO - this should be ALL networks
|
||||||
@Override
|
@Override
|
||||||
public LinkProperties getLinkProperties(Network network) {
|
public LinkProperties getLinkProperties(Network network) {
|
||||||
enforceAccessPermission();
|
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) {
|
if (nai == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
synchronized (nai) {
|
synchronized (nai) {
|
||||||
return new LinkProperties(nai.linkProperties);
|
return nai.linkProperties;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private NetworkCapabilities getNetworkCapabilitiesInternal(NetworkAgentInfo nai) {
|
private NetworkCapabilities getNetworkCapabilitiesInternal(NetworkAgentInfo nai) {
|
||||||
if (nai != null) {
|
if (nai == null) return null;
|
||||||
synchronized (nai) {
|
synchronized (nai) {
|
||||||
if (nai.networkCapabilities != null) {
|
if (nai.networkCapabilities == null) return null;
|
||||||
return networkCapabilitiesRestrictedForCallerPermissions(
|
return networkCapabilitiesRestrictedForCallerPermissions(
|
||||||
nai.networkCapabilities,
|
nai.networkCapabilities,
|
||||||
Binder.getCallingPid(), Binder.getCallingUid());
|
Binder.getCallingPid(), Binder.getCallingUid());
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1634,6 +1635,29 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||||||
return newNc;
|
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) {
|
private void restrictRequestUidsForCaller(NetworkCapabilities nc) {
|
||||||
if (!checkSettingsPermission()) {
|
if (!checkSettingsPermission()) {
|
||||||
nc.setSingleUid(Binder.getCallingUid());
|
nc.setSingleUid(Binder.getCallingUid());
|
||||||
@@ -6145,7 +6169,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||||||
case ConnectivityManager.CALLBACK_AVAILABLE: {
|
case ConnectivityManager.CALLBACK_AVAILABLE: {
|
||||||
putParcelable(bundle, networkCapabilitiesRestrictedForCallerPermissions(
|
putParcelable(bundle, networkCapabilitiesRestrictedForCallerPermissions(
|
||||||
networkAgent.networkCapabilities, nri.mPid, nri.mUid));
|
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.
|
// For this notification, arg1 contains the blocked status.
|
||||||
msg.arg1 = arg1;
|
msg.arg1 = arg1;
|
||||||
break;
|
break;
|
||||||
@@ -6162,7 +6187,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ConnectivityManager.CALLBACK_IP_CHANGED: {
|
case ConnectivityManager.CALLBACK_IP_CHANGED: {
|
||||||
putParcelable(bundle, new LinkProperties(networkAgent.linkProperties));
|
putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions(
|
||||||
|
networkAgent.linkProperties, nri.mPid, nri.mUid));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ConnectivityManager.CALLBACK_BLK_CHANGED: {
|
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 LINKADDRV4 = new LinkAddress(ADDRV4, 32);
|
||||||
private static final LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128);
|
private static final LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128);
|
||||||
private static final LinkAddress LINKADDRV6LINKLOCAL = new LinkAddress("fe80::1/64");
|
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) {
|
private static InetAddress address(String addrString) {
|
||||||
return InetAddresses.parseNumericAddress(addrString);
|
return InetAddresses.parseNumericAddress(addrString);
|
||||||
@@ -101,6 +104,8 @@ public class LinkPropertiesTest {
|
|||||||
assertFalse(lp.isIpv6Provisioned());
|
assertFalse(lp.isIpv6Provisioned());
|
||||||
assertFalse(lp.isPrivateDnsActive());
|
assertFalse(lp.isPrivateDnsActive());
|
||||||
assertFalse(lp.isWakeOnLanSupported());
|
assertFalse(lp.isWakeOnLanSupported());
|
||||||
|
assertNull(lp.getCaptivePortalApiUrl());
|
||||||
|
assertNull(lp.getCaptivePortalData());
|
||||||
}
|
}
|
||||||
|
|
||||||
private LinkProperties makeTestObject() {
|
private LinkProperties makeTestObject() {
|
||||||
@@ -124,6 +129,8 @@ public class LinkPropertiesTest {
|
|||||||
lp.setNat64Prefix(new IpPrefix("2001:db8:0:64::/96"));
|
lp.setNat64Prefix(new IpPrefix("2001:db8:0:64::/96"));
|
||||||
lp.setDhcpServerAddress(DHCPSERVER);
|
lp.setDhcpServerAddress(DHCPSERVER);
|
||||||
lp.setWakeOnLanSupported(true);
|
lp.setWakeOnLanSupported(true);
|
||||||
|
lp.setCaptivePortalApiUrl(CAPPORT_API_URL);
|
||||||
|
lp.setCaptivePortalData(CAPPORT_DATA);
|
||||||
return lp;
|
return lp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,6 +172,12 @@ public class LinkPropertiesTest {
|
|||||||
assertTrue(source.isIdenticalWakeOnLan(target));
|
assertTrue(source.isIdenticalWakeOnLan(target));
|
||||||
assertTrue(target.isIdenticalWakeOnLan(source));
|
assertTrue(target.isIdenticalWakeOnLan(source));
|
||||||
|
|
||||||
|
assertTrue(source.isIdenticalCaptivePortalApiUrl(target));
|
||||||
|
assertTrue(target.isIdenticalCaptivePortalApiUrl(source));
|
||||||
|
|
||||||
|
assertTrue(source.isIdenticalCaptivePortalData(target));
|
||||||
|
assertTrue(target.isIdenticalCaptivePortalData(source));
|
||||||
|
|
||||||
// Check result of equals().
|
// Check result of equals().
|
||||||
assertTrue(source.equals(target));
|
assertTrue(source.equals(target));
|
||||||
assertTrue(target.equals(source));
|
assertTrue(target.equals(source));
|
||||||
@@ -963,6 +976,8 @@ public class LinkPropertiesTest {
|
|||||||
source.setNat64Prefix(new IpPrefix("2001:db8:1:2:64:64::/96"));
|
source.setNat64Prefix(new IpPrefix("2001:db8:1:2:64:64::/96"));
|
||||||
|
|
||||||
source.setWakeOnLanSupported(true);
|
source.setWakeOnLanSupported(true);
|
||||||
|
source.setCaptivePortalApiUrl(CAPPORT_API_URL);
|
||||||
|
source.setCaptivePortalData(CAPPORT_DATA);
|
||||||
|
|
||||||
source.setDhcpServerAddress((Inet4Address) GATEWAY1);
|
source.setDhcpServerAddress((Inet4Address) GATEWAY1);
|
||||||
|
|
||||||
@@ -970,7 +985,13 @@ public class LinkPropertiesTest {
|
|||||||
stacked.setInterfaceName("test-stacked");
|
stacked.setInterfaceName("test-stacked");
|
||||||
source.addStackedLink(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
|
@Test
|
||||||
@@ -1113,4 +1134,22 @@ public class LinkPropertiesTest {
|
|||||||
lp.clear();
|
lp.clear();
|
||||||
assertFalse(lp.isWakeOnLanSupported());
|
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.PackageInfo.REQUESTED_PERMISSION_GRANTED;
|
||||||
import static android.content.pm.PackageManager.GET_PERMISSIONS;
|
import static android.content.pm.PackageManager.GET_PERMISSIONS;
|
||||||
import static android.content.pm.PackageManager.MATCH_ANY_USER;
|
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.ACTION_CAPTIVE_PORTAL_SIGN_IN;
|
||||||
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
||||||
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_SUPL;
|
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.verifyNoMoreInteractions;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.app.AlarmManager;
|
import android.app.AlarmManager;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
@@ -129,6 +132,7 @@ import android.content.pm.PackageInfo;
|
|||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.UserInfo;
|
import android.content.pm.UserInfo;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
import android.net.CaptivePortalData;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.ConnectivityManager.NetworkCallback;
|
import android.net.ConnectivityManager.NetworkCallback;
|
||||||
import android.net.ConnectivityManager.PacketKeepalive;
|
import android.net.ConnectivityManager.PacketKeepalive;
|
||||||
@@ -165,6 +169,7 @@ import android.net.ResolverParamsParcel;
|
|||||||
import android.net.RouteInfo;
|
import android.net.RouteInfo;
|
||||||
import android.net.SocketKeepalive;
|
import android.net.SocketKeepalive;
|
||||||
import android.net.UidRange;
|
import android.net.UidRange;
|
||||||
|
import android.net.Uri;
|
||||||
import android.net.metrics.IpConnectivityLog;
|
import android.net.metrics.IpConnectivityLog;
|
||||||
import android.net.shared.NetworkMonitorUtils;
|
import android.net.shared.NetworkMonitorUtils;
|
||||||
import android.net.shared.PrivateDnsConfig;
|
import android.net.shared.PrivateDnsConfig;
|
||||||
@@ -243,8 +248,10 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@@ -347,6 +354,8 @@ public class ConnectivityServiceTest {
|
|||||||
|
|
||||||
@Spy private Resources mResources;
|
@Spy private Resources mResources;
|
||||||
private final LinkedBlockingQueue<Intent> mStartedActivities = new LinkedBlockingQueue<>();
|
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) {
|
MockContext(Context base, ContentProvider settingsProvider) {
|
||||||
super(base);
|
super(base);
|
||||||
@@ -416,14 +425,40 @@ public class ConnectivityServiceTest {
|
|||||||
return mPackageManager;
|
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
|
@Override
|
||||||
public void enforceCallingOrSelfPermission(String permission, String message) {
|
public void enforceCallingOrSelfPermission(String permission, String message) {
|
||||||
// The mainline permission can only be held if signed with the network stack certificate
|
final Integer granted = mMockedPermissions.get(permission);
|
||||||
// Skip testing for this permission.
|
if (granted == null) {
|
||||||
if (NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK.equals(permission)) return;
|
super.enforceCallingOrSelfPermission(permission, message);
|
||||||
// All other permissions should be held by the test or unnecessary: check as normal to
|
return;
|
||||||
// make sure the code does not rely on unexpected permissions.
|
}
|
||||||
super.enforceCallingOrSelfPermission(permission, message);
|
|
||||||
|
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
|
@Override
|
||||||
@@ -1750,6 +1785,66 @@ public class ConnectivityServiceTest {
|
|||||||
assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
|
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
|
@Test
|
||||||
public void testMultipleLingering() throws Exception {
|
public void testMultipleLingering() throws Exception {
|
||||||
// This test would be flaky with the default 120ms timer: that is short enough that
|
// 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 testKey = "testkey";
|
||||||
final String testValue = "testvalue";
|
final String testValue = "testvalue";
|
||||||
testBundle.putString(testKey, testValue);
|
testBundle.putString(testKey, testValue);
|
||||||
|
mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
|
||||||
|
PERMISSION_GRANTED);
|
||||||
mCm.startCaptivePortalApp(wifiNetwork, testBundle);
|
mCm.startCaptivePortalApp(wifiNetwork, testBundle);
|
||||||
final Intent signInIntent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS);
|
final Intent signInIntent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS);
|
||||||
assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, signInIntent.getAction());
|
assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, signInIntent.getAction());
|
||||||
|
|||||||
Reference in New Issue
Block a user