Merge "KeySetManagerService: prevent NPE"
diff --git a/api/current.txt b/api/current.txt
index 10a5d92..42275de 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -24696,6 +24696,7 @@
method public java.security.cert.X509Certificate getCaCertificate();
method public java.security.cert.X509Certificate[] getCaCertificates();
method public java.security.cert.X509Certificate getClientCertificate();
+ method public java.security.cert.X509Certificate[] getClientCertificateChain();
method public java.lang.String getDomainSuffixMatch();
method public int getEapMethod();
method public java.lang.String getIdentity();
@@ -24709,6 +24710,7 @@
method public void setCaCertificate(java.security.cert.X509Certificate);
method public void setCaCertificates(java.security.cert.X509Certificate[]);
method public void setClientKeyEntry(java.security.PrivateKey, java.security.cert.X509Certificate);
+ method public void setClientKeyEntryWithCertificateChain(java.security.PrivateKey, java.security.cert.X509Certificate[]);
method public void setDomainSuffixMatch(java.lang.String);
method public void setEapMethod(int);
method public void setIdentity(java.lang.String);
@@ -24784,6 +24786,7 @@
method public boolean isTdlsSupported();
method public boolean isWifiEnabled();
method public boolean pingSupplicant();
+ method public void queryPasspointIcon(long, java.lang.String);
method public boolean reassociate();
method public boolean reconnect();
method public boolean removeNetwork(int);
@@ -24794,6 +24797,10 @@
method public boolean startScan();
method public void startWps(android.net.wifi.WpsInfo, android.net.wifi.WifiManager.WpsCallback);
method public int updateNetwork(android.net.wifi.WifiConfiguration);
+ field public static final java.lang.String ACTION_PASSPOINT_DEAUTH_IMMINENT = "android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT";
+ field public static final java.lang.String ACTION_PASSPOINT_ICON = "android.net.wifi.action.PASSPOINT_ICON";
+ field public static final java.lang.String ACTION_PASSPOINT_OSU_PROVIDERS_LIST = "android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST";
+ field public static final java.lang.String ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION = "android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION";
field public static final java.lang.String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
field public static final java.lang.String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
field public static final int ERROR_AUTHENTICATING = 1; // 0x1
@@ -24801,6 +24808,18 @@
field public static final java.lang.String EXTRA_NETWORK_INFO = "networkInfo";
field public static final java.lang.String EXTRA_NEW_RSSI = "newRssi";
field public static final java.lang.String EXTRA_NEW_STATE = "newState";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_BSSID = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_ESS = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_ESS";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REASON_URL = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REASON_URL";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY";
+ field public static final java.lang.String EXTRA_PASSPOINT_ICON_BSSID = "android.net.wifi.extra.PASSPOINT_ICON_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_ICON_DATA = "android.net.wifi.extra.PASSPOINT_ICON_DATA";
+ field public static final java.lang.String EXTRA_PASSPOINT_ICON_FILENAME = "android.net.wifi.extra.PASSPOINT_ICON_FILENAME";
+ field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_BSSID = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_DATA = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_DATA";
+ field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD";
+ field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL";
field public static final java.lang.String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state";
field public static final java.lang.String EXTRA_RESULTS_UPDATED = "resultsUpdated";
field public static final java.lang.String EXTRA_SUPPLICANT_CONNECTED = "connected";
diff --git a/api/system-current.txt b/api/system-current.txt
index 1c04a96..6ebbee2 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -27069,6 +27069,7 @@
method public java.security.cert.X509Certificate getCaCertificate();
method public java.security.cert.X509Certificate[] getCaCertificates();
method public java.security.cert.X509Certificate getClientCertificate();
+ method public java.security.cert.X509Certificate[] getClientCertificateChain();
method public java.lang.String getDomainSuffixMatch();
method public int getEapMethod();
method public java.lang.String getIdentity();
@@ -27082,6 +27083,7 @@
method public void setCaCertificate(java.security.cert.X509Certificate);
method public void setCaCertificates(java.security.cert.X509Certificate[]);
method public void setClientKeyEntry(java.security.PrivateKey, java.security.cert.X509Certificate);
+ method public void setClientKeyEntryWithCertificateChain(java.security.PrivateKey, java.security.cert.X509Certificate[]);
method public void setDomainSuffixMatch(java.lang.String);
method public void setEapMethod(int);
method public void setIdentity(java.lang.String);
@@ -27168,6 +27170,7 @@
method public boolean isWifiEnabled();
method public boolean isWifiScannerSupported();
method public boolean pingSupplicant();
+ method public void queryPasspointIcon(long, java.lang.String);
method public boolean reassociate();
method public boolean reconnect();
method public boolean removeNetwork(int);
@@ -27182,6 +27185,10 @@
method public boolean startScan(android.os.WorkSource);
method public void startWps(android.net.wifi.WpsInfo, android.net.wifi.WifiManager.WpsCallback);
method public int updateNetwork(android.net.wifi.WifiConfiguration);
+ field public static final java.lang.String ACTION_PASSPOINT_DEAUTH_IMMINENT = "android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT";
+ field public static final java.lang.String ACTION_PASSPOINT_ICON = "android.net.wifi.action.PASSPOINT_ICON";
+ field public static final java.lang.String ACTION_PASSPOINT_OSU_PROVIDERS_LIST = "android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST";
+ field public static final java.lang.String ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION = "android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION";
field public static final java.lang.String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
field public static final java.lang.String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
field public static final int CHANGE_REASON_ADDED = 0; // 0x0
@@ -27195,6 +27202,18 @@
field public static final java.lang.String EXTRA_NETWORK_INFO = "networkInfo";
field public static final java.lang.String EXTRA_NEW_RSSI = "newRssi";
field public static final java.lang.String EXTRA_NEW_STATE = "newState";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_BSSID = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_ESS = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_ESS";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REASON_URL = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REASON_URL";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY";
+ field public static final java.lang.String EXTRA_PASSPOINT_ICON_BSSID = "android.net.wifi.extra.PASSPOINT_ICON_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_ICON_DATA = "android.net.wifi.extra.PASSPOINT_ICON_DATA";
+ field public static final java.lang.String EXTRA_PASSPOINT_ICON_FILENAME = "android.net.wifi.extra.PASSPOINT_ICON_FILENAME";
+ field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_BSSID = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_DATA = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_DATA";
+ field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD";
+ field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL";
field public static final java.lang.String EXTRA_PREVIOUS_WIFI_AP_STATE = "previous_wifi_state";
field public static final java.lang.String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state";
field public static final java.lang.String EXTRA_RESULTS_UPDATED = "resultsUpdated";
diff --git a/api/test-current.txt b/api/test-current.txt
index 4b088e1..40085a2 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -24769,6 +24769,7 @@
method public java.security.cert.X509Certificate getCaCertificate();
method public java.security.cert.X509Certificate[] getCaCertificates();
method public java.security.cert.X509Certificate getClientCertificate();
+ method public java.security.cert.X509Certificate[] getClientCertificateChain();
method public java.lang.String getDomainSuffixMatch();
method public int getEapMethod();
method public java.lang.String getIdentity();
@@ -24782,6 +24783,7 @@
method public void setCaCertificate(java.security.cert.X509Certificate);
method public void setCaCertificates(java.security.cert.X509Certificate[]);
method public void setClientKeyEntry(java.security.PrivateKey, java.security.cert.X509Certificate);
+ method public void setClientKeyEntryWithCertificateChain(java.security.PrivateKey, java.security.cert.X509Certificate[]);
method public void setDomainSuffixMatch(java.lang.String);
method public void setEapMethod(int);
method public void setIdentity(java.lang.String);
@@ -24857,6 +24859,7 @@
method public boolean isTdlsSupported();
method public boolean isWifiEnabled();
method public boolean pingSupplicant();
+ method public void queryPasspointIcon(long, java.lang.String);
method public boolean reassociate();
method public boolean reconnect();
method public boolean removeNetwork(int);
@@ -24867,6 +24870,10 @@
method public boolean startScan();
method public void startWps(android.net.wifi.WpsInfo, android.net.wifi.WifiManager.WpsCallback);
method public int updateNetwork(android.net.wifi.WifiConfiguration);
+ field public static final java.lang.String ACTION_PASSPOINT_DEAUTH_IMMINENT = "android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT";
+ field public static final java.lang.String ACTION_PASSPOINT_ICON = "android.net.wifi.action.PASSPOINT_ICON";
+ field public static final java.lang.String ACTION_PASSPOINT_OSU_PROVIDERS_LIST = "android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST";
+ field public static final java.lang.String ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION = "android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION";
field public static final java.lang.String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
field public static final java.lang.String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
field public static final int ERROR_AUTHENTICATING = 1; // 0x1
@@ -24874,6 +24881,18 @@
field public static final java.lang.String EXTRA_NETWORK_INFO = "networkInfo";
field public static final java.lang.String EXTRA_NEW_RSSI = "newRssi";
field public static final java.lang.String EXTRA_NEW_STATE = "newState";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_BSSID = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_ESS = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_ESS";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REASON_URL = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REASON_URL";
+ field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY";
+ field public static final java.lang.String EXTRA_PASSPOINT_ICON_BSSID = "android.net.wifi.extra.PASSPOINT_ICON_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_ICON_DATA = "android.net.wifi.extra.PASSPOINT_ICON_DATA";
+ field public static final java.lang.String EXTRA_PASSPOINT_ICON_FILENAME = "android.net.wifi.extra.PASSPOINT_ICON_FILENAME";
+ field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_BSSID = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_DATA = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_DATA";
+ field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID";
+ field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD";
+ field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL";
field public static final java.lang.String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state";
field public static final java.lang.String EXTRA_RESULTS_UPDATED = "resultsUpdated";
field public static final java.lang.String EXTRA_SUPPLICANT_CONNECTED = "connected";
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index d6c0058..a66b0b9 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -110,6 +110,7 @@
private String mProfileFile;
private int mSamplingInterval;
private boolean mAutoStop;
+ private boolean mStreaming; // Streaming the profiling output to a file.
private int mStackId;
/**
@@ -127,7 +128,7 @@
pw.println(
"usage: am [subcommand] [options]\n" +
"usage: am start [-D] [-N] [-W] [-P <FILE>] [--start-profiler <FILE>]\n" +
- " [--sampling INTERVAL] [-R COUNT] [-S]\n" +
+ " [--sampling INTERVAL] [--streaming] [-R COUNT] [-S]\n" +
" [--track-allocation] [--user <USER_ID> | current] <INTENT>\n" +
" am startservice [--user <USER_ID> | current] <INTENT>\n" +
" am stopservice [--user <USER_ID> | current] <INTENT>\n" +
@@ -138,7 +139,8 @@
" am instrument [-r] [-e <NAME> <VALUE>] [-p <FILE>] [-w]\n" +
" [--user <USER_ID> | current]\n" +
" [--no-window-animation] [--abi <ABI>] <COMPONENT>\n" +
- " am profile start [--user <USER_ID> current] [--sampling INTERVAL] <PROCESS> <FILE>\n" +
+ " am profile start [--user <USER_ID> current] [--sampling INTERVAL]\n"+
+ " [--streaming] <PROCESS> <FILE>\n" +
" am profile stop [--user <USER_ID> current] [<PROCESS>]\n" +
" am dumpheap [--user <USER_ID> current] [-n] <PROCESS> <FILE>\n" +
" am set-debug-app [-w] [--persistent] <PACKAGE>\n" +
@@ -191,6 +193,8 @@
" --start-profiler <FILE>: start profiler and send results to <FILE>\n" +
" --sampling INTERVAL: use sample profiling with INTERVAL microseconds\n" +
" between samples (use with --start-profiler)\n" +
+ " --streaming: stream the profiling output to the specified file (use\n" +
+ " with --start-profiler)\n" +
" -P <FILE>: like above, but profiling stops when app goes idle\n" +
" -R: repeat the activity launch <COUNT> times. Prior to each repeat,\n" +
" the top activity will be finished.\n" +
@@ -250,6 +254,9 @@
" may be either a process name or pid. Options are:\n" +
" --user <USER_ID> | current: When supplying a process name,\n" +
" specify user of process to profile; uses current user if not specified.\n" +
+ " --sampling INTERVAL: use sample profiling with INTERVAL microseconds\n" +
+ " between samples\n" +
+ " --streaming: stream the profiling output to the specified file\n" +
"\n" +
"am dumpheap: dump the heap of a process. The given <PROCESS> argument may\n" +
" be either a process name or pid. Options are:\n" +
@@ -483,6 +490,7 @@
mProfileFile = null;
mSamplingInterval = 0;
mAutoStop = false;
+ mStreaming = false;
mUserId = defUser;
mStackId = INVALID_STACK_ID;
@@ -503,6 +511,8 @@
mAutoStop = false;
} else if (opt.equals("--sampling")) {
mSamplingInterval = Integer.parseInt(nextArgRequired());
+ } else if (opt.equals("--streaming")) {
+ mStreaming = true;
} else if (opt.equals("-R")) {
mRepeat = Integer.parseInt(nextArgRequired());
} else if (opt.equals("-S")) {
@@ -615,7 +625,8 @@
System.err.println("Consider using a file under /data/local/tmp/");
return;
}
- profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop);
+ profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop,
+ mStreaming);
}
IActivityManager.WaitResult result = null;
@@ -973,6 +984,7 @@
int userId = UserHandle.USER_CURRENT;
int profileType = 0;
mSamplingInterval = 0;
+ mStreaming = false;
String process = null;
@@ -986,6 +998,8 @@
userId = parseUserArg(nextArgRequired());
} else if (opt.equals("--wall")) {
wall = true;
+ } else if (opt.equals("--streaming")) {
+ mStreaming = true;
} else if (opt.equals("--sampling")) {
mSamplingInterval = Integer.parseInt(nextArgRequired());
} else {
@@ -1037,7 +1051,7 @@
System.err.println("Consider using a file under /data/local/tmp/");
return;
}
- profilerInfo = new ProfilerInfo(profileFile, fd, mSamplingInterval, false);
+ profilerInfo = new ProfilerInfo(profileFile, fd, mSamplingInterval, false, mStreaming);
}
try {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index af981f6..e1ff383 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -32,6 +32,7 @@
import android.os.ParcelFileDescriptor;
import com.android.internal.app.procstats.ProcessStats;
+import com.android.internal.os.RoSystemProperties;
import com.android.internal.os.TransferPipe;
import com.android.internal.util.FastPrintWriter;
@@ -895,7 +896,7 @@
/** @hide */
public static boolean isLowRamDeviceStatic() {
- return "true".equals(SystemProperties.get("ro.config.low_ram", "false"));
+ return RoSystemProperties.CONFIG_LOW_RAM;
}
/**
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2d22f26..cae4be6 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -534,6 +534,7 @@
ParcelFileDescriptor profileFd;
int samplingInterval;
boolean autoStopProfiler;
+ boolean streamingOutput;
boolean profiling;
boolean handlingProfiling;
public void setProfiler(ProfilerInfo profilerInfo) {
@@ -559,6 +560,7 @@
profileFd = fd;
samplingInterval = profilerInfo.samplingInterval;
autoStopProfiler = profilerInfo.autoStopProfiler;
+ streamingOutput = profilerInfo.streamingOutput;
}
public void startProfiling() {
if (profileFd == null || profiling) {
@@ -567,7 +569,8 @@
try {
int bufferSize = SystemProperties.getInt("debug.traceview-buffer-size-mb", 8);
VMDebug.startMethodTracing(profileFile, profileFd.getFileDescriptor(),
- bufferSize * 1024 * 1024, 0, samplingInterval != 0, samplingInterval);
+ bufferSize * 1024 * 1024, 0, samplingInterval != 0, samplingInterval,
+ streamingOutput);
profiling = true;
} catch (RuntimeException e) {
Slog.w(TAG, "Profiling failed on path " + profileFile);
@@ -5109,6 +5112,7 @@
mProfiler.profileFd = data.initProfilerInfo.profileFd;
mProfiler.samplingInterval = data.initProfilerInfo.samplingInterval;
mProfiler.autoStopProfiler = data.initProfilerInfo.autoStopProfiler;
+ mProfiler.streamingOutput = data.initProfilerInfo.streamingOutput;
}
// send up app name; do this *before* waiting for debugger
diff --git a/core/java/android/app/ProfilerInfo.java b/core/java/android/app/ProfilerInfo.java
index cea7c3c..f3fe677 100644
--- a/core/java/android/app/ProfilerInfo.java
+++ b/core/java/android/app/ProfilerInfo.java
@@ -39,11 +39,16 @@
/* Automatically stop the profiler when the app goes idle. */
public final boolean autoStopProfiler;
- public ProfilerInfo(String filename, ParcelFileDescriptor fd, int interval, boolean autoStop) {
+ /* Indicates whether to stream the profiling info to the out file continuously. */
+ public final boolean streamingOutput;
+
+ public ProfilerInfo(String filename, ParcelFileDescriptor fd, int interval, boolean autoStop,
+ boolean streaming) {
profileFile = filename;
profileFd = fd;
samplingInterval = interval;
autoStopProfiler = autoStop;
+ streamingOutput = streaming;
}
public int describeContents() {
@@ -64,6 +69,7 @@
}
out.writeInt(samplingInterval);
out.writeInt(autoStopProfiler ? 1 : 0);
+ out.writeInt(streamingOutput ? 1 : 0);
}
public static final Parcelable.Creator<ProfilerInfo> CREATOR =
@@ -82,5 +88,6 @@
profileFd = in.readInt() != 0 ? ParcelFileDescriptor.CREATOR.createFromParcel(in) : null;
samplingInterval = in.readInt();
autoStopProfiler = in.readInt() != 0;
+ streamingOutput = in.readInt() != 0;
}
}
diff --git a/core/java/android/bluetooth/BluetoothCodecConfig.java b/core/java/android/bluetooth/BluetoothCodecConfig.java
index 52cd2de..a37a0b3 100644
--- a/core/java/android/bluetooth/BluetoothCodecConfig.java
+++ b/core/java/android/bluetooth/BluetoothCodecConfig.java
@@ -51,9 +51,10 @@
// NOTE: The values should be same as those listed in the following file:
// hardware/libhardware/include/hardware/bt_av.h
public static final int SOURCE_CODEC_TYPE_SBC = 0;
- public static final int SOURCE_CODEC_TYPE_APTX = 1;
- public static final int SOURCE_CODEC_TYPE_APTX_HD = 2;
- public static final int SOURCE_CODEC_TYPE_LDAC = 3;
+ public static final int SOURCE_CODEC_TYPE_AAC = 1;
+ public static final int SOURCE_CODEC_TYPE_APTX = 2;
+ public static final int SOURCE_CODEC_TYPE_APTX_HD = 3;
+ public static final int SOURCE_CODEC_TYPE_LDAC = 4;
public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000;
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index 27096b1..b56437e 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -18,6 +18,8 @@
import android.os.SystemProperties;
import android.util.Log;
+
+import com.android.internal.os.RoSystemProperties;
import com.android.org.conscrypt.OpenSSLContextImpl;
import com.android.org.conscrypt.OpenSSLSocketImpl;
import com.android.org.conscrypt.SSLClientSessionCache;
@@ -221,8 +223,8 @@
}
private static boolean isSslCheckRelaxed() {
- return "1".equals(SystemProperties.get("ro.debuggable")) &&
- "yes".equals(SystemProperties.get("socket.relaxsslcheck"));
+ return RoSystemProperties.DEBUGGABLE &&
+ SystemProperties.getBoolean("socket.relaxsslcheck", false);
}
private synchronized SSLSocketFactory getDelegate() {
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 175d883..210ddb6 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -1119,8 +1119,8 @@
* @hide
*/
public static void startMethodTracing(String traceName, FileDescriptor fd,
- int bufferSize, int flags) {
- VMDebug.startMethodTracing(traceName, fd, bufferSize, flags, false, 0);
+ int bufferSize, int flags, boolean streamOutput) {
+ VMDebug.startMethodTracing(traceName, fd, bufferSize, flags, false, 0, streamOutput);
}
/**
diff --git a/core/java/android/os/FactoryTest.java b/core/java/android/os/FactoryTest.java
index 7a252f9..b59227c 100644
--- a/core/java/android/os/FactoryTest.java
+++ b/core/java/android/os/FactoryTest.java
@@ -16,6 +16,8 @@
package android.os;
+import com.android.internal.os.RoSystemProperties;
+
/**
* Provides support for in-place factory test functions.
*
@@ -36,7 +38,7 @@
* or {@link #FACTORY_TEST_HIGH_LEVEL}.
*/
public static int getMode() {
- return SystemProperties.getInt("ro.factorytest", FACTORY_TEST_OFF);
+ return RoSystemProperties.FACTORYTEST;
}
/**
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index e47c238..6a751e8 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -16,7 +16,13 @@
package android.os;
+import android.util.Log;
+import android.util.MutableInt;
+
+import com.android.internal.annotations.GuardedBy;
+
import java.util.ArrayList;
+import java.util.HashMap;
/**
@@ -25,13 +31,45 @@
*
* {@hide}
*/
-public class SystemProperties
-{
+public class SystemProperties {
+ private static final String TAG = "SystemProperties";
+ private static final boolean TRACK_KEY_ACCESS = false;
+
public static final int PROP_NAME_MAX = 31;
public static final int PROP_VALUE_MAX = 91;
private static final ArrayList<Runnable> sChangeCallbacks = new ArrayList<Runnable>();
+ @GuardedBy("sRoReads")
+ private static final HashMap<String, MutableInt> sRoReads;
+ static {
+ if (TRACK_KEY_ACCESS) {
+ sRoReads = new HashMap<>();
+ } else {
+ sRoReads = null;
+ }
+ }
+
+ private static void onKeyAccess(String key) {
+ if (!TRACK_KEY_ACCESS) return;
+
+ if (key != null && key.startsWith("ro.")) {
+ synchronized (sRoReads) {
+ MutableInt numReads = sRoReads.getOrDefault(key, null);
+ if (numReads == null) {
+ numReads = new MutableInt(0);
+ sRoReads.put(key, numReads);
+ }
+ numReads.value++;
+ if (numReads.value > 3) {
+ Log.d(TAG, "Repeated read (count=" + numReads.value
+ + ") of a read-only system property '" + key + "'",
+ new Exception());
+ }
+ }
+ }
+ }
+
private static native String native_get(String key);
private static native String native_get(String key, String def);
private static native int native_get_int(String key, int def);
@@ -50,6 +88,7 @@
if (key.length() > PROP_NAME_MAX) {
throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
}
+ if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get(key);
}
@@ -62,6 +101,7 @@
if (key.length() > PROP_NAME_MAX) {
throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
}
+ if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get(key, def);
}
@@ -77,6 +117,7 @@
if (key.length() > PROP_NAME_MAX) {
throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
}
+ if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get_int(key, def);
}
@@ -92,6 +133,7 @@
if (key.length() > PROP_NAME_MAX) {
throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
}
+ if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get_long(key, def);
}
@@ -112,6 +154,7 @@
if (key.length() > PROP_NAME_MAX) {
throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
}
+ if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get_boolean(key, def);
}
@@ -128,6 +171,7 @@
throw new IllegalArgumentException("val.length > " +
PROP_VALUE_MAX);
}
+ if (TRACK_KEY_ACCESS) onKeyAccess(key);
native_set(key, val);
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index a4db940..ab462e4 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -42,6 +42,7 @@
import android.view.WindowManager.LayoutParams;
import com.android.internal.R;
+import com.android.internal.os.RoSystemProperties;
import java.io.IOException;
import java.lang.annotation.Retention;
@@ -761,7 +762,7 @@
* a single owner user. see @link {android.os.UserHandle#USER_OWNER}
*/
public static boolean isSplitSystemUser() {
- return SystemProperties.getBoolean("ro.fw.system_user_split", false);
+ return RoSystemProperties.FW_SYSTEM_USER_SPLIT;
}
/**
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 9252887..63b6db0 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -44,6 +44,7 @@
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.os.RoSystemProperties;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.Preconditions;
@@ -1167,8 +1168,7 @@
* false not encrypted and not encryptable
*/
public static boolean isEncryptable() {
- final String state = SystemProperties.get("ro.crypto.state", "unsupported");
- return !"unsupported".equalsIgnoreCase(state);
+ return RoSystemProperties.CRYPTO_ENCRYPTABLE;
}
/** {@hide}
@@ -1177,8 +1177,7 @@
* false not encrypted
*/
public static boolean isEncrypted() {
- final String state = SystemProperties.get("ro.crypto.state", "");
- return "encrypted".equalsIgnoreCase(state);
+ return RoSystemProperties.CRYPTO_ENCRYPTED;
}
/** {@hide}
@@ -1190,9 +1189,7 @@
if (!isEncrypted()) {
return false;
}
-
- final String status = SystemProperties.get("ro.crypto.type", "");
- return "file".equalsIgnoreCase(status);
+ return RoSystemProperties.CRYPTO_FILE_ENCRYPTED;
}
/** {@hide}
@@ -1204,8 +1201,7 @@
if (!isEncrypted()) {
return false;
}
- final String status = SystemProperties.get("ro.crypto.type", "");
- return "block".equalsIgnoreCase(status);
+ return RoSystemProperties.CRYPTO_BLOCK_ENCRYPTED;
}
/** {@hide}
diff --git a/core/java/com/android/internal/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java
new file mode 100644
index 0000000..80c55fb
--- /dev/null
+++ b/core/java/com/android/internal/os/RoSystemProperties.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.os.SystemProperties;
+
+/**
+ * This is a cache of various ro.* properties so that they can be read just once
+ * at class init time.
+ */
+public class RoSystemProperties {
+ public static final boolean DEBUGGABLE =
+ SystemProperties.getInt("ro.debuggable", 0) == 1;
+ public static final int FACTORYTEST =
+ SystemProperties.getInt("ro.factorytest", 0);
+
+ // ------ ro.config.* -------- //
+ public static final boolean CONFIG_LOW_RAM =
+ SystemProperties.getBoolean("ro.config.low_ram", false);
+
+ // ------ ro.fw.* ------------ //
+ public static final boolean FW_SYSTEM_USER_SPLIT =
+ SystemProperties.getBoolean("ro.fw.system_user_split", false);
+
+ // ------ ro.crypto.* -------- //
+ public static final String CRYPTO_STATE = SystemProperties.get("ro.crypto.state");
+ public static final String CRYPTO_TYPE = SystemProperties.get("ro.crypto.type");
+ // These are pseudo-properties
+ public static final boolean CRYPTO_ENCRYPTABLE =
+ !CRYPTO_STATE.isEmpty() && !"unsupported".equals(CRYPTO_STATE);
+ public static final boolean CRYPTO_ENCRYPTED =
+ "encrypted".equalsIgnoreCase(CRYPTO_STATE);
+ public static final boolean CRYPTO_FILE_ENCRYPTED =
+ "file".equalsIgnoreCase(CRYPTO_TYPE);
+ public static final boolean CRYPTO_BLOCK_ENCRYPTED =
+ "block".equalsIgnoreCase(CRYPTO_TYPE);
+}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index ec80303..763d392 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -24,6 +24,7 @@
import android.net.Credentials;
import android.net.LocalSocket;
+import android.os.FactoryTest;
import android.os.Process;
import android.os.SELinux;
import android.os.SystemProperties;
@@ -642,13 +643,10 @@
throws ZygoteSecurityException {
if (peer.getUid() == Process.SYSTEM_UID) {
- String factoryTest = SystemProperties.get("ro.factorytest");
- boolean uidRestricted;
-
/* In normal operation, SYSTEM_UID can only specify a restricted
* set of UIDs. In factory test mode, SYSTEM_UID may specify any uid.
*/
- uidRestricted = !(factoryTest.equals("1") || factoryTest.equals("2"));
+ boolean uidRestricted = FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF;
if (uidRestricted && args.uidSpecified && (args.uid < Process.SYSTEM_UID)) {
throw new ZygoteSecurityException(
@@ -678,7 +676,7 @@
* @param args non-null; zygote spawner args
*/
public static void applyDebuggerSystemProperty(Arguments args) {
- if ("1".equals(SystemProperties.get("ro.debuggable"))) {
+ if (RoSystemProperties.DEBUGGABLE) {
args.debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
}
}
diff --git a/core/jni/android_hardware_Radio.cpp b/core/jni/android_hardware_Radio.cpp
index ec6471e..d2ac2cc 100644
--- a/core/jni/android_hardware_Radio.cpp
+++ b/core/jni/android_hardware_Radio.cpp
@@ -23,7 +23,7 @@
#include "JNIHelp.h"
#include "core_jni_helpers.h"
#include <system/radio.h>
-#include <system/radio_metadata.h>
+#include <system/RadioMetadataWrapper.h>
#include <radio/RadioCallback.h>
#include <radio/Radio.h>
#include <utils/RefBase.h>
@@ -749,7 +749,7 @@
}
struct radio_program_info nInfo;
- radio_metadata_allocate(&nInfo.metadata, 0, 0);
+ RadioMetadataWrapper metadataWrapper(&nInfo.metadata);
jobject jInfo = NULL;
int jStatus;
@@ -767,7 +767,6 @@
if (jInfo != NULL) {
env->DeleteLocalRef(jInfo);
}
- radio_metadata_deallocate(nInfo.metadata);
return jStatus;
}
diff --git a/core/res/res/values-mcc334-mnc050/config.xml b/core/res/res/values-mcc334-mnc050/config.xml
index f6777d0..616a8e8 100644
--- a/core/res/res/values-mcc334-mnc050/config.xml
+++ b/core/res/res/values-mcc334-mnc050/config.xml
@@ -40,4 +40,8 @@
<item>Modem,modem.iusacellgsm.mx,,,iusacellgsm,iusacellgsm,,,,,334,050,1,DUN</item>
</string-array>
+ <!-- Do not translate. Defines the slots is Two Digit Number for dialing normally not USSD -->
+ <string-array translatable="false" name="config_twoDigitNumberPattern">
+ <item>"#9"</item>
+ </string-array>
</resources>
diff --git a/core/res/res/values-mcc334-mnc090/config.xml b/core/res/res/values-mcc334-mnc090/config.xml
new file mode 100644
index 0000000..1632a42
--- /dev/null
+++ b/core/res/res/values-mcc334-mnc090/config.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2017, 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 my 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+ for different hardware and product builds. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Do not translate. Defines the slots is Two Digit Number for dialing normally not USSD -->
+
+ <string-array translatable="false" name="config_twoDigitNumberPattern">
+ <item>"#9"</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index dbc4324..fbde9c0 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2267,6 +2267,13 @@
<!-- Whether to use voip audio mode for ims call -->
<bool name="config_use_voip_mode_for_ims">false</bool>
+ <!-- ImsService package name to bind to by default. If none is specified in an overlay, an
+ empty string is passed in -->
+ <string name="config_ims_package"/>
+
+ <!-- Flag specifying whether or not IMS will use the dynamic ImsResolver -->
+ <bool name="config_dynamic_bind_ims">true</bool>
+
<bool name="config_networkSamplingWakesDevice">true</bool>
<string-array translatable="false" name="config_cdma_home_system" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9db131b..bdac134 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -252,6 +252,8 @@
<java-symbol type="bool" name="config_enableBurnInProtection" />
<java-symbol type="bool" name="config_hotswapCapable" />
<java-symbol type="bool" name="config_mms_content_disposition_support" />
+ <java-symbol type="string" name="config_ims_package" />
+ <java-symbol type="bool" name="config_dynamic_bind_ims" />
<java-symbol type="bool" name="config_networkSamplingWakesDevice" />
<java-symbol type="bool" name="config_showMenuShortcutsWhenKeyboardPresent" />
<java-symbol type="bool" name="config_sip_wifi_only" />
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index d52967f..1f432de 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -103,10 +103,11 @@
<!-- Bluetooth settings -->
- <!-- Titles for Bluetooth Audio Codec selection preference. [CHAR LIMIT=40] -->
+ <!-- Titles for Bluetooth Audio Codec selection preference. [CHAR LIMIT=50] -->
<string-array name="bluetooth_a2dp_codec_titles">
<item>Use System Selection (Default)</item>
<item>SBC</item>
+ <item>AAC</item>
<item>aptX</item>
<item>aptX HD</item>
<item>LDAC</item>
@@ -119,18 +120,20 @@
<item>1</item>
<item>2</item>
<item>3</item>
+ <item>4</item>
</string-array>
- <!-- Summaries for Bluetooth Audio Codec selection preference. [CHAR LIMIT=40]-->
+ <!-- Summaries for Bluetooth Audio Codec selection preference. [CHAR LIMIT=50]-->
<string-array name="bluetooth_a2dp_codec_summaries" >
<item>Use System Selection (Default)</item>
<item>SBC</item>
+ <item>AAC</item>
<item>aptX</item>
<item>aptX HD</item>
<item>LDAC</item>
</string-array>
- <!-- Titles for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=40] -->
+ <!-- Titles for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=50] -->
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
<item>Use System Selection (Default)</item>
<item>44.1 kHz</item>
@@ -148,7 +151,7 @@
<item>8</item>
</string-array>
- <!-- Summaries for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=40]-->
+ <!-- Summaries for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=50]-->
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries" >
<item>Use System Selection (Default)</item>
<item>44.1 kHz</item>
@@ -157,7 +160,7 @@
<item>96.0 kHz</item>
</string-array>
- <!-- Titles for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=40] -->
+ <!-- Titles for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=50] -->
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
<item>Use System Selection (Default)</item>
<item>16 bits/sample</item>
@@ -173,7 +176,7 @@
<item>4</item>
</string-array>
- <!-- Summaries for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=40]-->
+ <!-- Summaries for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=50]-->
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries" >
<item>Use System Selection (Default)</item>
<item>16 bits/sample</item>
@@ -181,7 +184,7 @@
<item>32 bits/sample</item>
</string-array>
- <!-- Titles for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=40] -->
+ <!-- Titles for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=50] -->
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
<item>Use System Selection (Default)</item>
<item>Mono</item>
@@ -195,7 +198,7 @@
<item>2</item>
</string-array>
- <!-- Summaries for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=40]-->
+ <!-- Summaries for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=50]-->
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries" >
<item>Use System Selection (Default)</item>
<item>Mono</item>
diff --git a/preloaded-classes b/preloaded-classes
index 805a1c9..42f290e 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -2542,6 +2542,7 @@
com.android.internal.os.RuntimeInit$Arguments
com.android.internal.os.RuntimeInit$KillApplicationHandler
com.android.internal.os.RuntimeInit$LoggingHandler
+com.android.internal.os.RoSystemProperties
com.android.internal.os.SamplingProfilerIntegration
com.android.internal.os.SomeArgs
com.android.internal.os.Zygote
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 8c38ed6..803c2db 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -690,11 +690,6 @@
}
private LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker();
- @VisibleForTesting
- protected HandlerThread createHandlerThread() {
- return new HandlerThread("ConnectivityServiceThread");
- }
-
public ConnectivityService(Context context, INetworkManagementService netManager,
INetworkStatsService statsService, INetworkPolicyManager policyManager) {
this(context, netManager, statsService, policyManager, new IpConnectivityLog());
@@ -715,7 +710,7 @@
mDefaultMobileDataRequest = createInternetRequestForTransport(
NetworkCapabilities.TRANSPORT_CELLULAR, NetworkRequest.Type.BACKGROUND_REQUEST);
- mHandlerThread = createHandlerThread();
+ mHandlerThread = new HandlerThread("ConnectivityServiceThread");
mHandlerThread.start();
mHandler = new InternalHandler(mHandlerThread.getLooper());
mTrackerHandler = new NetworkStateTrackerHandler(mHandlerThread.getLooper());
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a2963b8..257ffb4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1381,6 +1381,7 @@
ParcelFileDescriptor mProfileFd;
int mSamplingInterval = 0;
boolean mAutoStopProfiler = false;
+ boolean mStreamingOutput = false;
int mProfileType = 0;
final ProcessMap<Pair<Long, String>> mMemWatchProcesses = new ProcessMap<>();
String mMemWatchDumpProcName;
@@ -6571,12 +6572,14 @@
ParcelFileDescriptor profileFd = null;
int samplingInterval = 0;
boolean profileAutoStop = false;
+ boolean profileStreamingOutput = false;
if (mProfileApp != null && mProfileApp.equals(processName)) {
mProfileProc = app;
profileFile = mProfileFile;
profileFd = mProfileFd;
samplingInterval = mSamplingInterval;
profileAutoStop = mAutoStopProfiler;
+ profileStreamingOutput = mStreamingOutput;
}
boolean enableTrackAllocation = false;
if (mTrackAllocationApp != null && mTrackAllocationApp.equals(processName)) {
@@ -6606,7 +6609,8 @@
profileFd = profileFd.dup();
}
ProfilerInfo profilerInfo = profileFile == null ? null
- : new ProfilerInfo(profileFile, profileFd, samplingInterval, profileAutoStop);
+ : new ProfilerInfo(profileFile, profileFd, samplingInterval, profileAutoStop,
+ profileStreamingOutput);
thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
app.instrumentationUiAutomationConnection, testMode,
@@ -12039,6 +12043,7 @@
mProfileFd = profilerInfo.profileFd;
mSamplingInterval = profilerInfo.samplingInterval;
mAutoStopProfiler = profilerInfo.autoStopProfiler;
+ mStreamingOutput = profilerInfo.streamingOutput;
mProfileType = 0;
}
}
@@ -14920,7 +14925,7 @@
pw.println(" mProfileApp=" + mProfileApp + " mProfileProc=" + mProfileProc);
pw.println(" mProfileFile=" + mProfileFile + " mProfileFd=" + mProfileFd);
pw.println(" mSamplingInterval=" + mSamplingInterval + " mAutoStopProfiler="
- + mAutoStopProfiler);
+ + mAutoStopProfiler + " mStreamingOutput=" + mStreamingOutput);
pw.println(" mProfileType=" + mProfileType);
}
}
@@ -21439,6 +21444,7 @@
mProfileFile = null;
mProfileType = 0;
mAutoStopProfiler = false;
+ mStreamingOutput = false;
mSamplingInterval = 0;
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index c6ab918..2262697 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1296,7 +1296,8 @@
}
profilerInfo = new ProfilerInfo(profileFile, profileFd,
- mService.mSamplingInterval, mService.mAutoStopProfiler);
+ mService.mSamplingInterval, mService.mAutoStopProfiler,
+ mService.mStreamingOutput);
}
}
}
diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index 23481dc..08a3332 100644
--- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -40,7 +40,9 @@
* pertaining to the current and any potential upstream network.
*
* Calling #start() registers two callbacks: one to track the system default
- * network and a second to specifically observe TYPE_MOBILE_DUN networks.
+ * network and a second to observe all networks. The latter is necessary
+ * while the expression of preferred upstreams remains a list of legacy
+ * connectivity types. In future, this can be revisited.
*
* The methods and data members of this class are only to be accessed and
* modified from the tethering master state machine thread. Any other
@@ -48,6 +50,10 @@
*
* TODO: Move upstream selection logic here.
*
+ * All callback methods are run on the same thread as the specified target
+ * state machine. This class does not require locking when accessed from this
+ * thread. Access from other threads is not advised.
+ *
* @hide
*/
public class UpstreamNetworkMonitor {
@@ -60,15 +66,20 @@
public static final int EVENT_ON_LINKPROPERTIES = 3;
public static final int EVENT_ON_LOST = 4;
+ private static final int LISTEN_ALL = 1;
+ private static final int TRACK_DEFAULT = 2;
+ private static final int MOBILE_REQUEST = 3;
+
private final Context mContext;
private final StateMachine mTarget;
private final int mWhat;
private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>();
private ConnectivityManager mCM;
+ private NetworkCallback mListenAllCallback;
private NetworkCallback mDefaultNetworkCallback;
- private NetworkCallback mDunTetheringCallback;
private NetworkCallback mMobileNetworkCallback;
private boolean mDunRequired;
+ private Network mCurrentDefault;
public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, int what) {
mContext = ctx;
@@ -85,16 +96,13 @@
public void start() {
stop();
- mDefaultNetworkCallback = new UpstreamNetworkCallback();
- cm().registerDefaultNetworkCallback(mDefaultNetworkCallback);
+ final NetworkRequest listenAllRequest = new NetworkRequest.Builder()
+ .clearCapabilities().build();
+ mListenAllCallback = new UpstreamNetworkCallback(LISTEN_ALL);
+ cm().registerNetworkCallback(listenAllRequest, mListenAllCallback);
- final NetworkRequest dunTetheringRequest = new NetworkRequest.Builder()
- .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
- .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
- .build();
- mDunTetheringCallback = new UpstreamNetworkCallback();
- cm().registerNetworkCallback(dunTetheringRequest, mDunTetheringCallback);
+ mDefaultNetworkCallback = new UpstreamNetworkCallback(TRACK_DEFAULT);
+ cm().registerDefaultNetworkCallback(mDefaultNetworkCallback);
}
public void stop() {
@@ -103,8 +111,8 @@
releaseCallback(mDefaultNetworkCallback);
mDefaultNetworkCallback = null;
- releaseCallback(mDunTetheringCallback);
- mDunTetheringCallback = null;
+ releaseCallback(mListenAllCallback);
+ mListenAllCallback = null;
mNetworkMap.clear();
}
@@ -140,7 +148,7 @@
// The existing default network and DUN callbacks will be notified.
// Therefore, to avoid duplicate notifications, we only register a no-op.
- mMobileNetworkCallback = new NetworkCallback();
+ mMobileNetworkCallback = new UpstreamNetworkCallback(MOBILE_REQUEST);
// TODO: Change the timeout from 0 (no onUnavailable callback) to some
// moderate callback timeout. This might be useful for updating some UI.
@@ -165,86 +173,117 @@
return (network != null) ? mNetworkMap.get(network) : null;
}
- private void handleAvailable(Network network) {
- if (VDBG) {
- Log.d(TAG, "EVENT_ON_AVAILABLE for " + network);
- }
+ private void handleAvailable(int callbackType, Network network) {
+ if (VDBG) Log.d(TAG, "EVENT_ON_AVAILABLE for " + network);
+
if (!mNetworkMap.containsKey(network)) {
mNetworkMap.put(network,
new NetworkState(null, null, null, network, null, null));
}
- final ConnectivityManager cm = cm();
-
- if (mDefaultNetworkCallback != null) {
+ // Always request whatever extra information we can, in case this
+ // was already up when start() was called, in which case we would
+ // not have been notified of any information that had not changed.
+ final NetworkCallback cb =
+ (callbackType == TRACK_DEFAULT) ? mDefaultNetworkCallback :
+ (callbackType == MOBILE_REQUEST) ? mMobileNetworkCallback : null;
+ if (cb != null) {
+ final ConnectivityManager cm = cm();
cm.requestNetworkCapabilities(mDefaultNetworkCallback);
cm.requestLinkProperties(mDefaultNetworkCallback);
}
- // Requesting updates for mDunTetheringCallback is not
- // necessary. Because it's a listen, it will already have
- // heard all NetworkCapabilities and LinkProperties updates
- // since UpstreamNetworkMonitor was started. Because we
- // start UpstreamNetworkMonitor before chooseUpstreamType()
- // is ever invoked (it can register a DUN request) this is
- // mostly safe. However, if a DUN network is already up for
- // some reason (unlikely, because DUN is restricted and,
- // unless the DUN network is shared with another APN, only
- // the system can request it and this is the only part of
- // the system that requests it) we won't know its
- // LinkProperties or NetworkCapabilities.
+ if (callbackType == TRACK_DEFAULT) {
+ mCurrentDefault = network;
+ }
+ // Requesting updates for mListenAllCallback is not currently possible
+ // because it's a "listen". Two possible solutions to getting updates
+ // about networks without waiting for a change (which might never come)
+ // are:
+ //
+ // [1] extend request{NetworkCapabilities,LinkProperties}() to
+ // take a Network argument and have ConnectivityService do
+ // what's required (if the network satisfies the request)
+ //
+ // [2] explicitly file a NetworkRequest for each connectivity type
+ // listed as a preferred upstream and wait for these callbacks
+ // to be notified (requires tracking many more callbacks).
+ //
+ // Until this is addressed, networks that exist prior to the "listen"
+ // registration and which do not subsequently change will not cause
+ // us to learn their NetworkCapabilities nor their LinkProperties.
+
+ // TODO: If sufficient information is available to select a more
+ // preferable upstream, do so now and notify the target.
notifyTarget(EVENT_ON_AVAILABLE, network);
}
private void handleNetCap(Network network, NetworkCapabilities newNc) {
- if (!mNetworkMap.containsKey(network)) {
- // Ignore updates for networks for which we have not yet
- // received onAvailable() - which should never happen -
- // or for which we have already received onLost().
+ final NetworkState prev = mNetworkMap.get(network);
+ if (prev == null || newNc.equals(prev.networkCapabilities)) {
+ // Ignore notifications about networks for which we have not yet
+ // received onAvailable() (should never happen) and any duplicate
+ // notifications (e.g. matching more than one of our callbacks).
return;
}
+
if (VDBG) {
Log.d(TAG, String.format("EVENT_ON_CAPABILITIES for %s: %s",
network, newNc));
}
- final NetworkState prev = mNetworkMap.get(network);
- mNetworkMap.put(network,
- new NetworkState(null, prev.linkProperties, newNc,
- network, null, null));
+ mNetworkMap.put(network, new NetworkState(
+ null, prev.linkProperties, newNc, network, null, null));
+ // TODO: If sufficient information is available to select a more
+ // preferable upstream, do so now and notify the target.
notifyTarget(EVENT_ON_CAPABILITIES, network);
}
private void handleLinkProp(Network network, LinkProperties newLp) {
- if (!mNetworkMap.containsKey(network)) {
- // Ignore updates for networks for which we have not yet
- // received onAvailable() - which should never happen -
- // or for which we have already received onLost().
+ final NetworkState prev = mNetworkMap.get(network);
+ if (prev == null || newLp.equals(prev.linkProperties)) {
+ // Ignore notifications about networks for which we have not yet
+ // received onAvailable() (should never happen) and any duplicate
+ // notifications (e.g. matching more than one of our callbacks).
return;
}
+
if (VDBG) {
Log.d(TAG, String.format("EVENT_ON_LINKPROPERTIES for %s: %s",
network, newLp));
}
- final NetworkState prev = mNetworkMap.get(network);
- mNetworkMap.put(network,
- new NetworkState(null, newLp, prev.networkCapabilities,
- network, null, null));
+ mNetworkMap.put(network, new NetworkState(
+ null, newLp, prev.networkCapabilities, network, null, null));
+ // TODO: If sufficient information is available to select a more
+ // preferable upstream, do so now and notify the target.
notifyTarget(EVENT_ON_LINKPROPERTIES, network);
}
- private void handleLost(Network network) {
- if (!mNetworkMap.containsKey(network)) {
- // Ignore updates for networks for which we have not yet
- // received onAvailable() - which should never happen -
- // or for which we have already received onLost().
+ private void handleLost(int callbackType, Network network) {
+ if (callbackType == TRACK_DEFAULT) {
+ mCurrentDefault = null;
+ // Receiving onLost() for a default network does not necessarily
+ // mean the network is gone. We wait for a separate notification
+ // on either the LISTEN_ALL or MOBILE_REQUEST callbacks before
+ // clearing all state.
return;
}
- if (VDBG) {
- Log.d(TAG, "EVENT_ON_LOST for " + network);
+
+ if (!mNetworkMap.containsKey(network)) {
+ // Ignore loss of networks about which we had not previously
+ // learned any information or for which we have already processed
+ // an onLost() notification.
+ return;
}
+
+ if (VDBG) Log.d(TAG, "EVENT_ON_LOST for " + network);
+
+ // TODO: If sufficient information is available to select a more
+ // preferable upstream, do so now and notify the target. Likewise,
+ // if the current upstream network is gone, notify the target of the
+ // fact that we now have no upstream at all.
notifyTarget(EVENT_ON_LOST, mNetworkMap.remove(network));
}
@@ -261,9 +300,15 @@
* tethering master state machine thread for subsequent processing.
*/
private class UpstreamNetworkCallback extends NetworkCallback {
+ private final int mCallbackType;
+
+ UpstreamNetworkCallback(int callbackType) {
+ mCallbackType = callbackType;
+ }
+
@Override
public void onAvailable(Network network) {
- mTarget.getHandler().post(() -> handleAvailable(network));
+ mTarget.getHandler().post(() -> handleAvailable(mCallbackType, network));
}
@Override
@@ -278,7 +323,7 @@
@Override
public void onLost(Network network) {
- mTarget.getHandler().post(() -> handleLost(network));
+ mTarget.getHandler().post(() -> handleLost(mCallbackType, network));
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 307dc1a..1554868 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -16,6 +16,7 @@
package android.telephony;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.SdkConstant;
@@ -39,8 +40,11 @@
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telephony.TelephonyHistogram;
+import android.telephony.ims.feature.ImsFeature;
import android.util.Log;
+import com.android.ims.internal.IImsServiceController;
+import com.android.ims.internal.IImsServiceFeatureListener;
import com.android.internal.telecom.ITelecomService;
import com.android.internal.telephony.CellNetworkScanResult;
import com.android.internal.telephony.IPhoneSubInfo;
@@ -53,6 +57,8 @@
import java.io.FileInputStream;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -4041,6 +4047,37 @@
}
}
+ /** @hide */
+ @IntDef({ImsFeature.EMERGENCY_MMTEL, ImsFeature.MMTEL, ImsFeature.RCS})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Feature {}
+
+ /**
+ * Returns the {@link IImsServiceController} that corresponds to the given slot Id and IMS
+ * feature or {@link null} if the service is not available. If an ImsServiceController is
+ * available, the {@link IImsServiceFeatureListener} callback is registered as a listener for
+ * feature updates.
+ * @param slotId The SIM slot that we are requesting the {@link IImsServiceController} for.
+ * @param feature The IMS Feature we are requesting, corresponding to {@link ImsFeature}.
+ * @param callback Listener that will send updates to ImsManager when there are updates to
+ * ImsServiceController.
+ * @return {@link IImsServiceController} interface for the feature specified or {@link null} if
+ * it is unavailable.
+ * @hide
+ */
+ public IImsServiceController getImsServiceControllerAndListen(int slotId, @Feature int feature,
+ IImsServiceFeatureListener callback) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.getImsServiceControllerAndListen(slotId, feature, callback);
+ }
+ } catch (RemoteException e) {
+ Rlog.e(TAG, "getImsServiceControllerAndListen, RemoteException: " + e.getMessage());
+ }
+ return null;
+ }
+
/**
* Set IMS registration state
*
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index a8eaf36..d406cb0 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -31,6 +31,8 @@
import android.telephony.ServiceState;
import android.telephony.TelephonyHistogram;
import android.telephony.VisualVoicemailSmsFilterSettings;
+import com.android.ims.internal.IImsServiceController;
+import com.android.ims.internal.IImsServiceFeatureListener;
import com.android.internal.telephony.CellNetworkScanResult;
import com.android.internal.telephony.OperatorInfo;
@@ -715,6 +717,14 @@
int getTetherApnRequired();
/**
+ * Get ImsServiceController binder from ImsResolver that corresponds to the subId and feature
+ * requested as well as registering the ImsServiceController for callbacks using the
+ * IImsServiceFeatureListener interface.
+ */
+ IImsServiceController getImsServiceControllerAndListen(int slotId, int feature,
+ IImsServiceFeatureListener callback);
+
+ /**
* Set the network selection mode to automatic.
*
* @param subId the id of the subscription to update.
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 46b6403..2d7a68f 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -68,7 +68,6 @@
import android.os.SystemClock;
import android.provider.Settings;
import android.test.AndroidTestCase;
-import android.test.FlakyTest;
import android.test.mock.MockContentResolver;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
@@ -154,49 +153,32 @@
}
/**
- * A subclass of HandlerThread that allows callers to wait for it to become idle. waitForIdle
- * will return immediately if the handler is already idle.
+ * Block until the given handler becomes idle, or until timeoutMs has passed.
*/
- private class IdleableHandlerThread extends HandlerThread {
- private IdleHandler mIdleHandler;
-
- public IdleableHandlerThread(String name) {
- super(name);
- }
-
- public void waitForIdle(int timeoutMs) {
- final ConditionVariable cv = new ConditionVariable();
- final MessageQueue queue = getLooper().getQueue();
-
+ private static void waitForIdleHandler(HandlerThread handler, int timeoutMs) {
+ final ConditionVariable cv = new ConditionVariable();
+ final MessageQueue queue = handler.getLooper().getQueue();
+ final IdleHandler idleHandler = () -> {
synchronized (queue) {
- if (queue.isIdle()) {
- return;
- }
-
- assertNull("BUG: only one idle handler allowed", mIdleHandler);
- mIdleHandler = new IdleHandler() {
- public boolean queueIdle() {
- synchronized (queue) {
- cv.open();
- mIdleHandler = null;
- return false; // Remove the handler.
- }
- }
- };
- queue.addIdleHandler(mIdleHandler);
+ cv.open();
+ return false; // Remove the idleHandler.
}
-
- if (!cv.block(timeoutMs)) {
- fail("HandlerThread " + getName() +
- " did not become idle after " + timeoutMs + " ms");
- queue.removeIdleHandler(mIdleHandler);
+ };
+ synchronized (queue) {
+ if (queue.isIdle()) {
+ return;
}
+ queue.addIdleHandler(idleHandler);
+ }
+ if (!cv.block(timeoutMs)) {
+ fail("HandlerThread " + handler.getName() +
+ " did not become idle after " + timeoutMs + " ms");
+ queue.removeIdleHandler(idleHandler);
}
}
- // Tests that IdleableHandlerThread works as expected.
@SmallTest
- public void testIdleableHandlerThread() {
+ public void testWaitForIdle() {
final int attempts = 50; // Causes the test to take about 200ms on bullhead-eng.
// Tests that waitForIdle returns immediately if the service is already idle.
@@ -220,9 +202,9 @@
}
}
- @SmallTest
- @FlakyTest(tolerance = 3)
- public void testNotWaitingForIdleCausesRaceConditions() {
+ // This test has an inherent race condition in it, and cannot be enabled for continuous testing
+ // or presubmit tests. It is kept for manual runs and documentation purposes.
+ public void verifyThatNotWaitingForIdleCausesRaceConditions() {
// Bring up a network that we can use to send messages to ConnectivityService.
ConditionVariable cv = waitForConnectivityBroadcasts(1);
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
@@ -249,7 +231,7 @@
private final WrappedNetworkMonitor mWrappedNetworkMonitor;
private final NetworkInfo mNetworkInfo;
private final NetworkCapabilities mNetworkCapabilities;
- private final IdleableHandlerThread mHandlerThread;
+ private final HandlerThread mHandlerThread;
private final ConditionVariable mDisconnected = new ConditionVariable();
private final ConditionVariable mNetworkStatusReceived = new ConditionVariable();
private final ConditionVariable mPreventReconnectReceived = new ConditionVariable();
@@ -281,7 +263,7 @@
default:
throw new UnsupportedOperationException("unimplemented network type");
}
- mHandlerThread = new IdleableHandlerThread("Mock-" + typeName);
+ mHandlerThread = new HandlerThread("Mock-" + typeName);
mHandlerThread.start();
mNetworkAgent = new NetworkAgent(mHandlerThread.getLooper(), mServiceContext,
"Mock-" + typeName, mNetworkInfo, mNetworkCapabilities,
@@ -321,7 +303,7 @@
}
public void waitForIdle(int timeoutMs) {
- mHandlerThread.waitForIdle(timeoutMs);
+ waitForIdleHandler(mHandlerThread, timeoutMs);
}
public void waitForIdle() {
@@ -648,11 +630,6 @@
}
@Override
- protected HandlerThread createHandlerThread() {
- return new IdleableHandlerThread("WrappedConnectivityService");
- }
-
- @Override
protected int getDefaultTcpRwnd() {
// Prevent wrapped ConnectivityService from trying to write to SystemProperties.
return 0;
@@ -710,7 +687,7 @@
}
public void waitForIdle(int timeoutMs) {
- ((IdleableHandlerThread) mHandlerThread).waitForIdle(timeoutMs);
+ waitForIdleHandler(mHandlerThread, timeoutMs);
}
public void waitForIdle() {
@@ -1135,7 +1112,7 @@
// Chosen to be much less than the linger timeout. This ensures that we can distinguish
// between a LOST callback that arrives immediately and a LOST callback that arrives after
// the linger timeout.
- private final static int TIMEOUT_MS = 50;
+ private final static int TIMEOUT_MS = 100;
private final LinkedBlockingQueue<CallbackInfo> mCallbacks = new LinkedBlockingQueue<>();
@@ -1487,8 +1464,8 @@
// Let linger run its course.
callback.assertNoCallback();
- callback.expectCallback(CallbackState.LOST, mCellNetworkAgent,
- TEST_LINGER_DELAY_MS /* timeoutMs */);
+ final int lingerTimeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
+ callback.expectCallback(CallbackState.LOST, mCellNetworkAgent, lingerTimeoutMs);
// Clean up.
mWiFiNetworkAgent.disconnect();
@@ -1977,7 +1954,9 @@
assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
// When lingering is complete, cell is still there but is now in the background.
- fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent, TEST_LINGER_DELAY_MS);
+ mService.waitForIdle();
+ int timeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
+ fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent, timeoutMs);
callback.assertNoCallback();
assertFalse(isForegroundNetwork(mCellNetworkAgent));
assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
@@ -2003,6 +1982,7 @@
// Disconnect wifi and check that cell is foreground again.
mWiFiNetworkAgent.disconnect();
+ mService.waitForIdle();
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
fgCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
fgCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
@@ -2339,14 +2319,14 @@
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI).build();
final TestNetworkCallback networkCallback = new TestNetworkCallback();
- mCm.requestNetwork(nr, networkCallback, 10);
+ final int timeoutMs = 150;
+ mCm.requestNetwork(nr, networkCallback, timeoutMs);
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false);
- networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent, timeoutMs);
// pass timeout and validate that UNAVAILABLE is not called
- sleepFor(15);
networkCallback.assertNoCallback();
}
@@ -2359,17 +2339,19 @@
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI).build();
final TestNetworkCallback networkCallback = new TestNetworkCallback();
- mCm.requestNetwork(nr, networkCallback, 500);
+ final int requestTimeoutMs = 100;
+ mCm.requestNetwork(nr, networkCallback, requestTimeoutMs);
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false);
- networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ final int assertTimeoutMs = 150;
+ networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent, assertTimeoutMs);
sleepFor(20);
mWiFiNetworkAgent.disconnect();
networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
// pass timeout and validate that UNAVAILABLE is not called
- sleepFor(600);
+ sleepFor(100);
networkCallback.assertNoCallback();
}
@@ -2383,7 +2365,8 @@
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI).build();
final TestNetworkCallback networkCallback = new TestNetworkCallback();
- mCm.requestNetwork(nr, networkCallback, 10);
+ final int timeoutMs = 10;
+ mCm.requestNetwork(nr, networkCallback, timeoutMs);
// pass timeout and validate that UNAVAILABLE is called
networkCallback.expectCallback(CallbackState.UNAVAILABLE, null);
@@ -2403,7 +2386,8 @@
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI).build();
final TestNetworkCallback networkCallback = new TestNetworkCallback();
- mCm.requestNetwork(nr, networkCallback, 10);
+ final int timeoutMs = 10;
+ mCm.requestNetwork(nr, networkCallback, timeoutMs);
// remove request
mCm.unregisterNetworkCallback(networkCallback);
@@ -2420,13 +2404,13 @@
networkCallback.assertNoCallback();
}
- public void assertEventuallyTrue(BooleanSupplier fn, long maxWaitingTimeMs) throws Exception {
+ public void assertEventuallyTrue(BooleanSupplier fn, long maxWaitingTimeMs) {
long start = SystemClock.elapsedRealtime();
while (SystemClock.elapsedRealtime() <= start + maxWaitingTimeMs) {
if (fn.getAsBoolean()) {
return;
}
- Thread.sleep(10);
+ sleepFor(15);
}
assertTrue(fn.getAsBoolean());
}
@@ -2594,7 +2578,9 @@
callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK);
// ... and that stopping it after that has no adverse effects.
- assertNull(mCm.getNetworkCapabilities(myNet));
+ // TODO: investigate assertEventuallyTrue is needed and waitForIdle() is not enough
+ final Network myNetAlias = myNet;
+ assertEventuallyTrue(() -> mCm.getNetworkCapabilities(myNetAlias) == null, 100);
ka.stop();
// Reconnect.
@@ -2838,11 +2824,11 @@
}
/* test utilities */
+ // TODO: eliminate all usages of sleepFor and replace by proper timeouts/waitForIdle.
static private void sleepFor(int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
}
-
}
}
diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
index 1e67769..b8c739b 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -91,12 +91,12 @@
}
@Test
- public void testListensForDunNetworks() throws Exception {
+ public void testListensForAllNetworks() throws Exception {
assertTrue(mCM.listening.isEmpty());
mUNM.start();
assertFalse(mCM.listening.isEmpty());
- assertTrue(mCM.isListeningForDun());
+ assertTrue(mCM.isListeningForAll());
mUNM.stop();
assertTrue(mCM.hasNoCallbacks());
@@ -197,9 +197,12 @@
legacyTypeMap.isEmpty();
}
- boolean isListeningForDun() {
+ boolean isListeningForAll() {
+ final NetworkCapabilities empty = new NetworkCapabilities();
+ empty.clearAll();
+
for (NetworkRequest req : listening.values()) {
- if (req.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)) {
+ if (req.networkCapabilities.equalRequestableCapabilities(empty)) {
return true;
}
}
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index e410a9c..0bfb955 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -142,7 +142,7 @@
private HashMap<String, String> mFields = new HashMap<String, String>();
private X509Certificate[] mCaCerts;
private PrivateKey mClientPrivateKey;
- private X509Certificate mClientCertificate;
+ private X509Certificate[] mClientCertificateChain;
private int mEapMethod = Eap.NONE;
private int mPhase2Method = Phase2.NONE;
@@ -161,9 +161,19 @@
for (String key : source.mFields.keySet()) {
mFields.put(key, source.mFields.get(key));
}
- mCaCerts = source.mCaCerts;
+ if (source.mCaCerts != null) {
+ mCaCerts = Arrays.copyOf(source.mCaCerts, source.mCaCerts.length);
+ } else {
+ mCaCerts = null;
+ }
mClientPrivateKey = source.mClientPrivateKey;
- mClientCertificate = source.mClientCertificate;
+ if (source.mClientCertificateChain != null) {
+ mClientCertificateChain = Arrays.copyOf(
+ source.mClientCertificateChain,
+ source.mClientCertificateChain.length);
+ } else {
+ mClientCertificateChain = null;
+ }
mEapMethod = source.mEapMethod;
mPhase2Method = source.mPhase2Method;
}
@@ -185,7 +195,7 @@
dest.writeInt(mPhase2Method);
ParcelUtil.writeCertificates(dest, mCaCerts);
ParcelUtil.writePrivateKey(dest, mClientPrivateKey);
- ParcelUtil.writeCertificate(dest, mClientCertificate);
+ ParcelUtil.writeCertificates(dest, mClientCertificateChain);
}
public static final Creator<WifiEnterpriseConfig> CREATOR =
@@ -204,7 +214,7 @@
enterpriseConfig.mPhase2Method = in.readInt();
enterpriseConfig.mCaCerts = ParcelUtil.readCertificates(in);
enterpriseConfig.mClientPrivateKey = ParcelUtil.readPrivateKey(in);
- enterpriseConfig.mClientCertificate = ParcelUtil.readCertificate(in);
+ enterpriseConfig.mClientCertificateChain = ParcelUtil.readCertificates(in);
return enterpriseConfig;
}
@@ -742,10 +752,54 @@
* @throws IllegalArgumentException for an invalid key or certificate.
*/
public void setClientKeyEntry(PrivateKey privateKey, X509Certificate clientCertificate) {
+ X509Certificate[] clientCertificates = null;
if (clientCertificate != null) {
- if (clientCertificate.getBasicConstraints() != -1) {
- throw new IllegalArgumentException("Cannot be a CA certificate");
+ clientCertificates = new X509Certificate[] {clientCertificate};
+ }
+ setClientKeyEntryWithCertificateChain(privateKey, clientCertificates);
+ }
+
+ /**
+ * Specify a private key and client certificate chain for client authorization.
+ *
+ * <p>A default name is automatically assigned to the key entry and used
+ * with this configuration. The framework takes care of installing the
+ * key entry when the config is saved and removing the key entry when
+ * the config is removed.
+
+ * @param privateKey
+ * @param clientCertificateChain
+ * @throws IllegalArgumentException for an invalid key or certificate.
+ */
+ public void setClientKeyEntryWithCertificateChain(PrivateKey privateKey,
+ X509Certificate[] clientCertificateChain) {
+ X509Certificate[] newCerts = null;
+ if (clientCertificateChain != null && clientCertificateChain.length > 0) {
+ // We validate that this is a well formed chain that starts
+ // with an end-certificate and is followed by CA certificates.
+ // We don't validate that each following certificate verifies
+ // the previous. https://en.wikipedia.org/wiki/Chain_of_trust
+ //
+ // Basic constraints is an X.509 extension type that defines
+ // whether a given certificate is allowed to sign additional
+ // certificates and what path length restrictions may exist.
+ // We use this to judge whether the certificate is an end
+ // certificate or a CA certificate.
+ // https://cryptography.io/en/latest/x509/reference/
+ if (clientCertificateChain[0].getBasicConstraints() != -1) {
+ throw new IllegalArgumentException(
+ "First certificate in the chain must be a client end certificate");
}
+
+ for (int i = 1; i < clientCertificateChain.length; i++) {
+ if (clientCertificateChain[i].getBasicConstraints() == -1) {
+ throw new IllegalArgumentException(
+ "All certificates following the first must be CA certificates");
+ }
+ }
+ newCerts = Arrays.copyOf(clientCertificateChain,
+ clientCertificateChain.length);
+
if (privateKey == null) {
throw new IllegalArgumentException("Client cert without a private key");
}
@@ -755,7 +809,7 @@
}
mClientPrivateKey = privateKey;
- mClientCertificate = clientCertificate;
+ mClientCertificateChain = newCerts;
}
/**
@@ -764,7 +818,24 @@
* @return X.509 client certificate
*/
public X509Certificate getClientCertificate() {
- return mClientCertificate;
+ if (mClientCertificateChain != null && mClientCertificateChain.length > 0) {
+ return mClientCertificateChain[0];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get the complete client certificate chain
+ *
+ * @return X.509 client certificates
+ */
+ @Nullable public X509Certificate[] getClientCertificateChain() {
+ if (mClientCertificateChain != null && mClientCertificateChain.length > 0) {
+ return mClientCertificateChain;
+ } else {
+ return null;
+ }
}
/**
@@ -772,7 +843,7 @@
*/
public void resetClientKeyEntry() {
mClientPrivateKey = null;
- mClientCertificate = null;
+ mClientCertificateChain = null;
}
/**
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 3b6e76f..ab725e2 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -120,11 +120,7 @@
public static final String PASSPOINT_ICON_RECEIVED_ACTION =
"android.net.wifi.PASSPOINT_ICON_RECEIVED";
/** @hide */
- public static final String EXTRA_PASSPOINT_ICON_BSSID = "bssid";
- /** @hide */
public static final String EXTRA_PASSPOINT_ICON_FILE = "file";
- /** @hide */
- public static final String EXTRA_PASSPOINT_ICON_DATA = "icon";
/**
* Broadcast intent action indicating that the a Passpoint release
@@ -159,6 +155,127 @@
public static final String EXTRA_PASSPOINT_WNM_DELAY = "delay";
/**
+ * Broadcast intent action indicating that a Passpoint provider icon has been received.
+ *
+ * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE
+ */
+ public static final String ACTION_PASSPOINT_ICON =
+ "android.net.wifi.action.PASSPOINT_ICON";
+ /**
+ * BSSID of the sender.
+ *
+ * Type: long
+ */
+ public static final String EXTRA_PASSPOINT_ICON_BSSID =
+ "android.net.wifi.extra.PASSPOINT_ICON_BSSID";
+ /**
+ * Filename of the icon.
+ *
+ * Type: String
+ */
+ public static final String EXTRA_PASSPOINT_ICON_FILENAME =
+ "android.net.wifi.extra.PASSPOINT_ICON_FILENAME";
+ /**
+ * Binary blob of the icon.
+ *
+ * Type: byte[]
+ */
+ public static final String EXTRA_PASSPOINT_ICON_DATA =
+ "android.net.wifi.extra.PASSPOINT_ICON_DATA";
+
+ /**
+ * Broadcast intent action indicating a Passpoint OSU Providers List element has been received.
+ *
+ * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE
+ */
+ public static final String ACTION_PASSPOINT_OSU_PROVIDERS_LIST =
+ "android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST";
+ /**
+ * BSSID of the sender.
+ *
+ * Type: long
+ */
+ public static final String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_BSSID =
+ "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_BSSID";
+ /**
+ * Raw data of OSU Providers List ANQP element. Refer to Section 4.8 of Hotspot 2.0 Release 2
+ * Technical Specification for the exact data format.
+ *
+ * Type: byte[]
+ */
+ public static final String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_DATA =
+ "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_DATA";
+
+ /**
+ * Broadcast intent action indicating that a Passpoint Deauth Imminent frame has been received.
+ *
+ * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE
+ */
+ public static final String ACTION_PASSPOINT_DEAUTH_IMMINENT =
+ "android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT";
+ /**
+ * The BSSID of the sender.
+ *
+ * Type: long
+ */
+ public static final String EXTRA_PASSPOINT_DEAUTH_IMMINENT_BSSID =
+ "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_BSSID";
+ /**
+ * Flag indicating failure at BSS (Basic Service Set) or ESS (Extended Service Set) level.
+ *
+ * Type: boolean
+ */
+ public static final String EXTRA_PASSPOINT_DEAUTH_IMMINENT_ESS =
+ "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_ESS";
+ /**
+ * Delay in seconds that a device shall wait before attempting re-association to the same BSS
+ * or ESS (as indicated by {@link #EXTRA_PASSPOINT_DEAUTH_IMMINENT_ESS}.
+ *
+ * Type: int
+ */
+ public static final String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY =
+ "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY";
+ /**
+ * URL that provides a webpage explaining the deauth reason.
+ *
+ * Type: String
+ */
+ public static final String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REASON_URL =
+ "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REASON_URL";
+
+ /**
+ * Broadcast intent action indicating a Passpoint subscription remediation frame has been
+ * received.
+ *
+ * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE
+ */
+ public static final String ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION =
+ "android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION";
+ /**
+ * The BSSID of the sender.
+ *
+ * Type: long
+ */
+ public static final String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID =
+ "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID";
+ /**
+ * The protocol supported by the subscription remediation server. The possible values are:
+ * 0 - OMA DM
+ * 1 - SOAP XML SPP
+ *
+ * Type: int
+ */
+ public static final String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD =
+ "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD";
+ /**
+ * URL of the subscription remediation server.
+ *
+ * Type: String
+ */
+ public static final String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL =
+ "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL";
+
+ /**
* Broadcast intent action indicating that Wi-Fi has been enabled, disabled,
* enabling, disabling, or unknown. One extra provides this state as an int.
* Another extra provides the previous state, if available.
@@ -898,10 +1015,10 @@
}
/**
- * Query for a Hotspot 2.0 release 2 OSU icon
+ * Query for a Hotspot 2.0 release 2 OSU icon file.
+ *
* @param bssid The BSSID of the AP
- * @param fileName Icon file name
- * @hide
+ * @param fileName File name of the icon to query
*/
public void queryPasspointIcon(long bssid, String fileName) {
try {
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index 643753a..ca4d121 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -18,9 +18,19 @@
import android.net.wifi.hotspot2.pps.Credential;
import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
import android.os.Parcel;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* Class representing Passpoint configuration. This contains configurations specified in
* PerProviderSubscription (PPS) Management Object (MO) tree.
@@ -28,13 +38,108 @@
* For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
* Release 2 Technical Specification.
*
- * Currently, only HomeSP and Credential subtrees are supported.
- *
* @hide
*/
public final class PasspointConfiguration implements Parcelable {
+ private static final String TAG = "PasspointConfiguration";
+
+ /**
+ * Number of bytes for certificate SHA-256 fingerprint byte array.
+ */
+ private static final int CERTIFICATE_SHA256_BYTES = 32;
+
+ /**
+ * Maximum bytes for URL string.
+ */
+ private static final int MAX_URL_BYTES = 1023;
+
+ /**
+ * Integer value used for indicating null value in the Parcel.
+ */
+ private static final int NULL_VALUE = -1;
+
public HomeSP homeSp = null;
public Credential credential = null;
+ public Policy policy = null;
+
+ /**
+ * Meta data for performing subscription update.
+ */
+ public UpdateParameter subscriptionUpdate = null;
+
+ /**
+ * List of HTTPS URL for retrieving trust root certificate and the corresponding SHA-256
+ * fingerprint of the certificate. The certificates are used for verifying AAA server's
+ * identity during EAP authentication.
+ */
+ public Map<String, byte[]> trustRootCertList = null;
+
+ /**
+ * Set by the subscription server, updated every time the configuration is updated by
+ * the subscription server.
+ *
+ * Use Integer.MIN_VALUE to indicate unset value.
+ */
+ public int updateIdentifier = Integer.MIN_VALUE;
+
+ /**
+ * The priority of the credential.
+ *
+ * Use Integer.MIN_VALUE to indicate unset value.
+ */
+ public int credentialPriority = Integer.MIN_VALUE;
+
+ /**
+ * The time this subscription is created. It is in the format of number
+ * of milliseconds since January 1, 1970, 00:00:00 GMT.
+ *
+ * Use Long.MIN_VALUE to indicate unset value.
+ */
+ public long subscriptionCreationTimeInMs = Long.MIN_VALUE;
+
+ /**
+ * The time this subscription will expire. It is in the format of number
+ * of milliseconds since January 1, 1970, 00:00:00 GMT.
+ *
+ * Use Long.MIN_VALUE to indicate unset value.
+ */
+ public long subscriptionExpirationTimeInMs = Long.MIN_VALUE;
+
+ /**
+ * The type of the subscription. This is defined by the provider and the value is provider
+ * specific.
+ */
+ public String subscriptionType = null;
+
+ /**
+ * The time period for usage statistics accumulation. A value of zero means that usage
+ * statistics are not accumulated on a periodic basis (e.g., a one-time limit for
+ * “pay as you go” - PAYG service). A non-zero value specifies the usage interval in minutes.
+ */
+ public long usageLimitUsageTimePeriodInMinutes = Long.MIN_VALUE;
+
+ /**
+ * The time at which usage statistic accumulation begins. It is in the format of number
+ * of milliseconds since January 1, 1970, 00:00:00 GMT.
+ *
+ * Use Long.MIN_VALUE to indicate unset value.
+ */
+ public long usageLimitStartTimeInMs = Long.MIN_VALUE;
+
+ /**
+ * The cumulative data limit in megabytes for the {@link #usageLimitUsageTimePeriodInMinutes}.
+ * A value of zero indicate unlimited data usage.
+ *
+ * Use Long.MIN_VALUE to indicate unset value.
+ */
+ public long usageLimitDataLimit = Long.MIN_VALUE;
+
+ /**
+ * The cumulative time limit in minutes for the {@link #usageLimitUsageTimePeriodInMinutes}.
+ * A value of zero indicate unlimited time usage.
+ */
+ public long usageLimitTimeLimitInMinutes = Long.MIN_VALUE;
+
/**
* Constructor for creating PasspointConfiguration with default values.
@@ -47,14 +152,34 @@
* @param source The source to copy from
*/
public PasspointConfiguration(PasspointConfiguration source) {
- if (source != null) {
- if (source.homeSp != null) {
- homeSp = new HomeSP(source.homeSp);
- }
- if (source.credential != null) {
- credential = new Credential(source.credential);
- }
+ if (source == null) {
+ return;
}
+
+ if (source.homeSp != null) {
+ homeSp = new HomeSP(source.homeSp);
+ }
+ if (source.credential != null) {
+ credential = new Credential(source.credential);
+ }
+ if (source.policy != null) {
+ policy = new Policy(source.policy);
+ }
+ if (source.trustRootCertList != null) {
+ trustRootCertList = Collections.unmodifiableMap(source.trustRootCertList);
+ }
+ if (source.subscriptionUpdate != null) {
+ subscriptionUpdate = new UpdateParameter(source.subscriptionUpdate);
+ }
+ updateIdentifier = source.updateIdentifier;
+ credentialPriority = source.credentialPriority;
+ subscriptionCreationTimeInMs = source.subscriptionCreationTimeInMs;
+ subscriptionExpirationTimeInMs = source.subscriptionExpirationTimeInMs;
+ subscriptionType = source.subscriptionType;
+ usageLimitDataLimit = source.usageLimitDataLimit;
+ usageLimitStartTimeInMs = source.usageLimitStartTimeInMs;
+ usageLimitTimeLimitInMinutes = source.usageLimitTimeLimitInMinutes;
+ usageLimitUsageTimePeriodInMinutes = source.usageLimitUsageTimePeriodInMinutes;
}
@Override
@@ -66,6 +191,18 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(homeSp, flags);
dest.writeParcelable(credential, flags);
+ dest.writeParcelable(policy, flags);
+ dest.writeParcelable(subscriptionUpdate, flags);
+ writeTrustRootCerts(dest, trustRootCertList);
+ dest.writeInt(updateIdentifier);
+ dest.writeInt(credentialPriority);
+ dest.writeLong(subscriptionCreationTimeInMs);
+ dest.writeLong(subscriptionExpirationTimeInMs);
+ dest.writeString(subscriptionType);
+ dest.writeLong(usageLimitUsageTimePeriodInMinutes);
+ dest.writeLong(usageLimitStartTimeInMs);
+ dest.writeLong(usageLimitDataLimit);
+ dest.writeLong(usageLimitTimeLimitInMinutes);
}
@Override
@@ -77,9 +214,22 @@
return false;
}
PasspointConfiguration that = (PasspointConfiguration) thatObject;
- return (homeSp == null ? that.homeSp == null : homeSp.equals(that.homeSp)) &&
- (credential == null ? that.credential == null :
- credential.equals(that.credential));
+ return (homeSp == null ? that.homeSp == null : homeSp.equals(that.homeSp))
+ && (credential == null ? that.credential == null
+ : credential.equals(that.credential))
+ && (policy == null) ? that.policy == null : policy.equals(that.policy)
+ && (subscriptionUpdate == null) ? that.subscriptionUpdate == null
+ : subscriptionUpdate.equals(that.subscriptionUpdate)
+ && isTrustRootCertListEquals(trustRootCertList, that.trustRootCertList)
+ && updateIdentifier == that.updateIdentifier
+ && credentialPriority == that.credentialPriority
+ && subscriptionCreationTimeInMs == that.subscriptionCreationTimeInMs
+ && subscriptionExpirationTimeInMs == that.subscriptionExpirationTimeInMs
+ && TextUtils.equals(subscriptionType, that.subscriptionType)
+ && usageLimitUsageTimePeriodInMinutes == that.usageLimitUsageTimePeriodInMinutes
+ && usageLimitStartTimeInMs == that.usageLimitStartTimeInMs
+ && usageLimitDataLimit == that.usageLimitDataLimit
+ && usageLimitTimeLimitInMinutes == that .usageLimitTimeLimitInMinutes;
}
/**
@@ -94,6 +244,37 @@
if (credential == null || !credential.validate()) {
return false;
}
+ if (policy != null && !policy.validate()) {
+ return false;
+ }
+ if (subscriptionUpdate != null && !subscriptionUpdate.validate()) {
+ return false;
+ }
+ if (trustRootCertList != null) {
+ for (Map.Entry<String, byte[]> entry : trustRootCertList.entrySet()) {
+ String url = entry.getKey();
+ byte[] certFingerprint = entry.getValue();
+ if (TextUtils.isEmpty(url)) {
+ Log.d(TAG, "Empty URL");
+ return false;
+ }
+ if (url.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) {
+ Log.d(TAG, "URL bytes exceeded the max: "
+ + url.getBytes(StandardCharsets.UTF_8).length);
+ return false;
+ }
+
+ if (certFingerprint == null) {
+ Log.d(TAG, "Fingerprint not specified");
+ return false;
+ }
+ if (certFingerprint.length != CERTIFICATE_SHA256_BYTES) {
+ Log.d(TAG, "Incorrect size of trust root certificate SHA-256 fingerprint: "
+ + certFingerprint.length);
+ return false;
+ }
+ }
+ }
return true;
}
@@ -104,11 +285,88 @@
PasspointConfiguration config = new PasspointConfiguration();
config.homeSp = in.readParcelable(null);
config.credential = in.readParcelable(null);
+ config.policy = in.readParcelable(null);
+ config.subscriptionUpdate = in.readParcelable(null);
+ config.trustRootCertList = readTrustRootCerts(in);
+ config.updateIdentifier = in.readInt();
+ config.credentialPriority = in.readInt();
+ config.subscriptionCreationTimeInMs = in.readLong();
+ config.subscriptionExpirationTimeInMs = in.readLong();
+ config.subscriptionType = in.readString();
+ config.usageLimitUsageTimePeriodInMinutes = in.readLong();
+ config.usageLimitStartTimeInMs = in.readLong();
+ config.usageLimitDataLimit = in.readLong();
+ config.usageLimitTimeLimitInMinutes = in.readLong();
return config;
}
+
@Override
public PasspointConfiguration[] newArray(int size) {
return new PasspointConfiguration[size];
}
+
+ /**
+ * Helper function for reading trust root certificate info list from a Parcel.
+ *
+ * @param in The Parcel to read from
+ * @return The list of trust root certificate URL with the corresponding certificate
+ * fingerprint
+ */
+ private Map<String, byte[]> readTrustRootCerts(Parcel in) {
+ int size = in.readInt();
+ if (size == NULL_VALUE) {
+ return null;
+ }
+ Map<String, byte[]> trustRootCerts = new HashMap<>(size);
+ for (int i = 0; i < size; i++) {
+ String key = in.readString();
+ byte[] value = in.createByteArray();
+ trustRootCerts.put(key, value);
+ }
+ return trustRootCerts;
+ }
};
+
+ /**
+ * Helper function for writing trust root certificate information list.
+ *
+ * @param dest The Parcel to write to
+ * @param trustRootCerts The list of trust root certificate URL with the corresponding
+ * certificate fingerprint
+ */
+ private static void writeTrustRootCerts(Parcel dest, Map<String, byte[]> trustRootCerts) {
+ if (trustRootCerts == null) {
+ dest.writeInt(NULL_VALUE);
+ return;
+ }
+ dest.writeInt(trustRootCerts.size());
+ for (Map.Entry<String, byte[]> entry : trustRootCerts.entrySet()) {
+ dest.writeString(entry.getKey());
+ dest.writeByteArray(entry.getValue());
+ }
+ }
+
+ /**
+ * Helper function for comparing two trust root certificate list. Cannot use Map#equals
+ * method since the value type (byte[]) doesn't override equals method.
+ *
+ * @param list1 The first trust root certificate list
+ * @param list2 The second trust root certificate list
+ * @return true if the two list are equal
+ */
+ private static boolean isTrustRootCertListEquals(Map<String, byte[]> list1,
+ Map<String, byte[]> list2) {
+ if (list1 == null || list2 == null) {
+ return list1 == list2;
+ }
+ if (list1.size() != list2.size()) {
+ return false;
+ }
+ for (Map.Entry<String, byte[]> entry : list1.entrySet()) {
+ if (!Arrays.equals(entry.getValue(), list2.get(entry.getKey()))) {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
index 98fd0f3..22b0f97 100644
--- a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
+++ b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
@@ -19,6 +19,8 @@
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.hotspot2.pps.Credential;
import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
@@ -131,6 +133,20 @@
private static final String NODE_PER_PROVIDER_SUBSCRIPTION = "PerProviderSubscription";
/**
+ * Fields under PerProviderSubscription.
+ */
+ private static final String NODE_UPDATE_IDENTIFIER = "UpdateIdentifier";
+ private static final String NODE_AAA_SERVER_TRUST_ROOT = "AAAServerTrustRoot";
+ private static final String NODE_SUBSCRIPTION_UPDATE = "SubscriptionUpdate";
+ private static final String NODE_SUBSCRIPTION_PARAMETER = "SubscriptionParameter";
+ private static final String NODE_TYPE_OF_SUBSCRIPTION = "TypeOfSubscription";
+ private static final String NODE_USAGE_LIMITS = "UsageLimits";
+ private static final String NODE_DATA_LIMIT = "DataLimit";
+ private static final String NODE_START_DATE = "StartDate";
+ private static final String NODE_TIME_LIMIT = "TimeLimit";
+ private static final String NODE_USAGE_TIME_PERIOD = "UsageTimePeriod";
+ private static final String NODE_CREDENTIAL_PRIORITY = "CredentialPriority";
+ /**
* Fields under HomeSP subtree.
*/
private static final String NODE_HOMESP = "HomeSP";
@@ -168,13 +184,40 @@
private static final String NODE_INNER_METHOD = "InnerMethod";
private static final String NODE_DIGITAL_CERTIFICATE = "DigitalCertificate";
private static final String NODE_CERTIFICATE_TYPE = "CertificateType";
- private static final String NODE_CERT_SHA256_FINGERPRINT = "CertSHA256FingerPrint";
+ private static final String NODE_CERT_SHA256_FINGERPRINT = "CertSHA256Fingerprint";
private static final String NODE_REALM = "Realm";
private static final String NODE_SIM = "SIM";
private static final String NODE_SIM_IMSI = "IMSI";
private static final String NODE_CHECK_AAA_SERVER_CERT_STATUS = "CheckAAAServerCertStatus";
/**
+ * Fields under Policy subtree.
+ */
+ private static final String NODE_POLICY = "Policy";
+ private static final String NODE_PREFERRED_ROAMING_PARTNER_LIST =
+ "PreferredRoamingPartnerList";
+ private static final String NODE_FQDN_MATCH = "FQDN_Match";
+ private static final String NODE_PRIORITY = "Priority";
+ private static final String NODE_COUNTRY = "Country";
+ private static final String NODE_MIN_BACKHAUL_THRESHOLD = "MinBackhaulThreshold";
+ private static final String NODE_NETWORK_TYPE = "NetworkType";
+ private static final String NODE_DOWNLINK_BANDWIDTH = "DLBandwidth";
+ private static final String NODE_UPLINK_BANDWIDTH = "ULBandwidth";
+ private static final String NODE_POLICY_UPDATE = "PolicyUpdate";
+ private static final String NODE_UPDATE_INTERVAL = "UpdateInterval";
+ private static final String NODE_UPDATE_METHOD = "UpdateMethod";
+ private static final String NODE_RESTRICTION = "Restriction";
+ private static final String NODE_URI = "URI";
+ private static final String NODE_TRUST_ROOT = "TrustRoot";
+ private static final String NODE_CERT_URL = "CertURL";
+ private static final String NODE_SP_EXCLUSION_LIST = "SPExclusionList";
+ private static final String NODE_REQUIRED_PROTO_PORT_TUPLE = "RequiredProtoPortTuple";
+ private static final String NODE_IP_PROTOCOL = "IPProtocol";
+ private static final String NODE_PORT_NUMBER = "PortNumber";
+ private static final String NODE_MAXIMUM_BSS_LOAD_VALUE = "MaximumBSSLoadValue";
+ private static final String NODE_OTHER = "Other";
+
+ /**
* URN (Unique Resource Name) for PerProviderSubscription Management Object Tree.
*/
private static final String PPS_MO_URN =
@@ -349,6 +392,10 @@
* ...
* </RTPProperties>
* <Node>
+ * <NodeName>UpdateIdentifier</NodeName>
+ * <Value>...</Value>
+ * </Node>
+ * <Node>
* ...
* </Node>
* </Node>
@@ -361,11 +408,12 @@
throws ParsingException {
PasspointConfiguration config = null;
String nodeName = null;
+ int updateIdentifier = Integer.MIN_VALUE;
for (XMLNode child : node.getChildren()) {
switch (child.getTag()) {
case TAG_NODE_NAME:
if (nodeName != null) {
- throw new ParsingException("Duplicant NodeName: " + child.getText());
+ throw new ParsingException("Duplicate NodeName: " + child.getText());
}
nodeName = child.getText();
if (!TextUtils.equals(nodeName, NODE_PER_PROVIDER_SUBSCRIPTION)) {
@@ -373,13 +421,22 @@
}
break;
case TAG_NODE:
- // Only one PerProviderSubscription instance is expected and allowed.
- if (config != null) {
- throw new ParsingException("Multiple PPS instance");
+ // A node can be either an UpdateIdentifier node or a PerProviderSubscription
+ // instance node. Flatten out the XML tree first by converting it to a PPS
+ // tree to reduce the complexity of the parsing code.
+ PPSNode ppsNodeRoot = buildPpsNode(child);
+ if (TextUtils.equals(ppsNodeRoot.getName(), NODE_UPDATE_IDENTIFIER)) {
+ if (updateIdentifier != Integer.MIN_VALUE) {
+ throw new ParsingException("Multiple node for UpdateIdentifier");
+ }
+ updateIdentifier = parseInteger(getPpsNodeValue(ppsNodeRoot));
+ } else {
+ // Only one PerProviderSubscription instance is expected and allowed.
+ if (config != null) {
+ throw new ParsingException("Multiple PPS instance");
+ }
+ config = parsePpsInstance(ppsNodeRoot);
}
- // Convert the XML tree to a PPS tree.
- PPSNode ppsInstanceRoot = buildPpsNode(child);
- config = parsePpsInstance(ppsInstanceRoot);
break;
case TAG_RT_PROPERTIES:
// Parse and verify URN stored in the RT (Run Time) Properties.
@@ -392,6 +449,9 @@
throw new ParsingException("Unknown tag under PPS node: " + child.getTag());
}
}
+ if (config != null && updateIdentifier != Integer.MIN_VALUE) {
+ config.updateIdentifier = updateIdentifier;
+ }
return config;
}
@@ -551,6 +611,21 @@
case NODE_CREDENTIAL:
config.credential = parseCredential(child);
break;
+ case NODE_POLICY:
+ config.policy = parsePolicy(child);
+ break;
+ case NODE_AAA_SERVER_TRUST_ROOT:
+ config.trustRootCertList = parseAAAServerTrustRootList(child);
+ break;
+ case NODE_SUBSCRIPTION_UPDATE:
+ config.subscriptionUpdate = parseUpdateParameter(child);
+ break;
+ case NODE_SUBSCRIPTION_PARAMETER:
+ parseSubscriptionParameter(child, config);
+ break;
+ case NODE_CREDENTIAL_PRIORITY:
+ config.credentialPriority = parseInteger(getPpsNodeValue(child));
+ break;
default:
throw new ParsingException("Unknown node: " + child.getName());
}
@@ -616,11 +691,7 @@
String[] oiStrArray = oiStr.split(",");
long[] oiArray = new long[oiStrArray.length];
for (int i = 0; i < oiStrArray.length; i++) {
- try {
- oiArray[i] = Long.parseLong(oiStrArray[i], 16);
- } catch (NumberFormatException e) {
- throw new ParsingException("Invalid OI: " + oiStrArray[i]);
- }
+ oiArray[i] = parseLong(oiStrArray[i], 16);
}
return oiArray;
}
@@ -671,11 +742,7 @@
ssid = getPpsNodeValue(child);
break;
case NODE_HESSID:
- try {
- hessid = Long.parseLong(getPpsNodeValue(child), 16);
- } catch (NumberFormatException e) {
- throw new ParsingException("Invalid HESSID: " + getPpsNodeValue(child));
- }
+ hessid = parseLong(getPpsNodeValue(child), 16);
break;
default:
throw new ParsingException("Unknown node under NetworkID instance: " +
@@ -999,6 +1066,503 @@
}
/**
+ * Parse configurations under PerProviderSubscription/Policy subtree.
+ *
+ * @param node PPSNode representing the root of the PerProviderSubscription/Policy subtree
+ * @return {@link Policy}
+ * @throws ParsingException
+ */
+ private static Policy parsePolicy(PPSNode node) throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for Policy");
+ }
+
+ Policy policy = new Policy();
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_PREFERRED_ROAMING_PARTNER_LIST:
+ policy.preferredRoamingPartnerList = parsePreferredRoamingPartnerList(child);
+ break;
+ case NODE_MIN_BACKHAUL_THRESHOLD:
+ parseMinBackhaulThreshold(child, policy);
+ break;
+ case NODE_POLICY_UPDATE:
+ policy.policyUpdate = parseUpdateParameter(child);
+ break;
+ case NODE_SP_EXCLUSION_LIST:
+ policy.excludedSsidList = parseSpExclusionList(child);
+ break;
+ case NODE_REQUIRED_PROTO_PORT_TUPLE:
+ policy.requiredProtoPortMap = parseRequiredProtoPortTuple(child);
+ break;
+ case NODE_MAXIMUM_BSS_LOAD_VALUE:
+ policy.maximumBssLoadValue = parseInteger(getPpsNodeValue(child));
+ break;
+ default:
+ throw new ParsingException("Unknown node under Policy: " + child.getName());
+ }
+ }
+ return policy;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/PreferredRoamingPartnerList
+ * subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/PreferredRoamingPartnerList subtree
+ * @return List of {@link Policy#RoamingPartner}
+ * @throws ParsingException
+ */
+ private static List<Policy.RoamingPartner> parsePreferredRoamingPartnerList(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for PreferredRoamingPartnerList");
+ }
+ List<Policy.RoamingPartner> partnerList = new ArrayList<>();
+ for (PPSNode child : node.getChildren()) {
+ partnerList.add(parsePreferredRoamingPartner(child));
+ }
+ return partnerList;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/PreferredRoamingPartnerList/<X+>
+ * subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/PreferredRoamingPartnerList/<X+> subtree
+ * @return {@link Policy#RoamingPartner}
+ * @throws ParsingException
+ */
+ private static Policy.RoamingPartner parsePreferredRoamingPartner(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for PreferredRoamingPartner "
+ + "instance");
+ }
+
+ Policy.RoamingPartner roamingPartner = new Policy.RoamingPartner();
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_FQDN_MATCH:
+ // FQDN_Match field is in the format of "[FQDN],[MatchInfo]", where [MatchInfo]
+ // is either "exactMatch" for exact match of FQDN or "includeSubdomains" for
+ // matching all FQDNs with the same sub-domain.
+ String fqdnMatch = getPpsNodeValue(child);
+ String[] fqdnMatchArray = fqdnMatch.split(",");
+ if (fqdnMatchArray.length != 2) {
+ throw new ParsingException("Invalid FQDN_Match: " + fqdnMatch);
+ }
+ roamingPartner.fqdn = fqdnMatchArray[0];
+ if (TextUtils.equals(fqdnMatchArray[1], "exactMatch")) {
+ roamingPartner.fqdnExactMatch = true;
+ } else if (TextUtils.equals(fqdnMatchArray[1], "includeSubdomains")) {
+ roamingPartner.fqdnExactMatch = false;
+ } else {
+ throw new ParsingException("Invalid FQDN_Match: " + fqdnMatch);
+ }
+ break;
+ case NODE_PRIORITY:
+ roamingPartner.priority = parseInteger(getPpsNodeValue(child));
+ break;
+ case NODE_COUNTRY:
+ roamingPartner.countries = getPpsNodeValue(child);
+ break;
+ default:
+ throw new ParsingException("Unknown node under PreferredRoamingPartnerList "
+ + "instance " + child.getName());
+ }
+ }
+ return roamingPartner;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/MinBackhaulThreshold subtree
+ * into the given policy.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/MinBackhaulThreshold subtree
+ * @param policy The policy to store the MinBackhualThreshold configuration
+ * @throws ParsingException
+ */
+ private static void parseMinBackhaulThreshold(PPSNode node, Policy policy)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for MinBackhaulThreshold");
+ }
+ for (PPSNode child : node.getChildren()) {
+ parseMinBackhaulThresholdInstance(child, policy);
+ }
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/MinBackhaulThreshold/<X+> subtree
+ * into the given policy.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/MinBackhaulThreshold/<X+> subtree
+ * @param policy The policy to store the MinBackhaulThreshold configuration
+ * @throws ParsingException
+ */
+ private static void parseMinBackhaulThresholdInstance(PPSNode node, Policy policy)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for MinBackhaulThreshold instance");
+ }
+ String networkType = null;
+ long downlinkBandwidth = Long.MIN_VALUE;
+ long uplinkBandwidth = Long.MIN_VALUE;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_NETWORK_TYPE:
+ networkType = getPpsNodeValue(child);
+ break;
+ case NODE_DOWNLINK_BANDWIDTH:
+ downlinkBandwidth = parseLong(getPpsNodeValue(child), 10);
+ break;
+ case NODE_UPLINK_BANDWIDTH:
+ uplinkBandwidth = parseLong(getPpsNodeValue(child), 10);
+ break;
+ default:
+ throw new ParsingException("Unknown node under MinBackhaulThreshold instance "
+ + child.getName());
+ }
+ }
+ if (networkType == null) {
+ throw new ParsingException("Missing NetworkType field");
+ }
+
+ if (TextUtils.equals(networkType, "home")) {
+ policy.minHomeDownlinkBandwidth = downlinkBandwidth;
+ policy.minHomeUplinkBandwidth = uplinkBandwidth;
+ } else if (TextUtils.equals(networkType, "roaming")) {
+ policy.minRoamingDownlinkBandwidth = downlinkBandwidth;
+ policy.minRoamingUplinkBandwidth = uplinkBandwidth;
+ } else {
+ throw new ParsingException("Invalid network type: " + networkType);
+ }
+ }
+
+ /**
+ * Parse update parameters. This contained configurations from either
+ * PerProviderSubscription/Policy/PolicyUpdate or PerProviderSubscription/SubscriptionUpdate
+ * subtree.
+ *
+ * @param node PPSNode representing the root of the PerProviderSubscription/Policy/PolicyUpdate
+ * or PerProviderSubscription/SubscriptionUpdate subtree
+ * @return {@link UpdateParameter}
+ * @throws ParsingException
+ */
+ private static UpdateParameter parseUpdateParameter(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for Update Parameters");
+ }
+
+ UpdateParameter updateParam = new UpdateParameter();
+ for (PPSNode child : node.getChildren()) {
+ switch(child.getName()) {
+ case NODE_UPDATE_INTERVAL:
+ updateParam.updateIntervalInMinutes = parseLong(getPpsNodeValue(child), 10);
+ break;
+ case NODE_UPDATE_METHOD:
+ updateParam.updateMethod = getPpsNodeValue(child);
+ break;
+ case NODE_RESTRICTION:
+ updateParam.restriction = getPpsNodeValue(child);
+ break;
+ case NODE_URI:
+ updateParam.serverUri = getPpsNodeValue(child);
+ break;
+ case NODE_USERNAME_PASSWORD:
+ Pair<String, String> usernamePassword = parseUpdateUserCredential(child);
+ updateParam.username = usernamePassword.first;
+ updateParam.base64EncodedPassword = usernamePassword.second;
+ break;
+ case NODE_TRUST_ROOT:
+ Pair<String, byte[]> trustRoot = parseTrustRoot(child);
+ updateParam.trustRootCertUrl = trustRoot.first;
+ updateParam.trustRootCertSha256Fingerprint = trustRoot.second;
+ break;
+ case NODE_OTHER:
+ Log.d(TAG, "Ignore unsupported paramter: " + child.getName());
+ break;
+ default:
+ throw new ParsingException("Unknown node under Update Parameters: "
+ + child.getName());
+ }
+ }
+ return updateParam;
+ }
+
+ /**
+ * Parse username and password parameters associated with policy or subscription update.
+ * This contained configurations under either
+ * PerProviderSubscription/Policy/PolicyUpdate/UsernamePassword or
+ * PerProviderSubscription/SubscriptionUpdate/UsernamePassword subtree.
+ *
+ * @param node PPSNode representing the root of the UsernamePassword subtree
+ * @return Pair of username and password
+ * @throws ParsingException
+ */
+ private static Pair<String, String> parseUpdateUserCredential(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for UsernamePassword");
+ }
+
+ String username = null;
+ String password = null;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_USERNAME:
+ username = getPpsNodeValue(child);
+ break;
+ case NODE_PASSWORD:
+ password = getPpsNodeValue(child);
+ break;
+ default:
+ throw new ParsingException("Unknown node under UsernamePassword: "
+ + child.getName());
+ }
+ }
+ return Pair.create(username, password);
+ }
+
+ /**
+ * Parse the trust root parameters associated with policy update, subscription update, or AAA
+ * server trust root.
+ *
+ * This contained configurations under either
+ * PerProviderSubscription/Policy/PolicyUpdate/TrustRoot or
+ * PerProviderSubscription/SubscriptionUpdate/TrustRoot or
+ * PerProviderSubscription/AAAServerTrustRoot/<X+> subtree.
+ *
+ * @param node PPSNode representing the root of the TrustRoot subtree
+ * @return Pair of Certificate URL and fingerprint
+ * @throws ParsingException
+ */
+ private static Pair<String, byte[]> parseTrustRoot(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for TrustRoot");
+ }
+
+ String certUrl = null;
+ byte[] certFingerprint = null;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_CERT_URL:
+ certUrl = getPpsNodeValue(child);
+ break;
+ case NODE_CERT_SHA256_FINGERPRINT:
+ certFingerprint = parseHexString(getPpsNodeValue(child));
+ break;
+ default:
+ throw new ParsingException("Unknown node under TrustRoot: "
+ + child.getName());
+ }
+ }
+ return Pair.create(certUrl, certFingerprint);
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/SPExclusionList subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/SPExclusionList subtree
+ * @return Array of excluded SSIDs
+ * @throws ParsingException
+ */
+ private static String[] parseSpExclusionList(PPSNode node) throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for SPExclusionList");
+ }
+ List<String> ssidList = new ArrayList<>();
+ for (PPSNode child : node.getChildren()) {
+ ssidList.add(parseSpExclusionInstance(child));
+ }
+ return ssidList.toArray(new String[ssidList.size()]);
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/SPExclusionList/<X+> subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/SPExclusionList/<X+> subtree
+ * @return String
+ * @throws ParsingException
+ */
+ private static String parseSpExclusionInstance(PPSNode node) throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for SPExclusion instance");
+ }
+ String ssid = null;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_SSID:
+ ssid = getPpsNodeValue(child);
+ break;
+ default:
+ throw new ParsingException("Unknown node under SPExclusion instance");
+ }
+ }
+ return ssid;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/RequiredProtoPortTuple subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/RequiredProtoPortTuple subtree
+ * @return Map of IP Protocol to Port Number tuples
+ * @throws ParsingException
+ */
+ private static Map<Integer, String> parseRequiredProtoPortTuple(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for RequiredProtoPortTuple");
+ }
+ Map<Integer, String> protoPortTupleMap = new HashMap<>();
+ for (PPSNode child : node.getChildren()) {
+ Pair<Integer, String> protoPortTuple = parseProtoPortTuple(child);
+ protoPortTupleMap.put(protoPortTuple.first, protoPortTuple.second);
+ }
+ return protoPortTupleMap;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/Policy/RequiredProtoPortTuple/<X+>
+ * subtree.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/Policy/RequiredProtoPortTuple/<X+> subtree
+ * @return Pair of IP Protocol to Port Number tuple
+ * @throws ParsingException
+ */
+ private static Pair<Integer, String> parseProtoPortTuple(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for RequiredProtoPortTuple "
+ + "instance");
+ }
+ int proto = Integer.MIN_VALUE;
+ String ports = null;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_IP_PROTOCOL:
+ proto = parseInteger(getPpsNodeValue(child));
+ break;
+ case NODE_PORT_NUMBER:
+ ports = getPpsNodeValue(child);
+ break;
+ default:
+ throw new ParsingException("Unknown node under RequiredProtoPortTuple instance"
+ + child.getName());
+ }
+ }
+ if (proto == Integer.MIN_VALUE) {
+ throw new ParsingException("Missing IPProtocol field");
+ }
+ if (ports == null) {
+ throw new ParsingException("Missing PortNumber field");
+ }
+ return Pair.create(proto, ports);
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/AAAServerTrustRoot subtree.
+ *
+ * @param node PPSNode representing the root of PerProviderSubscription/AAAServerTrustRoot
+ * subtree
+ * @return Map of certificate URL with the corresponding certificate fingerprint
+ * @throws ParsingException
+ */
+ private static Map<String, byte[]> parseAAAServerTrustRootList(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for AAAServerTrustRoot");
+ }
+ Map<String, byte[]> certList = new HashMap<>();
+ for (PPSNode child : node.getChildren()) {
+ Pair<String, byte[]> certTuple = parseTrustRoot(child);
+ certList.put(certTuple.first, certTuple.second);
+ }
+ return certList;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/SubscriptionParameter subtree.
+ *
+ * @param node PPSNode representing the root of PerProviderSubscription/SubscriptionParameter
+ * subtree
+ * @param config Instance of {@link PasspointConfiguration}
+ * @throws ParsingException
+ */
+ private static void parseSubscriptionParameter(PPSNode node, PasspointConfiguration config)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for SubscriptionParameter");
+ }
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_CREATION_DATE:
+ config.subscriptionCreationTimeInMs = parseDate(getPpsNodeValue(child));
+ break;
+ case NODE_EXPIRATION_DATE:
+ config.subscriptionExpirationTimeInMs = parseDate(getPpsNodeValue(child));
+ break;
+ case NODE_TYPE_OF_SUBSCRIPTION:
+ config.subscriptionType = getPpsNodeValue(child);
+ break;
+ case NODE_USAGE_LIMITS:
+ parseUsageLimits(child, config);
+ break;
+ default:
+ throw new ParsingException("Unknown node under SubscriptionParameter"
+ + child.getName());
+ }
+ }
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/SubscriptionParameter/UsageLimits
+ * subtree.
+ *
+ * @param node PPSNode representing the root of
+ * PerProviderSubscription/SubscriptionParameter/UsageLimits subtree
+ * @param config Instance of {@link PasspointConfiguration}
+ * @throws ParsingException
+ */
+ private static void parseUsageLimits(PPSNode node, PasspointConfiguration config)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for UsageLimits");
+ }
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_DATA_LIMIT:
+ config.usageLimitDataLimit = parseLong(getPpsNodeValue(child), 10);
+ break;
+ case NODE_START_DATE:
+ config.usageLimitStartTimeInMs = parseDate(getPpsNodeValue(child));
+ break;
+ case NODE_TIME_LIMIT:
+ config.usageLimitTimeLimitInMinutes = parseLong(getPpsNodeValue(child), 10);
+ break;
+ case NODE_USAGE_TIME_PERIOD:
+ config.usageLimitUsageTimePeriodInMinutes =
+ parseLong(getPpsNodeValue(child), 10);
+ break;
+ default:
+ throw new ParsingException("Unknown node under UsageLimits"
+ + child.getName());
+ }
+ }
+ }
+
+ /**
* Convert a hex string to a byte array.
*
* @param str String containing hex values
@@ -1054,6 +1618,21 @@
}
/**
+ * Parse a string representing a long integer.
+ *
+ * @param value String of long integer value
+ * @return long
+ * @throws ParsingException
+ */
+ private static long parseLong(String value, int radix) throws ParsingException {
+ try {
+ return Long.parseLong(value, radix);
+ } catch (NumberFormatException e) {
+ throw new ParsingException("Invalid long integer value: " + value);
+ }
+ }
+
+ /**
* Convert a List<Long> to a primitive long array long[].
*
* @param list List to be converted
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Policy.aidl b/wifi/java/android/net/wifi/hotspot2/pps/Policy.aidl
new file mode 100644
index 0000000..e923f1f
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Policy.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2017, 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.wifi.hotspot2.pps;
+
+parcelable Policy;
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Policy.java b/wifi/java/android/net/wifi/hotspot2/pps/Policy.java
new file mode 100644
index 0000000..b2583d3
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Policy.java
@@ -0,0 +1,452 @@
+/**
+ * Copyright (c) 2017, 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.wifi.hotspot2.pps;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class representing Policy subtree in PerProviderSubscription (PPS)
+ * Management Object (MO) tree.
+ *
+ * The Policy specifies additional criteria for Passpoint network selections, such as preferred
+ * roaming partner, minimum backhaul bandwidth, and etc. It also provides the meta data for
+ * updating the policy.
+ *
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ *
+ * @hide
+ */
+public final class Policy implements Parcelable {
+ private static final String TAG = "Policy";
+
+ /**
+ * Default priority for preferred roaming partner.
+ */
+ public static final int PREFERRED_ROAMING_PARTNER_DEFAULT_PRIORITY = 128;
+
+ /**
+ * Maximum number of SSIDs in the exclusion list.
+ */
+ private static final int MAX_EXCLUSION_SSIDS = 128;
+
+ /**
+ * Maximum byte for SSID.
+ */
+ private static final int MAX_SSID_BYTES = 32;
+
+ /**
+ * Maximum bytes for port string in {@link #requiredProtoPortMap}.
+ */
+ private static final int MAX_PORT_STRING_BYTES = 64;
+
+ /**
+ * Integer value used for indicating null value in the Parcel.
+ */
+ private static final int NULL_VALUE = -1;
+
+ /**
+ * Minimum available downlink/uplink bandwidth (in kilobits per second) required when
+ * selecting a network from home providers.
+ *
+ * The bandwidth is calculated as the LinkSpeed * (1 – LinkLoad/255), where LinkSpeed
+ * and LinkLoad parameters are drawn from the WAN Metrics ANQP element at that hotspot.
+ *
+ * Using Long.MIN_VALUE to indicate unset value.
+ */
+ public long minHomeDownlinkBandwidth = Long.MIN_VALUE;
+ public long minHomeUplinkBandwidth = Long.MIN_VALUE;
+
+ /**
+ * Minimum available downlink/uplink bandwidth (in kilobits per second) required when
+ * selecting a network from roaming providers.
+ *
+ * The bandwidth is calculated as the LinkSpeed * (1 – LinkLoad/255), where LinkSpeed
+ * and LinkLoad parameters are drawn from the WAN Metrics ANQP element at that hotspot.
+ *
+ * Using Long.MIN_VALUE to indicate unset value.
+ */
+ public long minRoamingDownlinkBandwidth = Long.MIN_VALUE;
+ public long minRoamingUplinkBandwidth = Long.MIN_VALUE;
+
+ /**
+ * List of SSIDs that are not preferred by the Home SP.
+ */
+ public String[] excludedSsidList = null;
+
+ /**
+ * List of IP protocol and port number required by one or more operator supported application.
+ * The port string contained one or more port numbers delimited by ",".
+ */
+ public Map<Integer, String> requiredProtoPortMap = null;
+
+ /**
+ * This specifies the maximum acceptable BSS load policy. This is used to prevent device
+ * from joining an AP whose channel is overly congested with traffic.
+ * Using Integer.MIN_VALUE to indicate unset value.
+ */
+ public int maximumBssLoadValue = Integer.MIN_VALUE;
+
+ /**
+ * Policy associated with a roaming provider. This specifies a priority associated
+ * with a roaming provider for given list of countries.
+ *
+ * Contains field under PerProviderSubscription/Policy/PreferredRoamingPartnerList.
+ */
+ public static final class RoamingPartner implements Parcelable {
+ /**
+ * FQDN of the roaming partner.
+ */
+ public String fqdn = null;
+
+ /**
+ * Flag indicating the exact match of FQDN is required for FQDN matching.
+ *
+ * When this flag is set to false, sub-domain matching is used. For example, when
+ * {@link #fqdn} s set to "example.com", "host.example.com" would be a match.
+ */
+ public boolean fqdnExactMatch = false;
+
+ /**
+ * Priority associated with this roaming partner policy.
+ */
+ public int priority = PREFERRED_ROAMING_PARTNER_DEFAULT_PRIORITY;
+
+ /**
+ * A string contained One or more, comma delimited (i.e., ",") ISO/IEC 3166-1 two
+ * character country strings or the country-independent value, "*".
+ */
+ public String countries = null;
+
+ public RoamingPartner() {}
+
+ public RoamingPartner(RoamingPartner source) {
+ if (source != null) {
+ fqdn = source.fqdn;
+ fqdnExactMatch = source.fqdnExactMatch;
+ priority = source.priority;
+ countries = source.countries;
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(fqdn);
+ dest.writeInt(fqdnExactMatch ? 1 : 0);
+ dest.writeInt(priority);
+ dest.writeString(countries);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof RoamingPartner)) {
+ return false;
+ }
+
+ RoamingPartner that = (RoamingPartner) thatObject;
+ return TextUtils.equals(fqdn, that.fqdn)
+ && fqdnExactMatch == that.fqdnExactMatch
+ && priority == that.priority
+ && TextUtils.equals(countries, that.countries);
+ }
+
+ /**
+ * Validate RoamingParnter data.
+ *
+ * @return true on success
+ */
+ public boolean validate() {
+ if (TextUtils.isEmpty(fqdn)) {
+ Log.d(TAG, "Missing FQDN");
+ return false;
+ }
+ if (TextUtils.isEmpty(countries)) {
+ Log.d(TAG, "Missing countries");
+ return false;
+ }
+ return true;
+ }
+
+ public static final Creator<RoamingPartner> CREATOR =
+ new Creator<RoamingPartner>() {
+ @Override
+ public RoamingPartner createFromParcel(Parcel in) {
+ RoamingPartner roamingPartner = new RoamingPartner();
+ roamingPartner.fqdn = in.readString();
+ roamingPartner.fqdnExactMatch = in.readInt() != 0;
+ roamingPartner.priority = in.readInt();
+ roamingPartner.countries = in.readString();
+ return roamingPartner;
+ }
+
+ @Override
+ public RoamingPartner[] newArray(int size) {
+ return new RoamingPartner[size];
+ }
+ };
+ }
+ public List<RoamingPartner> preferredRoamingPartnerList = null;
+
+ /**
+ * Meta data used for policy update.
+ */
+ public UpdateParameter policyUpdate = null;
+
+ /**
+ * Constructor for creating Policy with default values.
+ */
+ public Policy() {}
+
+ /**
+ * Copy constructor.
+ *
+ * @param source The source to copy from
+ */
+ public Policy(Policy source) {
+ if (source == null) {
+ return;
+ }
+ minHomeDownlinkBandwidth = source.minHomeDownlinkBandwidth;
+ minHomeUplinkBandwidth = source.minHomeUplinkBandwidth;
+ minRoamingDownlinkBandwidth = source.minRoamingDownlinkBandwidth;
+ minRoamingUplinkBandwidth = source.minRoamingUplinkBandwidth;
+ maximumBssLoadValue = source.maximumBssLoadValue;
+ if (source.excludedSsidList != null) {
+ excludedSsidList = Arrays.copyOf(source.excludedSsidList,
+ source.excludedSsidList.length);
+ }
+ if (source.requiredProtoPortMap != null) {
+ requiredProtoPortMap = Collections.unmodifiableMap(source.requiredProtoPortMap);
+ }
+ if (source.preferredRoamingPartnerList != null) {
+ preferredRoamingPartnerList = Collections.unmodifiableList(
+ source.preferredRoamingPartnerList);
+ }
+ if (source.policyUpdate != null) {
+ policyUpdate = new UpdateParameter(source.policyUpdate);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(minHomeDownlinkBandwidth);
+ dest.writeLong(minHomeUplinkBandwidth);
+ dest.writeLong(minRoamingDownlinkBandwidth);
+ dest.writeLong(minRoamingUplinkBandwidth);
+ dest.writeStringArray(excludedSsidList);
+ writeProtoPortMap(dest, requiredProtoPortMap);
+ dest.writeInt(maximumBssLoadValue);
+ writeRoamingPartnerList(dest, flags, preferredRoamingPartnerList);
+ dest.writeParcelable(policyUpdate, flags);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof Policy)) {
+ return false;
+ }
+ Policy that = (Policy) thatObject;
+
+ return minHomeDownlinkBandwidth == that.minHomeDownlinkBandwidth
+ && minHomeUplinkBandwidth == that.minHomeUplinkBandwidth
+ && minRoamingDownlinkBandwidth == that.minRoamingDownlinkBandwidth
+ && minRoamingUplinkBandwidth == that.minRoamingUplinkBandwidth
+ && Arrays.equals(excludedSsidList, that.excludedSsidList)
+ && (requiredProtoPortMap == null) ? that.requiredProtoPortMap == null
+ : requiredProtoPortMap.equals(that.requiredProtoPortMap)
+ && maximumBssLoadValue == that.maximumBssLoadValue
+ && (preferredRoamingPartnerList == null) ? that.preferredRoamingPartnerList == null
+ : preferredRoamingPartnerList.equals(that.preferredRoamingPartnerList)
+ && (policyUpdate == null) ? that.policyUpdate == null
+ : policyUpdate.equals(that.policyUpdate);
+ }
+
+ /**
+ * Validate Policy data.
+ *
+ * @return true on success
+ */
+ public boolean validate() {
+ if (policyUpdate == null) {
+ Log.d(TAG, "PolicyUpdate not specified");
+ return false;
+ }
+ if (!policyUpdate.validate()) {
+ return false;
+ }
+
+ // Validate SSID exclusion list.
+ if (excludedSsidList != null) {
+ if (excludedSsidList.length > MAX_EXCLUSION_SSIDS) {
+ Log.d(TAG, "SSID exclusion list size exceeded the max: "
+ + excludedSsidList.length);
+ return false;
+ }
+ for (String ssid : excludedSsidList) {
+ if (ssid.getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) {
+ Log.d(TAG, "Invalid SSID: " + ssid);
+ return false;
+ }
+ }
+ }
+ // Validate required protocol to port map.
+ if (requiredProtoPortMap != null) {
+ for (Map.Entry<Integer, String> entry : requiredProtoPortMap.entrySet()) {
+ String portNumber = entry.getValue();
+ if (portNumber.getBytes(StandardCharsets.UTF_8).length > MAX_PORT_STRING_BYTES) {
+ Log.d(TAG, "PortNumber string bytes exceeded the max: " + portNumber);
+ return false;
+ }
+ }
+ }
+ // Validate preferred roaming partner list.
+ if (preferredRoamingPartnerList != null) {
+ for (RoamingPartner partner : preferredRoamingPartnerList) {
+ if (!partner.validate()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ public static final Creator<Policy> CREATOR =
+ new Creator<Policy>() {
+ @Override
+ public Policy createFromParcel(Parcel in) {
+ Policy policy = new Policy();
+ policy.minHomeDownlinkBandwidth = in.readLong();
+ policy.minHomeUplinkBandwidth = in.readLong();
+ policy.minRoamingDownlinkBandwidth = in.readLong();
+ policy.minRoamingUplinkBandwidth = in.readLong();
+ policy.excludedSsidList = in.createStringArray();
+ policy.requiredProtoPortMap = readProtoPortMap(in);
+ policy.maximumBssLoadValue = in.readInt();
+ policy.preferredRoamingPartnerList = readRoamingPartnerList(in);
+ policy.policyUpdate = in.readParcelable(null);
+ return policy;
+ }
+
+ @Override
+ public Policy[] newArray(int size) {
+ return new Policy[size];
+ }
+
+ /**
+ * Helper function for reading IP Protocol to Port Number map from a Parcel.
+ *
+ * @param in The Parcel to read from
+ * @return Map of IP protocol to port number
+ */
+ private Map<Integer, String> readProtoPortMap(Parcel in) {
+ int size = in.readInt();
+ if (size == NULL_VALUE) {
+ return null;
+ }
+ Map<Integer, String> protoPortMap = new HashMap<>(size);
+ for (int i = 0; i < size; i++) {
+ int key = in.readInt();
+ String value = in.readString();
+ protoPortMap.put(key, value);
+ }
+ return protoPortMap;
+ }
+
+ /**
+ * Helper function for reading roaming partner list from a Parcel.
+ *
+ * @param in The Parcel to read from
+ * @return List of roaming partners
+ */
+ private List<RoamingPartner> readRoamingPartnerList(Parcel in) {
+ int size = in.readInt();
+ if (size == NULL_VALUE) {
+ return null;
+ }
+ List<RoamingPartner> partnerList = new ArrayList<>();
+ for (int i = 0; i < size; i++) {
+ partnerList.add(in.readParcelable(null));
+ }
+ return partnerList;
+ }
+
+ };
+
+ /**
+ * Helper function for writing IP Protocol to Port Number map to a Parcel.
+ *
+ * @param dest The Parcel to write to
+ * @param protoPortMap The map to write
+ */
+ private static void writeProtoPortMap(Parcel dest, Map<Integer, String> protoPortMap) {
+ if (protoPortMap == null) {
+ dest.writeInt(NULL_VALUE);
+ return;
+ }
+ dest.writeInt(protoPortMap.size());
+ for (Map.Entry<Integer, String> entry : protoPortMap.entrySet()) {
+ dest.writeInt(entry.getKey());
+ dest.writeString(entry.getValue());
+ }
+ }
+
+ /**
+ * Helper function for writing roaming partner list to a Parcel.
+ *
+ * @param dest The Parcel to write to
+ * @param flags The flag about how the object should be written
+ * @param partnerList The partner list to write
+ */
+ private static void writeRoamingPartnerList(Parcel dest, int flags,
+ List<RoamingPartner> partnerList) {
+ if (partnerList == null) {
+ dest.writeInt(NULL_VALUE);
+ return;
+ }
+ dest.writeInt(partnerList.size());
+ for (RoamingPartner partner : partnerList) {
+ dest.writeParcelable(partner, flags);
+ }
+ }
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.aidl b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.aidl
new file mode 100644
index 0000000..701db47
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2017, 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.wifi.hotspot2.pps;
+
+parcelable UpdateParameter;
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java
new file mode 100644
index 0000000..a390df7
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java
@@ -0,0 +1,303 @@
+/**
+ * Copyright (c) 2017, 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.wifi.hotspot2.pps;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+/**
+ * Class representing configuration parameters for subscription or policy update in
+ * PerProviderSubscription (PPS) Management Object (MO) tree. This is used by both
+ * PerProviderSubscription/Policy/PolicyUpdate and PerProviderSubscription/SubscriptionUpdate
+ * subtree.
+ *
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ *
+ * @hide
+ */
+public final class UpdateParameter implements Parcelable {
+ private static final String TAG = "UpdateParameter";
+
+ /**
+ * Value indicating policy update is not applicable. Thus, never check with policy server
+ * for updates.
+ */
+ public static final long UPDATE_CHECK_INTERVAL_NEVER = 0xFFFFFFFFL;
+
+ /**
+ * Valid string for UpdateMethod.
+ */
+ public static final String UPDATE_METHOD_OMADM = "OMA-DM-ClientInitiated";
+ public static final String UPDATE_METHOD_SSP = "SSP-ClientInitiated";
+
+ /**
+ * Valid string for Restriction.
+ */
+ public static final String UPDATE_RESTRICTION_HOMESP = "HomeSP";
+ public static final String UPDATE_RESTRICTION_ROAMING_PARTNER = "RoamingPartner";
+ public static final String UPDATE_RESTRICTION_UNRESTRICTED = "Unrestricted";
+
+ /**
+ * Maximum bytes for URI string.
+ */
+ private static final int MAX_URI_BYTES = 1023;
+
+ /**
+ * Maximum bytes for URI string.
+ */
+ private static final int MAX_URL_BYTES = 1023;
+
+ /**
+ * Maximum bytes for username.
+ */
+ private static final int MAX_USERNAME_BYTES = 63;
+
+ /**
+ * Maximum bytes for password.
+ */
+ private static final int MAX_PASSWORD_BYTES = 255;
+
+ /**
+ * Number of bytes for certificate SHA-256 fingerprint byte array.
+ */
+ private static final int CERTIFICATE_SHA256_BYTES = 32;
+
+ /**
+ * This specifies how often the mobile device shall check with policy server for updates.
+ *
+ * Using Long.MIN_VALUE to indicate unset value.
+ */
+ public long updateIntervalInMinutes = Long.MIN_VALUE;
+
+ /**
+ * The method used to update the policy. Permitted values are "OMA-DM-ClientInitiated"
+ * and "SPP-ClientInitiated".
+ */
+ public String updateMethod = null;
+
+ /**
+ * This specifies the hotspots at which the subscription update is permitted. Permitted
+ * values are "HomeSP", "RoamingPartner", or "Unrestricted";
+ */
+ public String restriction = null;
+
+ /**
+ * The URI of the update server.
+ */
+ public String serverUri = null;
+
+ /**
+ * Username used to authenticate with the policy server.
+ */
+ public String username = null;
+
+ /**
+ * Base64 encoded password used to authenticate with the policy server.
+ */
+ public String base64EncodedPassword = null;
+
+ /**
+ * HTTPS URL for retrieving certificate for trust root. The trust root is used to validate
+ * policy server's identity.
+ */
+ public String trustRootCertUrl = null;
+
+ /**
+ * SHA-256 fingerprint of the certificate located at {@link #trustRootCertUrl}
+ */
+ public byte[] trustRootCertSha256Fingerprint = null;
+
+ /**
+ * Constructor for creating Policy with default values.
+ */
+ public UpdateParameter() {}
+
+ /**
+ * Copy constructor.
+ *
+ * @param source The source to copy from
+ */
+ public UpdateParameter(UpdateParameter source) {
+ if (source == null) {
+ return;
+ }
+ updateIntervalInMinutes = source.updateIntervalInMinutes;
+ updateMethod = source.updateMethod;
+ restriction = source.restriction;
+ serverUri = source.serverUri;
+ username = source.username;
+ base64EncodedPassword = source.base64EncodedPassword;
+ trustRootCertUrl = source.trustRootCertUrl;
+ if (source.trustRootCertSha256Fingerprint != null) {
+ trustRootCertSha256Fingerprint = Arrays.copyOf(source.trustRootCertSha256Fingerprint,
+ source.trustRootCertSha256Fingerprint.length);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(updateIntervalInMinutes);
+ dest.writeString(updateMethod);
+ dest.writeString(restriction);
+ dest.writeString(serverUri);
+ dest.writeString(username);
+ dest.writeString(base64EncodedPassword);
+ dest.writeString(trustRootCertUrl);
+ dest.writeByteArray(trustRootCertSha256Fingerprint);
+ }
+
+ @Override
+ public boolean equals(Object thatObject) {
+ if (this == thatObject) {
+ return true;
+ }
+ if (!(thatObject instanceof UpdateParameter)) {
+ return false;
+ }
+ UpdateParameter that = (UpdateParameter) thatObject;
+
+ return updateIntervalInMinutes == that.updateIntervalInMinutes
+ && TextUtils.equals(updateMethod, that.updateMethod)
+ && TextUtils.equals(restriction, that.restriction)
+ && TextUtils.equals(serverUri, that.serverUri)
+ && TextUtils.equals(username, that.username)
+ && TextUtils.equals(base64EncodedPassword, that.base64EncodedPassword)
+ && TextUtils.equals(trustRootCertUrl, that.trustRootCertUrl)
+ && Arrays.equals(trustRootCertSha256Fingerprint,
+ that.trustRootCertSha256Fingerprint);
+ }
+
+ /**
+ * Validate UpdateParameter data.
+ *
+ * @return true on success
+ */
+ public boolean validate() {
+ if (updateIntervalInMinutes == Long.MIN_VALUE) {
+ Log.d(TAG, "Update interval not specified");
+ return false;
+ }
+ // Update not applicable.
+ if (updateIntervalInMinutes == UPDATE_CHECK_INTERVAL_NEVER) {
+ return true;
+ }
+
+ if (!TextUtils.equals(updateMethod, UPDATE_METHOD_OMADM)
+ && !TextUtils.equals(updateMethod, UPDATE_METHOD_SSP)) {
+ Log.d(TAG, "Unknown update method: " + updateMethod);
+ return false;
+ }
+
+ if (!TextUtils.equals(restriction, UPDATE_RESTRICTION_HOMESP)
+ && !TextUtils.equals(restriction, UPDATE_RESTRICTION_ROAMING_PARTNER)
+ && !TextUtils.equals(restriction, UPDATE_RESTRICTION_UNRESTRICTED)) {
+ Log.d(TAG, "Unknown restriction: " + restriction);
+ return false;
+ }
+
+ if (TextUtils.isEmpty(serverUri)) {
+ Log.d(TAG, "Missing update server URI");
+ return false;
+ }
+ if (serverUri.getBytes(StandardCharsets.UTF_8).length > MAX_URI_BYTES) {
+ Log.d(TAG, "URI bytes exceeded the max: "
+ + serverUri.getBytes(StandardCharsets.UTF_8).length);
+ return false;
+ }
+
+ if (TextUtils.isEmpty(username)) {
+ Log.d(TAG, "Missing username");
+ return false;
+ }
+ if (username.getBytes(StandardCharsets.UTF_8).length > MAX_USERNAME_BYTES) {
+ Log.d(TAG, "Username bytes exceeded the max: "
+ + username.getBytes(StandardCharsets.UTF_8).length);
+ return false;
+ }
+
+ if (TextUtils.isEmpty(base64EncodedPassword)) {
+ Log.d(TAG, "Missing username");
+ return false;
+ }
+ if (base64EncodedPassword.getBytes(StandardCharsets.UTF_8).length > MAX_PASSWORD_BYTES) {
+ Log.d(TAG, "Password bytes exceeded the max: "
+ + base64EncodedPassword.getBytes(StandardCharsets.UTF_8).length);
+ return false;
+ }
+ try {
+ Base64.decode(base64EncodedPassword, Base64.DEFAULT);
+ } catch (IllegalArgumentException e) {
+ Log.d(TAG, "Invalid encoding for password: " + base64EncodedPassword);
+ return false;
+ }
+
+ if (TextUtils.isEmpty(trustRootCertUrl)) {
+ Log.d(TAG, "Missing trust root certificate URL");
+ return false;
+ }
+ if (trustRootCertUrl.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) {
+ Log.d(TAG, "Trust root cert URL bytes exceeded the max: "
+ + trustRootCertUrl.getBytes(StandardCharsets.UTF_8).length);
+ return false;
+ }
+
+ if (trustRootCertSha256Fingerprint == null) {
+ Log.d(TAG, "Missing trust root certificate SHA-256 fingerprint");
+ return false;
+ }
+ if (trustRootCertSha256Fingerprint.length != CERTIFICATE_SHA256_BYTES) {
+ Log.d(TAG, "Incorrect size of trust root certificate SHA-256 fingerprint: "
+ + trustRootCertSha256Fingerprint.length);
+ return false;
+ }
+ return true;
+ }
+
+ public static final Creator<UpdateParameter> CREATOR =
+ new Creator<UpdateParameter>() {
+ @Override
+ public UpdateParameter createFromParcel(Parcel in) {
+ UpdateParameter updateParam = new UpdateParameter();
+ updateParam.updateIntervalInMinutes = in.readLong();
+ updateParam.updateMethod = in.readString();
+ updateParam.restriction = in.readString();
+ updateParam.serverUri = in.readString();
+ updateParam.username = in.readString();
+ updateParam.base64EncodedPassword = in.readString();
+ updateParam.trustRootCertUrl = in.readString();
+ updateParam.trustRootCertSha256Fingerprint = in.createByteArray();
+ return updateParam;
+ }
+
+ @Override
+ public UpdateParameter[] newArray(int size) {
+ return new UpdateParameter[size];
+ }
+ };
+}
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64 b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
index 8c1eb08..995963d 100644
--- a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
@@ -42,7 +42,7 @@
a05sY25ScFptbGpZWFJsVkhsd1pUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4V1lX
eDFaVDU0TlRBNWRqTThMMVpoCmJIVmxQZ29nSUNBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lD
QWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmwKVG1GdFpUNURaWEowVTBoQk1q
-VTJSbWx1WjJWeVVISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdV
+VTJSbWx1WjJWeWNISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdV
KwpNV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpG
bU1XWXhaakZtTVdZeFpqRm1NV1l4ClpqRm1NV1l4Wmp3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0FnSUNB
OEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWcKSUR4T2IyUmxQZ29nSUNB
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
index 6d86dd5..3ddd09f 100644
--- a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
@@ -35,7 +35,7 @@
aWZpY2F0ZTwvTm9kZU5hbWU+CiAgICAgICAgICA8Tm9kZT4KICAgICAgICAgICAgPE5vZGVOYW1l
PkNlcnRpZmljYXRlVHlwZTwvTm9kZU5hbWU+CiAgICAgICAgICAgIDxWYWx1ZT54NTA5djM8L1Zh
bHVlPgogICAgICAgICAgPC9Ob2RlPgogICAgICAgICAgPE5vZGU+CiAgICAgICAgICAgIDxOb2Rl
-TmFtZT5DZXJ0U0hBMjU2RmluZ2VyUHJpbnQ8L05vZGVOYW1lPgogICAgICAgICAgICA8VmFsdWU+
+TmFtZT5DZXJ0U0hBMjU2RmluZ2VycHJpbnQ8L05vZGVOYW1lPgogICAgICAgICAgICA8VmFsdWU+
MWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYx
ZjFmMWYxZjwvVmFsdWU+CiAgICAgICAgICA8L05vZGU+CiAgICAgICAgPC9Ob2RlPgogICAgICAg
IDxOb2RlPgogICAgICAgICAgPE5vZGVOYW1lPlNJTTwvTm9kZU5hbWU+CiAgICAgICAgICA8Tm9k
diff --git a/wifi/tests/assets/pps/PerProviderSubscription.xml b/wifi/tests/assets/pps/PerProviderSubscription.xml
index 3969f69..7f2d95d 100644
--- a/wifi/tests/assets/pps/PerProviderSubscription.xml
+++ b/wifi/tests/assets/pps/PerProviderSubscription.xml
@@ -8,6 +8,10 @@
</Type>
</RTProperties>
<Node>
+ <NodeName>UpdateIdentifier</NodeName>
+ <Value>12</Value>
+ </Node>
+ <Node>
<NodeName>i001</NodeName>
<Node>
<NodeName>HomeSP</NodeName>
@@ -143,7 +147,7 @@
<Value>x509v3</Value>
</Node>
<Node>
- <NodeName>CertSHA256FingerPrint</NodeName>
+ <NodeName>CertSHA256Fingerprint</NodeName>
<Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
</Node>
</Node>
@@ -159,6 +163,237 @@
</Node>
</Node>
</Node>
+ <Node>
+ <NodeName>Policy</NodeName>
+ <Node>
+ <NodeName>PreferredRoamingPartnerList</NodeName>
+ <Node>
+ <NodeName>p001</NodeName>
+ <Node>
+ <NodeName>FQDN_Match</NodeName>
+ <Value>test1.fqdn.com,exactMatch</Value>
+ </Node>
+ <Node>
+ <NodeName>Priority</NodeName>
+ <Value>127</Value>
+ </Node>
+ <Node>
+ <NodeName>Country</NodeName>
+ <Value>us,fr</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>p002</NodeName>
+ <Node>
+ <NodeName>FQDN_Match</NodeName>
+ <Value>test2.fqdn.com,includeSubdomains</Value>
+ </Node>
+ <Node>
+ <NodeName>Priority</NodeName>
+ <Value>200</Value>
+ </Node>
+ <Node>
+ <NodeName>Country</NodeName>
+ <Value>*</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>MinBackhaulThreshold</NodeName>
+ <Node>
+ <NodeName>m001</NodeName>
+ <Node>
+ <NodeName>NetworkType</NodeName>
+ <Value>home</Value>
+ </Node>
+ <Node>
+ <NodeName>DLBandwidth</NodeName>
+ <Value>23412</Value>
+ </Node>
+ <Node>
+ <NodeName>ULBandwidth</NodeName>
+ <Value>9823</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>m002</NodeName>
+ <Node>
+ <NodeName>NetworkType</NodeName>
+ <Value>roaming</Value>
+ </Node>
+ <Node>
+ <NodeName>DLBandwidth</NodeName>
+ <Value>9271</Value>
+ </Node>
+ <Node>
+ <NodeName>ULBandwidth</NodeName>
+ <Value>2315</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>PolicyUpdate</NodeName>
+ <Node>
+ <NodeName>UpdateInterval</NodeName>
+ <Value>120</Value>
+ </Node>
+ <Node>
+ <NodeName>UpdateMethod</NodeName>
+ <Value>OMA-DM-ClientInitiated</Value>
+ </Node>
+ <Node>
+ <NodeName>Restriction</NodeName>
+ <Value>HomeSP</Value>
+ </Node>
+ <Node>
+ <NodeName>URI</NodeName>
+ <Value>policy.update.com</Value>
+ </Node>
+ <Node>
+ <NodeName>UsernamePassword</NodeName>
+ <Node>
+ <NodeName>Username</NodeName>
+ <Value>updateUser</Value>
+ </Node>
+ <Node>
+ <NodeName>Password</NodeName>
+ <Value>updatePass</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>TrustRoot</NodeName>
+ <Node>
+ <NodeName>CertURL</NodeName>
+ <Value>update.cert.com</Value>
+ </Node>
+ <Node>
+ <NodeName>CertSHA256Fingerprint</NodeName>
+ <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>SPExclusionList</NodeName>
+ <Node>
+ <NodeName>s001</NodeName>
+ <Node>
+ <NodeName>SSID</NodeName>
+ <Value>excludeSSID</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>RequiredProtoPortTuple</NodeName>
+ <Node>
+ <NodeName>r001</NodeName>
+ <Node>
+ <NodeName>IPProtocol</NodeName>
+ <Value>12</Value>
+ </Node>
+ <Node>
+ <NodeName>PortNumber</NodeName>
+ <Value>34,92,234</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>MaximumBSSLoadValue</NodeName>
+ <Value>23</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>CredentialPriority</NodeName>
+ <Value>99</Value>
+ </Node>
+ <Node>
+ <NodeName>AAAServerTrustRoot</NodeName>
+ <Node>
+ <NodeName>a001</NodeName>
+ <Node>
+ <NodeName>CertURL</NodeName>
+ <Value>server1.trust.root.com</Value>
+ </Node>
+ <Node>
+ <NodeName>CertSHA256Fingerprint</NodeName>
+ <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>SubscriptionUpdate</NodeName>
+ <Node>
+ <NodeName>UpdateInterval</NodeName>
+ <Value>120</Value>
+ </Node>
+ <Node>
+ <NodeName>UpdateMethod</NodeName>
+ <Value>SSP-ClientInitiated</Value>
+ </Node>
+ <Node>
+ <NodeName>Restriction</NodeName>
+ <Value>RoamingPartner</Value>
+ </Node>
+ <Node>
+ <NodeName>URI</NodeName>
+ <Value>subscription.update.com</Value>
+ </Node>
+ <Node>
+ <NodeName>UsernamePassword</NodeName>
+ <Node>
+ <NodeName>Username</NodeName>
+ <Value>subscriptionUser</Value>
+ </Node>
+ <Node>
+ <NodeName>Password</NodeName>
+ <Value>subscriptionPass</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>TrustRoot</NodeName>
+ <Node>
+ <NodeName>CertURL</NodeName>
+ <Value>subscription.update.cert.com</Value>
+ </Node>
+ <Node>
+ <NodeName>CertSHA256Fingerprint</NodeName>
+ <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>SubscriptionParameter</NodeName>
+ <Node>
+ <NodeName>CreationDate</NodeName>
+ <Value>2016-02-01T10:00:00Z</Value>
+ </Node>
+ <Node>
+ <NodeName>ExpirationDate</NodeName>
+ <Value>2016-03-01T10:00:00Z</Value>
+ </Node>
+ <Node>
+ <NodeName>TypeOfSubscription</NodeName>
+ <Value>Gold</Value>
+ </Node>
+ <Node>
+ <NodeName>UsageLimits</NodeName>
+ <Node>
+ <NodeName>DataLimit</NodeName>
+ <Value>921890</Value>
+ </Node>
+ <Node>
+ <NodeName>StartDate</NodeName>
+ <Value>2016-12-01T10:00:00Z</Value>
+ </Node>
+ <Node>
+ <NodeName>TimeLimit</NodeName>
+ <Value>120</Value>
+ </Node>
+ <Node>
+ <NodeName>UsageTimePeriod</NodeName>
+ <Value>99910</Value>
+ </Node>
+ </Node>
+ </Node>
</Node>
</Node>
</MgmtTree>
diff --git a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
index 0e503d5..fa546a5 100644
--- a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
@@ -87,6 +87,52 @@
}
@Test
+ public void testSetClientKeyEntryWithNull() {
+ mEnterpriseConfig.setClientKeyEntry(null, null);
+ assertEquals(null, mEnterpriseConfig.getClientCertificateChain());
+ assertEquals(null, mEnterpriseConfig.getClientCertificate());
+ mEnterpriseConfig.setClientKeyEntryWithCertificateChain(null, null);
+ assertEquals(null, mEnterpriseConfig.getClientCertificateChain());
+ assertEquals(null, mEnterpriseConfig.getClientCertificate());
+ }
+
+ @Test
+ public void testSetClientCertificateChain() {
+ PrivateKey clientKey = FakeKeys.RSA_KEY1;
+ X509Certificate cert0 = FakeKeys.CLIENT_CERT;
+ X509Certificate cert1 = FakeKeys.CA_CERT1;
+ X509Certificate[] clientChain = new X509Certificate[] {cert0, cert1};
+ mEnterpriseConfig.setClientKeyEntryWithCertificateChain(clientKey, clientChain);
+ X509Certificate[] result = mEnterpriseConfig.getClientCertificateChain();
+ assertEquals(result.length, 2);
+ assertTrue(result[0] == cert0 && result[1] == cert1);
+ assertTrue(mEnterpriseConfig.getClientCertificate() == cert0);
+ }
+
+ private boolean isClientCertificateChainInvalid(X509Certificate[] clientChain) {
+ boolean exceptionThrown = false;
+ try {
+ PrivateKey clientKey = FakeKeys.RSA_KEY1;
+ mEnterpriseConfig.setClientKeyEntryWithCertificateChain(clientKey, clientChain);
+ } catch (IllegalArgumentException e) {
+ exceptionThrown = true;
+ }
+ return exceptionThrown;
+ }
+
+ @Test
+ public void testSetInvalidClientCertificateChain() {
+ X509Certificate clientCert = FakeKeys.CLIENT_CERT;
+ X509Certificate caCert = FakeKeys.CA_CERT1;
+ assertTrue("Invalid client certificate",
+ isClientCertificateChainInvalid(new X509Certificate[] {caCert, caCert}));
+ assertTrue("Invalid CA certificate",
+ isClientCertificateChainInvalid(new X509Certificate[] {clientCert, clientCert}));
+ assertTrue("Both certificates invalid",
+ isClientCertificateChainInvalid(new X509Certificate[] {caCert, clientCert}));
+ }
+
+ @Test
public void testSaveSingleCaCertificateAlias() {
final String alias = "single_alias 0";
mEnterpriseConfig.setCaCertificateAliases(new String[] {alias});
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index 2350d32..1eb08e0 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -22,16 +22,26 @@
import android.net.wifi.EAPConstants;
import android.net.wifi.hotspot2.pps.Credential;
import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
import android.os.Parcel;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
import org.junit.Test;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
/**
* Unit tests for {@link android.net.wifi.hotspot2.PasspointConfiguration}.
*/
@SmallTest
public class PasspointConfigurationTest {
+ private static final int MAX_URL_BYTES = 1023;
+ private static final int CERTIFICATE_FINGERPRINT_BYTES = 32;
/**
* Utility function for creating a {@link android.net.wifi.hotspot2.pps.HomeSP}.
@@ -66,6 +76,93 @@
}
/**
+ * Helper function for creating a {@link Policy} for testing.
+ *
+ * @return {@link Policy}
+ */
+ private static Policy createPolicy() {
+ Policy policy = new Policy();
+ policy.minHomeDownlinkBandwidth = 123;
+ policy.minHomeUplinkBandwidth = 345;
+ policy.minRoamingDownlinkBandwidth = 567;
+ policy.minRoamingUplinkBandwidth = 789;
+ policy.maximumBssLoadValue = 12;
+ policy.excludedSsidList = new String[] {"ssid1", "ssid2"};
+ policy.requiredProtoPortMap = new HashMap<>();
+ policy.requiredProtoPortMap.put(12, "23,342,123");
+ policy.requiredProtoPortMap.put(23, "789,372,1235");
+
+ policy.preferredRoamingPartnerList = new ArrayList<>();
+ Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+ partner1.fqdn = "partner1.com";
+ partner1.fqdnExactMatch = true;
+ partner1.priority = 12;
+ partner1.countries = "us,jp";
+ Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+ partner2.fqdn = "partner2.com";
+ partner2.fqdnExactMatch = false;
+ partner2.priority = 42;
+ partner2.countries = "ca,fr";
+ policy.preferredRoamingPartnerList.add(partner1);
+ policy.preferredRoamingPartnerList.add(partner2);
+
+ policy.policyUpdate = new UpdateParameter();
+ policy.policyUpdate.updateIntervalInMinutes = 1712;
+ policy.policyUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+ policy.policyUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+ policy.policyUpdate.serverUri = "policy.update.com";
+ policy.policyUpdate.username = "username";
+ policy.policyUpdate.base64EncodedPassword =
+ Base64.encodeToString("password".getBytes(), Base64.DEFAULT);
+ policy.policyUpdate.trustRootCertUrl = "trust.cert.com";
+ policy.policyUpdate.trustRootCertSha256Fingerprint =
+ new byte[CERTIFICATE_FINGERPRINT_BYTES];
+
+ return policy;
+ }
+
+ private static UpdateParameter createSubscriptionUpdate() {
+ UpdateParameter subUpdate = new UpdateParameter();
+ subUpdate.updateIntervalInMinutes = 9021;
+ subUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_SSP;
+ subUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_ROAMING_PARTNER;
+ subUpdate.serverUri = "subscription.update.com";
+ subUpdate.username = "subUsername";
+ subUpdate.base64EncodedPassword =
+ Base64.encodeToString("subPassword".getBytes(), Base64.DEFAULT);
+ subUpdate.trustRootCertUrl = "subscription.trust.cert.com";
+ subUpdate.trustRootCertSha256Fingerprint = new byte[CERTIFICATE_FINGERPRINT_BYTES];
+ return subUpdate;
+ }
+ /**
+ * Helper function for creating a {@link PasspointConfiguration} for testing.
+ *
+ * @return {@link PasspointConfiguration}
+ */
+ private static PasspointConfiguration createConfig() {
+ PasspointConfiguration config = new PasspointConfiguration();
+ config.homeSp = createHomeSp();
+ config.credential = createCredential();
+ config.policy = createPolicy();
+ config.subscriptionUpdate = createSubscriptionUpdate();
+ config.trustRootCertList = new HashMap<>();
+ config.trustRootCertList.put("trustRoot.cert1.com",
+ new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+ config.trustRootCertList.put("trustRoot.cert2.com",
+ new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+ config.updateIdentifier = 1;
+ config.credentialPriority = 120;
+ config.subscriptionCreationTimeInMs = 231200;
+ config.subscriptionExpirationTimeInMs = 2134232;
+ config.subscriptionType = "Gold";
+ config.usageLimitUsageTimePeriodInMinutes = 3600;
+ config.usageLimitStartTimeInMs = 124214213;
+ config.usageLimitDataLimit = 14121;
+ config.usageLimitTimeLimitInMinutes = 78912;
+ return config;
+ }
+
+ /**
* Verify parcel write and read consistency for the given configuration.
*
* @param writeConfig The configuration to verify
@@ -92,39 +189,73 @@
}
/**
- * Verify parcel read/write for a configuration that contained both HomeSP and Credential.
+ * Verify parcel read/write for a configuration that contained the full configuration.
*
* @throws Exception
*/
@Test
- public void verifyParcelWithHomeSPAndCredential() throws Exception {
- PasspointConfiguration config = new PasspointConfiguration();
- config.homeSp = createHomeSp();
- config.credential = createCredential();
+ public void verifyParcelWithFullConfiguration() throws Exception {
+ verifyParcel(createConfig());
+ }
+
+ /**
+ * Verify parcel read/write for a configuration that doesn't contain HomeSP.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithoutHomeSP() throws Exception {
+ PasspointConfiguration config = createConfig();
+ config.homeSp = null;
verifyParcel(config);
}
/**
- * Verify parcel read/write for a configuration that contained only HomeSP.
+ * Verify parcel read/write for a configuration that doesn't contain Credential.
*
* @throws Exception
*/
@Test
- public void verifyParcelWithHomeSPOnly() throws Exception {
- PasspointConfiguration config = new PasspointConfiguration();
- config.homeSp = createHomeSp();
+ public void verifyParcelWithoutCredential() throws Exception {
+ PasspointConfiguration config = createConfig();
+ config.credential = null;
verifyParcel(config);
}
/**
- * Verify parcel read/write for a configuration that contained only Credential.
+ * Verify parcel read/write for a configuration that doesn't contain Policy.
*
* @throws Exception
*/
@Test
- public void verifyParcelWithCredentialOnly() throws Exception {
- PasspointConfiguration config = new PasspointConfiguration();
- config.credential = createCredential();
+ public void verifyParcelWithoutPolicy() throws Exception {
+ PasspointConfiguration config = createConfig();
+ config.policy = null;
+ verifyParcel(config);
+ }
+
+ /**
+ * Verify parcel read/write for a configuration that doesn't contain subscription update.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithoutSubscriptionUpdate() throws Exception {
+ PasspointConfiguration config = createConfig();
+ config.subscriptionUpdate = null;
+ verifyParcel(config);
+ }
+
+ /**
+ * Verify parcel read/write for a configuration that doesn't contain trust root certificate
+ * list.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithoutTrustRootCertList() throws Exception {
+ PasspointConfiguration config = createConfig();
+ config.trustRootCertList = null;
verifyParcel(config);
}
@@ -140,43 +271,108 @@
}
/**
+ * Verify that a configuration contained all fields is valid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateFullConfig() throws Exception {
+ PasspointConfiguration config = createConfig();
+ assertTrue(config.validate());
+ }
+
+ /**
* Verify that a configuration without Credential is invalid.
*
* @throws Exception
*/
@Test
public void validateConfigWithoutCredential() throws Exception {
- PasspointConfiguration config = new PasspointConfiguration();
- config.homeSp = createHomeSp();
+ PasspointConfiguration config = createConfig();
+ config.credential = null;
assertFalse(config.validate());
}
/**
- * Verify that a a configuration without HomeSP is invalid.
+ * Verify that a configuration without HomeSP is invalid.
*
* @throws Exception
*/
@Test
public void validateConfigWithoutHomeSp() throws Exception {
- PasspointConfiguration config = new PasspointConfiguration();
- config.credential = createCredential();
+ PasspointConfiguration config = createConfig();
+ config.homeSp = null;
assertFalse(config.validate());
}
/**
- * Verify a valid configuration.
+ * Verify that a configuration without Policy is valid, since Policy configurations
+ * are optional (applied for Hotspot 2.0 Release only).
*
* @throws Exception
*/
@Test
- public void validateValidConfig() throws Exception {
- PasspointConfiguration config = new PasspointConfiguration();
- config.homeSp = createHomeSp();
- config.credential = createCredential();
+ public void validateConfigWithoutPolicy() throws Exception {
+ PasspointConfiguration config = createConfig();
+ config.policy = null;
assertTrue(config.validate());
}
/**
+ * Verify that a configuration without subscription update is valid, since subscription
+ * update configurations are optional (applied for Hotspot 2.0 Release only).
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateConfigWithoutSubscriptionUpdate() throws Exception {
+ PasspointConfiguration config = createConfig();
+ config.subscriptionUpdate = null;
+ assertTrue(config.validate());
+ }
+
+ /**
+ * Verify that a configuration with a trust root certificate URL exceeding the max size
+ * is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateConfigWithInvalidTrustRootCertUrl() throws Exception {
+ PasspointConfiguration config = createConfig();
+ byte[] rawUrlBytes = new byte[MAX_URL_BYTES + 1];
+ Arrays.fill(rawUrlBytes, (byte) 'a');
+ config.trustRootCertList.put(new String(rawUrlBytes, StandardCharsets.UTF_8),
+ new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+ assertFalse(config.validate());
+
+ config.trustRootCertList = new HashMap<>();
+ config.trustRootCertList.put(null, new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+ assertFalse(config.validate());
+ }
+
+ /**
+ * Verify that a configuration with an invalid trust root certificate fingerprint is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateConfigWithInvalidTrustRootCertFingerprint() throws Exception {
+ PasspointConfiguration config = createConfig();
+ config.trustRootCertList = new HashMap<>();
+ config.trustRootCertList.put("test.cert.com", new byte[CERTIFICATE_FINGERPRINT_BYTES + 1]);
+ assertFalse(config.validate());
+
+ config.trustRootCertList = new HashMap<>();
+ config.trustRootCertList.put("test.cert.com", new byte[CERTIFICATE_FINGERPRINT_BYTES - 1]);
+ assertFalse(config.validate());
+
+ config.trustRootCertList = new HashMap<>();
+ config.trustRootCertList.put("test.cert.com", null);
+ assertFalse(config.validate());
+ }
+
+ /**
* Verify that copy constructor works when pass in a null source.
*
* @throws Exception
@@ -195,9 +391,7 @@
*/
@Test
public void validateCopyConstructorWithValidSource() throws Exception {
- PasspointConfiguration sourceConfig = new PasspointConfiguration();
- sourceConfig.homeSp = createHomeSp();
- sourceConfig.credential = createCredential();
+ PasspointConfiguration sourceConfig = createConfig();
PasspointConfiguration copyConfig = new PasspointConfiguration(sourceConfig);
assertTrue(copyConfig.equals(sourceConfig));
}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
index 1c7508e..055204c 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
@@ -23,7 +23,10 @@
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.hotspot2.pps.Credential;
import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
import org.junit.Test;
@@ -33,6 +36,7 @@
import java.io.InputStreamReader;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -81,7 +85,38 @@
* @return {@link PasspointConfiguration}
*/
private PasspointConfiguration generateConfigurationFromPPSMOTree() throws Exception {
+ DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+
PasspointConfiguration config = new PasspointConfiguration();
+ config.updateIdentifier = 12;
+ config.credentialPriority = 99;
+
+ // AAA Server trust root.
+ config.trustRootCertList = new HashMap<>();
+ byte[] certFingerprint = new byte[32];
+ Arrays.fill(certFingerprint, (byte) 0x1f);
+ config.trustRootCertList.put("server1.trust.root.com", certFingerprint);
+
+ // Subscription update.
+ config.subscriptionUpdate = new UpdateParameter();
+ config.subscriptionUpdate.updateIntervalInMinutes = 120;
+ config.subscriptionUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_SSP;
+ config.subscriptionUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_ROAMING_PARTNER;
+ config.subscriptionUpdate.serverUri = "subscription.update.com";
+ config.subscriptionUpdate.username = "subscriptionUser";
+ config.subscriptionUpdate.base64EncodedPassword = "subscriptionPass";
+ config.subscriptionUpdate.trustRootCertUrl = "subscription.update.cert.com";
+ config.subscriptionUpdate.trustRootCertSha256Fingerprint = new byte[32];
+ Arrays.fill(config.subscriptionUpdate.trustRootCertSha256Fingerprint, (byte) 0x1f);
+
+ // Subscription parameters.
+ config.subscriptionCreationTimeInMs = format.parse("2016-02-01T10:00:00Z").getTime();
+ config.subscriptionExpirationTimeInMs = format.parse("2016-03-01T10:00:00Z").getTime();
+ config.subscriptionType = "Gold";
+ config.usageLimitDataLimit = 921890;
+ config.usageLimitStartTimeInMs = format.parse("2016-12-01T10:00:00Z").getTime();
+ config.usageLimitTimeLimitInMinutes = 120;
+ config.usageLimitUsageTimePeriodInMinutes = 99910;
// HomeSP configuration.
config.homeSp = new HomeSP();
@@ -97,7 +132,6 @@
config.homeSp.otherHomePartners = new String[] {"other.fqdn.com"};
// Credential configuration.
- DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
config.credential = new Credential();
config.credential.creationTimeInMs = format.parse("2016-01-01T10:00:00Z").getTime();
config.credential.expirationTimeInMs = format.parse("2016-02-01T10:00:00Z").getTime();
@@ -118,12 +152,46 @@
config.credential.simCredential = new Credential.SimCredential();
config.credential.simCredential.imsi = "imsi";
config.credential.simCredential.eapType = 24;
+
+ // Policy configuration.
+ config.policy = new Policy();
+ config.policy.preferredRoamingPartnerList = new ArrayList<>();
+ Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+ partner1.fqdn = "test1.fqdn.com";
+ partner1.fqdnExactMatch = true;
+ partner1.priority = 127;
+ partner1.countries = "us,fr";
+ Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+ partner2.fqdn = "test2.fqdn.com";
+ partner2.fqdnExactMatch = false;
+ partner2.priority = 200;
+ partner2.countries = "*";
+ config.policy.preferredRoamingPartnerList.add(partner1);
+ config.policy.preferredRoamingPartnerList.add(partner2);
+ config.policy.minHomeDownlinkBandwidth = 23412;
+ config.policy.minHomeUplinkBandwidth = 9823;
+ config.policy.minRoamingDownlinkBandwidth = 9271;
+ config.policy.minRoamingUplinkBandwidth = 2315;
+ config.policy.excludedSsidList = new String[] {"excludeSSID"};
+ config.policy.requiredProtoPortMap = new HashMap<>();
+ config.policy.requiredProtoPortMap.put(12, "34,92,234");
+ config.policy.maximumBssLoadValue = 23;
+ config.policy.policyUpdate = new UpdateParameter();
+ config.policy.policyUpdate.updateIntervalInMinutes = 120;
+ config.policy.policyUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+ config.policy.policyUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+ config.policy.policyUpdate.serverUri = "policy.update.com";
+ config.policy.policyUpdate.username = "updateUser";
+ config.policy.policyUpdate.base64EncodedPassword = "updatePass";
+ config.policy.policyUpdate.trustRootCertUrl = "update.cert.com";
+ config.policy.policyUpdate.trustRootCertSha256Fingerprint = new byte[32];
+ Arrays.fill(config.policy.policyUpdate.trustRootCertSha256Fingerprint, (byte) 0x1f);
+
return config;
}
/**
- * Parse and verify all supported fields under PPS MO tree (currently only fields under
- * HomeSP and Credential subtree).
+ * Parse and verify all supported fields under PPS MO tree.
*
* @throws Exception
*/
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/PolicyTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/PolicyTest.java
new file mode 100644
index 0000000..c371c49
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/PolicyTest.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2017 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.wifi.hotspot2.pps;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
+
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link android.net.wifi.hotspot2.pps.Policy}.
+ */
+@SmallTest
+public class PolicyTest {
+ private static final int MAX_NUMBER_OF_EXCLUDED_SSIDS = 128;
+ private static final int MAX_SSID_BYTES = 32;
+ private static final int MAX_PORT_STRING_BYTES = 64;
+
+ /**
+ * Helper function for creating a {@link Policy} for testing.
+ *
+ * @return {@link Policy}
+ */
+ private static Policy createPolicy() {
+ Policy policy = new Policy();
+ policy.minHomeDownlinkBandwidth = 123;
+ policy.minHomeUplinkBandwidth = 345;
+ policy.minRoamingDownlinkBandwidth = 567;
+ policy.minRoamingUplinkBandwidth = 789;
+ policy.excludedSsidList = new String[] {"ssid1", "ssid2"};
+ policy.requiredProtoPortMap = new HashMap<>();
+ policy.requiredProtoPortMap.put(12, "23,342,123");
+ policy.requiredProtoPortMap.put(23, "789,372,1235");
+ policy.maximumBssLoadValue = 12;
+
+ policy.preferredRoamingPartnerList = new ArrayList<>();
+ Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+ partner1.fqdn = "partner1.com";
+ partner1.fqdnExactMatch = true;
+ partner1.priority = 12;
+ partner1.countries = "us,jp";
+ Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+ partner2.fqdn = "partner2.com";
+ partner2.fqdnExactMatch = false;
+ partner2.priority = 42;
+ partner2.countries = "ca,fr";
+ policy.preferredRoamingPartnerList.add(partner1);
+ policy.preferredRoamingPartnerList.add(partner2);
+
+ policy.policyUpdate = new UpdateParameter();
+ policy.policyUpdate.updateIntervalInMinutes = 1712;
+ policy.policyUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+ policy.policyUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+ policy.policyUpdate.serverUri = "policy.update.com";
+ policy.policyUpdate.username = "username";
+ policy.policyUpdate.base64EncodedPassword =
+ Base64.encodeToString("password".getBytes(), Base64.DEFAULT);
+ policy.policyUpdate.trustRootCertUrl = "trust.cert.com";
+ policy.policyUpdate.trustRootCertSha256Fingerprint = new byte[32];
+
+ return policy;
+ }
+
+ /**
+ * Helper function for verifying Policy after parcel write then read.
+ * @param policyToWrite
+ * @throws Exception
+ */
+ private static void verifyParcel(Policy policyToWrite) throws Exception {
+ Parcel parcel = Parcel.obtain();
+ policyToWrite.writeToParcel(parcel, 0);
+
+ parcel.setDataPosition(0); // Rewind data position back to the beginning for read.
+ Policy policyFromRead = Policy.CREATOR.createFromParcel(parcel);
+ assertTrue(policyFromRead.equals(policyToWrite));
+ }
+
+ /**
+ * Verify parcel read/write for an empty Policy.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithEmptyPolicy() throws Exception {
+ verifyParcel(new Policy());
+ }
+
+ /**
+ * Verify parcel read/write for a Policy with all fields set.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithFullPolicy() throws Exception {
+ verifyParcel(createPolicy());
+ }
+
+ /**
+ * Verify parcel read/write for a Policy without protocol port map.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithoutProtoPortMap() throws Exception {
+ Policy policy = createPolicy();
+ policy.requiredProtoPortMap = null;
+ verifyParcel(policy);
+ }
+
+ /**
+ * Verify parcel read/write for a Policy without preferred roaming partner list.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithoutPreferredRoamingPartnerList() throws Exception {
+ Policy policy = createPolicy();
+ policy.preferredRoamingPartnerList = null;
+ verifyParcel(policy);
+ }
+
+ /**
+ * Verify parcel read/write for a Policy without policy update parameters.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithoutPolicyUpdate() throws Exception {
+ Policy policy = createPolicy();
+ policy.policyUpdate = null;
+ verifyParcel(policy);
+ }
+
+ /**
+ * Verify that policy created using copy constructor with null source should be the same
+ * as the policy created using default constructor.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyCopyConstructionWithNullSource() throws Exception {
+ Policy copyPolicy = new Policy(null);
+ Policy defaultPolicy = new Policy();
+ assertTrue(defaultPolicy.equals(copyPolicy));
+ }
+
+ /**
+ * Verify that policy created using copy constructor with a valid source should be the
+ * same as the source.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyCopyConstructionWithFullPolicy() throws Exception {
+ Policy policy = createPolicy();
+ Policy copyPolicy = new Policy(policy);
+ assertTrue(policy.equals(copyPolicy));
+ }
+
+ /**
+ * Verify that a default policy (with no informatio) is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validatePolicyWithDefault() throws Exception {
+ Policy policy = new Policy();
+ assertFalse(policy.validate());
+ }
+
+ /**
+ * Verify that a policy created using {@link #createPolicy} is valid, since all fields are
+ * filled in with valid values.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validatePolicyWithFullPolicy() throws Exception {
+ assertTrue(createPolicy().validate());
+ }
+
+ /**
+ * Verify that a policy without policy update parameters is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validatePolicyWithoutPolicyUpdate() throws Exception {
+ Policy policy = createPolicy();
+ policy.policyUpdate = null;
+ assertFalse(policy.validate());
+ }
+
+ /**
+ * Verify that a policy with invalid policy update parameters is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validatePolicyWithInvalidPolicyUpdate() throws Exception {
+ Policy policy = createPolicy();
+ policy.policyUpdate = new UpdateParameter();
+ assertFalse(policy.validate());
+ }
+
+ /**
+ * Verify that a policy with a preferred roaming partner with FQDN not specified is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validatePolicyWithRoamingPartnerWithoutFQDN() throws Exception {
+ Policy policy = createPolicy();
+ Policy.RoamingPartner partner = new Policy.RoamingPartner();
+ partner.fqdnExactMatch = true;
+ partner.priority = 12;
+ partner.countries = "us,jp";
+ policy.preferredRoamingPartnerList.add(partner);
+ assertFalse(policy.validate());
+ }
+
+ /**
+ * Verify that a policy with a preferred roaming partner with countries not specified is
+ * invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validatePolicyWithRoamingPartnerWithoutCountries() throws Exception {
+ Policy policy = createPolicy();
+ Policy.RoamingPartner partner = new Policy.RoamingPartner();
+ partner.fqdn = "test.com";
+ partner.fqdnExactMatch = true;
+ partner.priority = 12;
+ policy.preferredRoamingPartnerList.add(partner);
+ assertFalse(policy.validate());
+ }
+
+ /**
+ * Verify that a policy with a proto-port tuple that contains an invalid port string is
+ * invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validatePolicyWithInvalidPortStringInProtoPortMap() throws Exception {
+ Policy policy = createPolicy();
+ byte[] rawPortBytes = new byte[MAX_PORT_STRING_BYTES + 1];
+ policy.requiredProtoPortMap.put(324, new String(rawPortBytes, StandardCharsets.UTF_8));
+ assertFalse(policy.validate());
+ }
+
+ /**
+ * Verify that a policy with number of excluded SSIDs exceeded the max is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validatePolicyWithSsidExclusionListSizeExceededMax() throws Exception {
+ Policy policy = createPolicy();
+ policy.excludedSsidList = new String[MAX_NUMBER_OF_EXCLUDED_SSIDS + 1];
+ Arrays.fill(policy.excludedSsidList, "ssid");
+ assertFalse(policy.validate());
+ }
+
+ /**
+ * Verify that a policy with an invalid SSID in the excluded SSID list is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validatePolicyWithInvalidSsid() throws Exception {
+ Policy policy = createPolicy();
+ byte[] rawSsidBytes = new byte[MAX_SSID_BYTES + 1];
+ Arrays.fill(rawSsidBytes, (byte) 'a');
+ policy.excludedSsidList = new String[] {new String(rawSsidBytes, StandardCharsets.UTF_8)};
+ assertFalse(policy.validate());
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/UpdateParameterTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/UpdateParameterTest.java
new file mode 100644
index 0000000..6bf0db1b
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/UpdateParameterTest.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2017 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.wifi.hotspot2.pps;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
+
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link android.net.wifi.hotspot2.pps.UpdateParameter}.
+ */
+@SmallTest
+public class UpdateParameterTest {
+ private static final int MAX_URI_BYTES = 1023;
+ private static final int MAX_URL_BYTES = 1023;
+ private static final int MAX_USERNAME_BYTES = 63;
+ private static final int MAX_PASSWORD_BYTES = 255;
+ private static final int CERTIFICATE_SHA256_BYTES = 32;
+
+ /**
+ * Helper function for creating a {@link UpdateParameter} for testing.
+ *
+ * @return {@link UpdateParameter}
+ */
+ private static UpdateParameter createUpdateParameter() {
+ UpdateParameter updateParam = new UpdateParameter();
+ updateParam.updateIntervalInMinutes = 1712;
+ updateParam.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+ updateParam.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+ updateParam.serverUri = "server.pdate.com";
+ updateParam.username = "username";
+ updateParam.base64EncodedPassword =
+ Base64.encodeToString("password".getBytes(), Base64.DEFAULT);
+ updateParam.trustRootCertUrl = "trust.cert.com";
+ updateParam.trustRootCertSha256Fingerprint = new byte[32];
+ return updateParam;
+ }
+
+ /**
+ * Helper function for verifying UpdateParameter after parcel write then read.
+ * @param paramToWrite The UpdateParamter to verify
+ * @throws Exception
+ */
+ private static void verifyParcel(UpdateParameter paramToWrite) throws Exception {
+ Parcel parcel = Parcel.obtain();
+ paramToWrite.writeToParcel(parcel, 0);
+
+ parcel.setDataPosition(0); // Rewind data position back to the beginning for read.
+ UpdateParameter paramFromRead = UpdateParameter.CREATOR.createFromParcel(parcel);
+ assertTrue(paramFromRead.equals(paramToWrite));
+ }
+
+ /**
+ * Verify parcel read/write for an empty UpdateParameter.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithEmptyUpdateParameter() throws Exception {
+ verifyParcel(new UpdateParameter());
+ }
+
+ /**
+ * Verify parcel read/write for a UpdateParameter with all fields set.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithFullUpdateParameter() throws Exception {
+ verifyParcel(createUpdateParameter());
+ }
+
+ /**
+ * Verify that UpdateParameter created using copy constructor with null source should be the
+ * same as the UpdateParameter created using default constructor.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyCopyConstructionWithNullSource() throws Exception {
+ UpdateParameter copyParam = new UpdateParameter(null);
+ UpdateParameter defaultParam = new UpdateParameter();
+ assertTrue(defaultParam.equals(copyParam));
+ }
+
+ /**
+ * Verify that UpdateParameter created using copy constructor with a valid source should be the
+ * same as the source.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyCopyConstructionWithFullUpdateParameter() throws Exception {
+ UpdateParameter origParam = createUpdateParameter();
+ UpdateParameter copyParam = new UpdateParameter(origParam);
+ assertTrue(origParam.equals(copyParam));
+ }
+
+ /**
+ * Verify that a default UpdateParameter is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithDefault() throws Exception {
+ UpdateParameter updateParam = new UpdateParameter();
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter created using {@link #createUpdateParameter} is valid,
+ * since all fields are filled in with valid values.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithFullPolicy() throws Exception {
+ assertTrue(createUpdateParameter().validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with an unknown update method is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithUnknowMethod() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ updateParam.updateMethod = "adsfasd";
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with an unknown restriction is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithUnknowRestriction() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ updateParam.restriction = "adsfasd";
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with an username exceeding maximum size is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithUsernameExceedingMaxSize() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ byte[] rawUsernameBytes = new byte[MAX_USERNAME_BYTES + 1];
+ Arrays.fill(rawUsernameBytes, (byte) 'a');
+ updateParam.username = new String(rawUsernameBytes, StandardCharsets.UTF_8);
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with an empty username is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithEmptyUsername() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ updateParam.username = null;
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with a password exceeding maximum size is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithPasswordExceedingMaxSize() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ byte[] rawPasswordBytes = new byte[MAX_PASSWORD_BYTES + 1];
+ Arrays.fill(rawPasswordBytes, (byte) 'a');
+ updateParam.base64EncodedPassword = new String(rawPasswordBytes, StandardCharsets.UTF_8);
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with an empty password is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithEmptyPassword() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ updateParam.base64EncodedPassword = null;
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with a Base64 encoded password that contained invalid padding
+ * is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithPasswordContainedInvalidPadding() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ updateParam.base64EncodedPassword = updateParam.base64EncodedPassword + "=";
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter without trust root certificate URL is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithoutTrustRootCertUrl() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ updateParam.trustRootCertUrl = null;
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with invalid trust root certificate URL is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithInvalidTrustRootCertUrl() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ byte[] rawUrlBytes = new byte[MAX_URL_BYTES + 1];
+ Arrays.fill(rawUrlBytes, (byte) 'a');
+ updateParam.trustRootCertUrl = new String(rawUrlBytes, StandardCharsets.UTF_8);
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter without trust root certificate SHA-256 fingerprint is
+ * invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithouttrustRootCertSha256Fingerprint() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ updateParam.trustRootCertSha256Fingerprint = null;
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with an incorrect size trust root certificate SHA-256
+ * fingerprint is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithInvalidtrustRootCertSha256Fingerprint() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ updateParam.trustRootCertSha256Fingerprint = new byte[CERTIFICATE_SHA256_BYTES + 1];
+ assertFalse(updateParam.validate());
+
+ updateParam.trustRootCertSha256Fingerprint = new byte[CERTIFICATE_SHA256_BYTES - 1];
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter without server URI is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithoutServerUri() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ updateParam.serverUri = null;
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with an invalid server URI is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validatePolicyWithInvalidServerUri() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ byte[] rawUriBytes = new byte[MAX_URI_BYTES + 1];
+ Arrays.fill(rawUriBytes, (byte) 'a');
+ updateParam.serverUri = new String(rawUriBytes, StandardCharsets.UTF_8);
+ assertFalse(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with update interval set to "never" will not perform
+ * validation on other parameters, since update is not applicable in this case.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithNoServerCheck() throws Exception {
+ UpdateParameter updateParam = new UpdateParameter();
+ updateParam.updateIntervalInMinutes = UpdateParameter.UPDATE_CHECK_INTERVAL_NEVER;
+ updateParam.username = null;
+ updateParam.base64EncodedPassword = null;
+ updateParam.updateMethod = null;
+ updateParam.restriction = null;
+ updateParam.serverUri = null;
+ updateParam.trustRootCertUrl = null;
+ updateParam.trustRootCertSha256Fingerprint = null;
+ assertTrue(updateParam.validate());
+ }
+
+ /**
+ * Verify that an UpdateParameter with unset update interval is invalid.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateUpdateParameterWithoutUpdateInterval() throws Exception {
+ UpdateParameter updateParam = createUpdateParameter();
+ updateParam.updateIntervalInMinutes = Long.MIN_VALUE;
+ assertFalse(updateParam.validate());
+ }
+}