Merge changes from topic 'remove-idmap-adamlesinski'
* changes:
Fix memory leak during idmap creation
installd: add command 'removeIdmap'
diff --git a/api/current.txt b/api/current.txt
index 16cfff2..072d4bb 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -14208,6 +14208,7 @@
public final class Sensor {
method public int getFifoMaxEventCount();
method public int getFifoReservedEventCount();
+ method public int getHighestDirectReportRateLevel();
method public int getId();
method public int getMaxDelay();
method public float getMaximumRange();
@@ -14221,6 +14222,7 @@
method public java.lang.String getVendor();
method public int getVersion();
method public boolean isAdditionalInfoSupported();
+ method public boolean isDirectChannelTypeSupported(int);
method public boolean isDynamicSensor();
method public boolean isWakeUpSensor();
field public static final int REPORTING_MODE_CONTINUOUS = 0; // 0x0
@@ -14298,6 +14300,17 @@
field public final int type;
}
+ public final class SensorDirectChannel implements java.lang.AutoCloseable {
+ method public void close();
+ method public boolean isValid();
+ field public static final int RATE_FAST = 2; // 0x2
+ field public static final int RATE_NORMAL = 1; // 0x1
+ field public static final int RATE_STOP = 0; // 0x0
+ field public static final int RATE_VERY_FAST = 3; // 0x3
+ field public static final int TYPE_ASHMEM = 1; // 0x1
+ field public static final int TYPE_HARDWARE_BUFFER = 2; // 0x2
+ }
+
public class SensorEvent {
field public int accuracy;
field public android.hardware.Sensor sensor;
@@ -14329,6 +14342,9 @@
public abstract class SensorManager {
method public boolean cancelTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor);
+ method public int configureDirectChannel(android.hardware.SensorDirectChannel, android.hardware.Sensor, int);
+ method public android.hardware.SensorDirectChannel createDirectChannel(android.os.MemoryFile);
+ method public android.hardware.SensorDirectChannel createDirectChannel(android.hardware.HardwareBuffer);
method public boolean flush(android.hardware.SensorEventListener);
method public static float getAltitude(float, float);
method public static void getAngleChange(float[], float[], float[]);
diff --git a/api/system-current.txt b/api/system-current.txt
index 066f9ce..8a18190 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -14766,6 +14766,7 @@
public final class Sensor {
method public int getFifoMaxEventCount();
method public int getFifoReservedEventCount();
+ method public int getHighestDirectReportRateLevel();
method public int getId();
method public int getMaxDelay();
method public float getMaximumRange();
@@ -14781,6 +14782,7 @@
method public int getVersion();
method public boolean isAdditionalInfoSupported();
method public boolean isDataInjectionSupported();
+ method public boolean isDirectChannelTypeSupported(int);
method public boolean isDynamicSensor();
method public boolean isWakeUpSensor();
field public static final int REPORTING_MODE_CONTINUOUS = 0; // 0x0
@@ -14862,6 +14864,17 @@
field public final int type;
}
+ public final class SensorDirectChannel implements java.lang.AutoCloseable {
+ method public void close();
+ method public boolean isValid();
+ field public static final int RATE_FAST = 2; // 0x2
+ field public static final int RATE_NORMAL = 1; // 0x1
+ field public static final int RATE_STOP = 0; // 0x0
+ field public static final int RATE_VERY_FAST = 3; // 0x3
+ field public static final int TYPE_ASHMEM = 1; // 0x1
+ field public static final int TYPE_HARDWARE_BUFFER = 2; // 0x2
+ }
+
public class SensorEvent {
field public int accuracy;
field public android.hardware.Sensor sensor;
@@ -14893,6 +14906,9 @@
public abstract class SensorManager {
method public boolean cancelTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor);
+ method public int configureDirectChannel(android.hardware.SensorDirectChannel, android.hardware.Sensor, int);
+ method public android.hardware.SensorDirectChannel createDirectChannel(android.os.MemoryFile);
+ method public android.hardware.SensorDirectChannel createDirectChannel(android.hardware.HardwareBuffer);
method public boolean flush(android.hardware.SensorEventListener);
method public static float getAltitude(float, float);
method public static void getAngleChange(float[], float[], float[]);
diff --git a/api/test-current.txt b/api/test-current.txt
index f0c628c..057d5a4 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -14240,6 +14240,7 @@
public final class Sensor {
method public int getFifoMaxEventCount();
method public int getFifoReservedEventCount();
+ method public int getHighestDirectReportRateLevel();
method public int getId();
method public int getMaxDelay();
method public float getMaximumRange();
@@ -14253,6 +14254,7 @@
method public java.lang.String getVendor();
method public int getVersion();
method public boolean isAdditionalInfoSupported();
+ method public boolean isDirectChannelTypeSupported(int);
method public boolean isDynamicSensor();
method public boolean isWakeUpSensor();
field public static final int REPORTING_MODE_CONTINUOUS = 0; // 0x0
@@ -14330,6 +14332,17 @@
field public final int type;
}
+ public final class SensorDirectChannel implements java.lang.AutoCloseable {
+ method public void close();
+ method public boolean isValid();
+ field public static final int RATE_FAST = 2; // 0x2
+ field public static final int RATE_NORMAL = 1; // 0x1
+ field public static final int RATE_STOP = 0; // 0x0
+ field public static final int RATE_VERY_FAST = 3; // 0x3
+ field public static final int TYPE_ASHMEM = 1; // 0x1
+ field public static final int TYPE_HARDWARE_BUFFER = 2; // 0x2
+ }
+
public class SensorEvent {
field public int accuracy;
field public android.hardware.Sensor sensor;
@@ -14361,6 +14374,9 @@
public abstract class SensorManager {
method public boolean cancelTriggerSensor(android.hardware.TriggerEventListener, android.hardware.Sensor);
+ method public int configureDirectChannel(android.hardware.SensorDirectChannel, android.hardware.Sensor, int);
+ method public android.hardware.SensorDirectChannel createDirectChannel(android.os.MemoryFile);
+ method public android.hardware.SensorDirectChannel createDirectChannel(android.hardware.HardwareBuffer);
method public boolean flush(android.hardware.SensorEventListener);
method public static float getAltitude(float, float);
method public static void getAngleChange(float[], float[], float[]);
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index 06ab13e..d87c55e 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -729,14 +729,22 @@
private static final int DATA_INJECTION_MASK = 0x10;
private static final int DATA_INJECTION_SHIFT = 4;
- // MASK for dynamic sensor (sensor that added during runtime), bit 6.
+ // MASK for dynamic sensor (sensor that added during runtime), bit 5.
private static final int DYNAMIC_SENSOR_MASK = 0x20;
private static final int DYNAMIC_SENSOR_SHIFT = 5;
- // MASK for indication bit of sensor additional information support (bit 7).
+ // MASK for indication bit of sensor additional information support, bit 6.
private static final int ADDITIONAL_INFO_MASK = 0x40;
private static final int ADDITIONAL_INFO_SHIFT = 6;
+ // Mask for direct mode highest rate level, bit 7, 8, 9.
+ private static final int DIRECT_REPORT_MASK = 0x380;
+ private static final int DIRECT_REPORT_SHIFT = 7;
+
+ // Mask for supported direct channel, bit 10, 11
+ private static final int DIRECT_CHANNEL_MASK = 0xC00;
+ private static final int DIRECT_CHANNEL_SHIFT = 10;
+
// TODO(): The following arrays are fragile and error-prone. This needs to be refactored.
// Note: This needs to be updated, whenever a new sensor is added.
@@ -796,6 +804,42 @@
return ((mFlags & REPORTING_MODE_MASK) >> REPORTING_MODE_SHIFT);
}
+ /**
+ * Get the highest supported direct report mode rate level of the sensor.
+ *
+ * @return Highest direct report rate level of this sensor. If the sensor does not support
+ * direct report mode, this returns {@link SensorDirectChannel#RATE_STOP}.
+ * @see SensorDirectChannel#RATE_STOP
+ * @see SensorDirectChannel#RATE_NORMAL
+ * @see SensorDirectChannel#RATE_FAST
+ * @see SensorDirectChannel#RATE_VERY_FAST
+ */
+ @SensorDirectChannel.RateLevel
+ public int getHighestDirectReportRateLevel() {
+ int rateLevel = ((mFlags & DIRECT_REPORT_MASK) >> DIRECT_REPORT_SHIFT);
+ return rateLevel <= SensorDirectChannel.RATE_VERY_FAST
+ ? rateLevel : SensorDirectChannel.RATE_VERY_FAST;
+ }
+
+ /**
+ * Test if sensor support direct channel backed by a specific type of shared memory.
+ *
+ * @param sharedMemType type of shared memory used by direct channel.
+ * @return <code>true</code> if the shared memory type is supported.
+ * @see SensorDirectChannel#TYPE_ASHMEM
+ * @see SensorDirectChannel#TYPE_HARDWARE_BUFFER
+ */
+ public boolean isDirectChannelTypeSupported(@SensorDirectChannel.MemoryType int sharedMemType) {
+ switch (sharedMemType) {
+ case SensorDirectChannel.TYPE_ASHMEM:
+ return (mFlags & (1 << DIRECT_CHANNEL_SHIFT)) > 0;
+ case SensorDirectChannel.TYPE_HARDWARE_BUFFER:
+ return (mFlags & (1 << DIRECT_CHANNEL_SHIFT + 1)) > 0;
+ default:
+ return false;
+ }
+ }
+
static int getMaxLengthValuesArray(Sensor sensor, int sdkLevel) {
// RotationVector length has changed to 3 to 5 for API level 18
// Set it to 3 for backward compatibility.
diff --git a/core/java/android/hardware/SensorDirectChannel.java b/core/java/android/hardware/SensorDirectChannel.java
new file mode 100644
index 0000000..0efd62b
--- /dev/null
+++ b/core/java/android/hardware/SensorDirectChannel.java
@@ -0,0 +1,168 @@
+/*
+ * 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.hardware;
+
+import android.annotation.IntDef;
+import android.os.MemoryFile;
+
+import dalvik.system.CloseGuard;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Class representing a sensor direct channel. Use {@link
+ * SensorManager#createDirectChannel(android.os.MemoryFile)} to obtain object.
+ */
+public final class SensorDirectChannel implements AutoCloseable {
+
+ // shared memory types
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, value = {TYPE_ASHMEM, TYPE_HARDWARE_BUFFER})
+ public @interface MemoryType {};
+ /**
+ * Shared memory type ashmem, wrapped in MemoryFile object.
+ *
+ * @see SensorManager#createDirectChannel(MemoryFile)
+ */
+ public static final int TYPE_ASHMEM = 1;
+
+ /**
+ * Shared memory type wrapped by HardwareBuffer object.
+ *
+ * @see SensorManager#createDirectChannel(HardwareBuffer)
+ */
+ public static final int TYPE_HARDWARE_BUFFER = 2;
+
+ // sensor rate levels
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, value = {RATE_STOP, RATE_NORMAL, RATE_FAST, RATE_VERY_FAST})
+ public @interface RateLevel {};
+
+ /**
+ * Sensor stopped (no event output).
+ *
+ * @see SensorManager#configureDirectChannel(SensorDirectChannel, Sensor, int)
+ */
+ public static final int RATE_STOP = 0;
+ /**
+ * Sensor operates at nominal rate of 50Hz.
+ *
+ * The actual rate is expected to be between 55% to 220% of nominal rate, thus between 27.5Hz to
+ * 110Hz.
+ *
+ * @see SensorManager#configureDirectChannel(SensorDirectChannel, Sensor, int)
+ */
+ public static final int RATE_NORMAL = 1; //50Hz
+ /**
+ * Sensor operates at nominal rate of 200Hz.
+ *
+ * The actual rate is expected to be between 55% to 220% of nominal rate, thus between 110Hz to
+ * 440Hz.
+ *
+ * @see SensorManager#configureDirectChannel(SensorDirectChannel, Sensor, int)
+ */
+ public static final int RATE_FAST = 2; // ~200Hz
+ /**
+ * Sensor operates at nominal rate of 800Hz.
+ *
+ * The actual rate is expected to be between 55% to 220% of nominal rate, thus between 440Hz to
+ * 1760Hz.
+ *
+ * @see SensorManager#configureDirectChannel(SensorDirectChannel, Sensor, int)
+ */
+ public static final int RATE_VERY_FAST = 3; // ~800Hz
+
+ /**
+ * Determine if a channel is still valid. A channel is invalidated after {@link #close()} is
+ * called.
+ *
+ * @return <code>true</code> if channel is valid.
+ */
+ public boolean isValid() {
+ return !mClosed.get();
+ }
+
+ /**
+ * Close sensor direct channel.
+ *
+ * Stop all active sensor in the channel and free sensor system resource related to channel.
+ * Shared memory used for creating the direct channel need to be closed or freed separately.
+ *
+ * @see SensorManager#createDirectChannel(MemoryFile)
+ * @see SensorManager#createDirectChannel(HardwareBuffer)
+ */
+ @Override
+ public void close() {
+ mCloseGuard.close();
+ if (mClosed.compareAndSet(false, true)) {
+ // actual close action
+ mManager.destroyDirectChannel(this);
+ }
+ }
+
+ /** @hide */
+ SensorDirectChannel(SensorManager manager, int id, int type, long size) {
+ mManager = manager;
+ mNativeHandle = id;
+ mType = type;
+ mSize = size;
+ mCloseGuard.open("SensorDirectChannel");
+ }
+
+ /** @hide */
+ int getNativeHandle() {
+ return mNativeHandle;
+ }
+
+ /**
+ * This function encode handle information in {@link android.os.Memory} into a long array to be
+ * passed down to native methods.
+ *
+ * @hide */
+ static long[] encodeData(MemoryFile ashmem) {
+ int fd;
+ try {
+ fd = ashmem.getFileDescriptor().getInt$();
+ } catch (IOException e) {
+ fd = -1;
+ }
+ return new long[] { 1 /*numFds*/, 0 /*numInts*/, fd };
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ mCloseGuard.warnIfOpen();
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private final AtomicBoolean mClosed = new AtomicBoolean();
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final SensorManager mManager;
+ private final int mNativeHandle;
+ private final long mSize;
+ private final int mType;
+}
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index 3ccac69..cfda2f4 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -19,6 +19,7 @@
import android.annotation.SystemApi;
import android.os.Build;
import android.os.Handler;
+import android.os.MemoryFile;
import android.util.Log;
import android.util.SparseArray;
@@ -881,6 +882,104 @@
/**
+ * Create a sensor direct channel backed by shared memory wrapped by MemoryFile object.
+ *
+ * Use the returned {@link android.hardware.SensorDirectChannel} object to configure direct
+ * report of sensor events. After use, call {@link android.hardware.SensorDirectChannel#close()}
+ * to free up resource in sensor system associated with the direct channel.
+ *
+ * @param mem A {@link android.os.MemoryFile} shared memory object.
+ * @return A {@link android.hardware.SensorDirectChannel} object if successful, null otherwise.
+ * @throws IllegalArgumentException when mem is null.
+ * @see SensorDirectChannel#close()
+ * @see #configureDirectChannel(SensorDirectChannel, Sensor, int)
+ */
+ public SensorDirectChannel createDirectChannel(MemoryFile mem) {
+ return createDirectChannelImpl(mem.length(), mem, null);
+ }
+
+ /**
+ * Create a sensor direct channel backed by shared memory wrapped by HardwareBuffer object.
+ *
+ * Use the returned {@link android.hardware.SensorDirectChannel} object to configure direct
+ * report of sensor events. After use, call {@link android.hardware.SensorDirectChannel#close()}
+ * to free up resource in sensor system associated with the direct channel.
+ *
+ * @param mem A {@link android.hardware.HardwareBuffer} shared memory object.
+ * @return A {@link android.hardware.SensorDirectChannel} object if successful,
+ * null otherwise.
+ * @throws IllegalArgumentException when mem is null.
+ * @see SensorDirectChannel#close()
+ * @see #configureDirectChannel(SensorDirectChannel, Sensor, int)
+ */
+ public SensorDirectChannel createDirectChannel(HardwareBuffer mem) {
+ return null;
+ }
+
+ /** @hide */
+ protected abstract SensorDirectChannel createDirectChannelImpl(long size,
+ MemoryFile ashmemFile, HardwareBuffer hardwareBuffer);
+
+ /** @hide */
+ void destroyDirectChannel(SensorDirectChannel channel) {
+ destroyDirectChannelImpl(channel);
+ }
+
+ /** @hide */
+ protected abstract void destroyDirectChannelImpl(SensorDirectChannel channel);
+
+ /**
+ * Configure sensor rate or stop sensor report on a direct report channel specified.
+ *
+ * To start event report of a sensor, or change rate of existing report, call this function with
+ * rateLevel other than {@link android.hardware.SensorDirectChannel#RATE_STOP}. Sensor events
+ * will be added into a queue formed by the shared memory used in creation of direction channel.
+ * Each element of the queue has size of 104 bytes and represents a sensor event. Data
+ * structure of an element (all fields in little-endian):
+ *
+ * offset type name
+ *- ---------------------------------------------
+ * 0x0000 int32_t size (always 104)
+ * 0x0004 int32_t sensor report token
+ * 0x0008 int32_t type (see SensorType)
+ * 0x000C uint32_t atomic counter
+ * 0x0010 int64_t timestamp (see Event)
+ * 0x0018 float[16]/int64_t[8] data (data type depends on sensor type)
+ * 0x0058 int32_t[4] reserved (set to zero)
+ *
+ * There is no head or tail pointers. The sequence and frontier of new sensor events is
+ * determined by the atomic counter, which counts from 1 after creation of direct channel and
+ * increments 1 for each new event. The writer in sensor system will wrap around from to
+ * start of shared memory region when it reaches the end. If size of memory region is not
+ * a multiple of size of element (104 bytes), the residual is not used at the end.
+ * Function returns a positive sensor report token on success. This token can be used for
+ * differentiate sensor events from multiple sensor of the same type. For example, if there are
+ * two accelerometer in the system A and B, it is guaranteed different report tokens will be
+ * returned when starting sensor A and B.
+ *
+ * To stop a sensor, call this function with rateLevel equal {@link
+ * android.hardware.SensorDirectChannel#RATE_STOP}. If the sensor parameter is left to be null,
+ * this will stop all active sensor report associated with the direct channel specified.
+ * Function return 1 on success or 0 on failure.
+ *
+ * @param channel A {@link android.hardware.SensorDirectChannel} object representing direct
+ * channel to be configured.
+ * @param sensor A {@link android.hardware.Sensor} object to denote sensor to be operated.
+ * @param rateLevel rate level defined in {@link android.hardware.SensorDirectChannel}.
+ * @return starting report or changing rate: positive sensor report token on success, 0 on failure;
+ * stopping report: 1 on success, 0 on failure.
+ * @throws IllegalArgumentException when SensorDirectChannel is null.
+ */
+ public int configureDirectChannel(SensorDirectChannel channel, Sensor sensor,
+ @SensorDirectChannel.RateLevel int rateLevel) {
+ return configureDirectChannelImpl(channel, sensor, rateLevel);
+ }
+
+ /** @hide */
+ protected abstract int configureDirectChannelImpl(
+ SensorDirectChannel channel, Sensor s, int rate);
+
+ /**
* Used for receiving notifications from the SensorManager when dynamic sensors are connected or
* disconnected.
*/
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 259ca03..4992def 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -24,6 +24,7 @@
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper;
+import android.os.MemoryFile;
import android.os.MessageQueue;
import android.util.Log;
import android.util.SparseArray;
@@ -57,6 +58,13 @@
private static native void nativeGetDynamicSensors(long nativeInstance, List<Sensor> list);
private static native boolean nativeIsDataInjectionEnabled(long nativeInstance);
+ private static native int nativeCreateDirectChannel(
+ long nativeInstance, long size, int channelType, long [] channelData);
+ private static native void nativeDestroyDirectChannel(
+ long nativeInstance, int channelHandle);
+ private static native int nativeConfigDirectChannel(
+ long nativeInstance, int channelHandle, int sensorHandle, int rate);
+
private static final Object sLock = new Object();
@GuardedBy("sLock")
private static boolean sNativeClassInited = false;
@@ -484,6 +492,71 @@
return changed;
}
+ /** @hide */
+ protected int configureDirectChannelImpl(
+ SensorDirectChannel channel, Sensor sensor, int rate) {
+ if (channel == null) throw new IllegalArgumentException("channel cannot be null");
+
+ if (!channel.isValid()) {
+ throw new IllegalStateException("channel is invalid");
+ }
+
+ if (rate < SensorDirectChannel.RATE_STOP
+ || rate > SensorDirectChannel.RATE_VERY_FAST) {
+ throw new IllegalArgumentException("rate parameter invalid");
+ }
+
+ if (sensor == null && rate != SensorDirectChannel.RATE_STOP) {
+ // the stop all sensors case
+ throw new IllegalArgumentException(
+ "when sensor is null, rate can only be DIRECT_RATE_STOP");
+ }
+
+ int sensorHandle = (sensor == null) ? -1 : sensor.getHandle();
+
+ int ret = nativeConfigDirectChannel(
+ mNativeInstance, channel.getNativeHandle(), sensorHandle, rate);
+
+ if (rate == SensorDirectChannel.RATE_STOP) {
+ return (ret == 0) ? 1 : 0;
+ } else {
+ return (ret > 0) ? ret : 0;
+ }
+ }
+
+ /** @hide */
+ protected SensorDirectChannel createDirectChannelImpl(long size,
+ MemoryFile ashmemFile, HardwareBuffer grallocMemObject) {
+ SensorDirectChannel ch = null;
+
+ if (size <= 0) throw new IllegalArgumentException("size has to be greater than 0");
+
+ if (ashmemFile != null) {
+ if (size != ashmemFile.length()) {
+ throw new IllegalArgumentException("size has to match MemoryFile.length()");
+ }
+ int id = nativeCreateDirectChannel(
+ mNativeInstance, size, SensorDirectChannel.TYPE_ASHMEM,
+ SensorDirectChannel.encodeData(ashmemFile));
+ if (id > 0) {
+ ch = new SensorDirectChannel(this, id, SensorDirectChannel.TYPE_ASHMEM, size);
+ }
+ } else if (grallocMemObject != null) {
+ Log.wtf(TAG, "Implement GRALLOC or remove GRALLOC support entirely");
+ } else {
+ throw new IllegalArgumentException("Invalid parameter");
+ }
+
+ return ch;
+ }
+
+ /** @hide */
+ protected void destroyDirectChannelImpl(SensorDirectChannel channel) {
+ if (channel != null) {
+ nativeDestroyDirectChannel(mNativeInstance, channel.getNativeHandle());
+ }
+ }
+
/*
* BaseEventQueue is the communication channel with the sensor service,
* SensorEventQueue, TriggerEventQueue are subclases and there is one-to-one mapping between
diff --git a/core/java/android/net/NetworkKey.java b/core/java/android/net/NetworkKey.java
index 1a128e0..e5f0bf0 100644
--- a/core/java/android/net/NetworkKey.java
+++ b/core/java/android/net/NetworkKey.java
@@ -16,10 +16,14 @@
package android.net;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.net.wifi.ScanResult;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiSsid;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import java.util.Objects;
@@ -65,6 +69,27 @@
}
/**
+ * Constructs a new NetworkKey for the given {@link WifiInfo}.
+ *
+ * @param wifiInfo the {@link WifiInfo} to create a {@link NetworkKey} for.
+ * @return A new {@link NetworkKey} instance or <code>null</code> if the given {@link WifiInfo}
+ * instance doesn't represent a connected WiFi network.
+ * @hide
+ */
+ @Nullable
+ public static NetworkKey createFromWifiInfo(@Nullable WifiInfo wifiInfo) {
+ if (wifiInfo != null) {
+ final String ssid = wifiInfo.getSSID();
+ final String bssid = wifiInfo.getBSSID();
+ if (!TextUtils.isEmpty(ssid) && !ssid.equals(WifiSsid.NONE)
+ && !TextUtils.isEmpty(bssid)) {
+ return new NetworkKey(new WifiKey(ssid, bssid));
+ }
+ }
+ return null;
+ }
+
+ /**
* Construct a new {@link NetworkKey} for a Wi-Fi network.
* @param wifiKey the {@link WifiKey} identifying this Wi-Fi network.
*/
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index 271f24b..bc2fc1c 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -26,6 +26,7 @@
#include <gui/Sensor.h>
#include <gui/SensorEventQueue.h>
#include <gui/SensorManager.h>
+#include <cutils/native_handle.h>
#include <utils/Log.h>
#include <utils/Looper.h>
#include <utils/Vector.h>
@@ -243,6 +244,54 @@
return mgr->isDataInjectionEnabled();
}
+static jint nativeCreateDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager,
+ jlong size, jint channelType, jlongArray channelData) {
+ jint ret = -1;
+ jsize len = _env->GetArrayLength(channelData);
+ if (len > 2) {
+ jlong *data = _env->GetLongArrayElements(channelData, NULL);
+ if (data != nullptr) {
+ // construct native handle from jlong*
+ jlong numFd = data[0];
+ jlong numInt = data[1];
+ if ((numFd + numInt + 2) == len) {
+ native_handle_t *nativeHandle = native_handle_create(numFd, numInt);
+ if (nativeHandle != nullptr) {
+ const jlong *readPointer = data + 2;
+ int *writePointer = nativeHandle->data;
+ size_t n = static_cast<size_t>(numFd + numInt);
+ while (n--) {
+ // native type of data is int, jlong is just to ensure Java does not
+ // truncate data on 64-bit system. The cast here is safe.
+ *writePointer++ = static_cast<int>(*readPointer++);
+ }
+
+ SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
+ ret = mgr->createDirectChannel(size, channelType, nativeHandle);
+
+ // do not native_handle_close() here as handle is owned by java
+ native_handle_delete(nativeHandle);
+ }
+ }
+ // unidirectional parameter passing, thus JNI_ABORT
+ _env->ReleaseLongArrayElements(channelData, data, JNI_ABORT);
+ }
+ }
+ return ret;
+}
+
+static void nativeDestroyDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager,
+ jint channelHandle) {
+ SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
+ mgr->destroyDirectChannel(channelHandle);
+}
+
+static jint nativeConfigDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager,
+ jint channelHandle, jint sensorHandle, jint rate) {
+ SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
+ return mgr->configureDirectChannel(channelHandle, sensorHandle, rate);
+}
+
//----------------------------------------------------------------------------
class Receiver : public LooperCallback {
@@ -447,7 +496,19 @@
{"nativeIsDataInjectionEnabled",
"(J)Z",
- (void*)nativeIsDataInjectionEnabled},
+ (void*)nativeIsDataInjectionEnabled },
+
+ {"nativeCreateDirectChannel",
+ "(JJI[J)I",
+ (void*)nativeCreateDirectChannel },
+
+ {"nativeDestroyDirectChannel",
+ "(JI)V",
+ (void*)nativeDestroyDirectChannel },
+
+ {"nativeConfigDirectChannel",
+ "(JIII)I",
+ (void*)nativeConfigDirectChannel },
};
static const JNINativeMethod gBaseEventQueueMethods[] = {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 122bde1..6a8b556 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2625,8 +2625,14 @@
<!-- Component that is the default launcher when demo mode is enabled. -->
<string name="config_demoModeLauncherComponent">com.android.retaildemo/.DemoPlayer</string>
- <!-- Hashed password (SHA-256) used to restrict demo mode operation -->
- <string name="config_demoModePassword" translatable="false"></string>
+ <!-- Hashed password (SHA-256) used to restrict carrier demo mode operation. -->
+ <string name="config_carrierDemoModePassword" translatable="false"></string>
+
+ <!-- Secure setting used to activate carrier demo mode. -->
+ <string name="config_carrierDemoModeSetting" translatable="false"></string>
+
+ <!-- List of packages to enable in carrier demo mode (comma separated). -->
+ <string name="config_carrierDemoModePackages" translatable="false"></string>
<!-- Flag indicating whether round icons should be parsed from the application manifest. -->
<bool name="config_useRoundIcon">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b572dd0..58c925f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1136,7 +1136,9 @@
<java-symbol type="string" name="config_ethernet_tcp_buffers" />
<java-symbol type="string" name="config_wifi_tcp_buffers" />
<java-symbol type="string" name="config_demoModeLauncherComponent" />
- <java-symbol type="string" name="config_demoModePassword" />
+ <java-symbol type="string" name="config_carrierDemoModePassword" />
+ <java-symbol type="string" name="config_carrierDemoModeSetting" />
+ <java-symbol type="string" name="config_carrierDemoModePackages" />
<java-symbol type="string" name="demo_starting_message" />
<java-symbol type="string" name="demo_restarting_message" />
<java-symbol type="string" name="conference_call" />
diff --git a/core/tests/coretests/src/android/net/NetworkKeyTest.java b/core/tests/coretests/src/android/net/NetworkKeyTest.java
new file mode 100644
index 0000000..1afe9da
--- /dev/null
+++ b/core/tests/coretests/src/android/net/NetworkKeyTest.java
@@ -0,0 +1,75 @@
+package android.net;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.when;
+
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiSsid;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class NetworkKeyTest {
+ private static final String VALID_SSID = "\"ssid1\"";
+ private static final String VALID_BSSID = "00:00:00:00:00:00";
+ @Mock private WifiInfo mWifiInfo;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void createFromWifi_nullInput() throws Exception {
+ assertNull(NetworkKey.createFromWifiInfo(null));
+ }
+
+ @Test
+ public void createFromWifi_nullSsid() throws Exception {
+ when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID);
+ assertNull(NetworkKey.createFromWifiInfo(mWifiInfo));
+ }
+
+ @Test
+ public void createFromWifi_emptySsid() throws Exception {
+ when(mWifiInfo.getSSID()).thenReturn("");
+ when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID);
+ assertNull(NetworkKey.createFromWifiInfo(mWifiInfo));
+ }
+
+ @Test
+ public void createFromWifi_noneSsid() throws Exception {
+ when(mWifiInfo.getSSID()).thenReturn(WifiSsid.NONE);
+ when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID);
+ assertNull(NetworkKey.createFromWifiInfo(mWifiInfo));
+ }
+
+ @Test
+ public void createFromWifi_nullBssid() throws Exception {
+ when(mWifiInfo.getSSID()).thenReturn(VALID_SSID);
+ assertNull(NetworkKey.createFromWifiInfo(mWifiInfo));
+ }
+
+ @Test
+ public void createFromWifi_emptyBssid() throws Exception {
+ when(mWifiInfo.getSSID()).thenReturn(VALID_SSID);
+ when(mWifiInfo.getBSSID()).thenReturn("");
+ assertNull(NetworkKey.createFromWifiInfo(mWifiInfo));
+ }
+
+ @Test
+ public void createFromWifi_validWifiInfo() throws Exception {
+ when(mWifiInfo.getSSID()).thenReturn(VALID_SSID);
+ when(mWifiInfo.getBSSID()).thenReturn(VALID_BSSID);
+
+ NetworkKey expected = new NetworkKey(new WifiKey(VALID_SSID, VALID_BSSID));
+ final NetworkKey actual = NetworkKey.createFromWifiInfo(mWifiInfo);
+ assertEquals(expected, actual);
+ }
+}
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index e8ecc3e..dab4dfb 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -41,6 +41,10 @@
import android.net.RecommendationResult;
import android.net.ScoredNetwork;
import android.net.Uri;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiScanner;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -76,7 +80,9 @@
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Consumer;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
/**
* Backing service for {@link android.net.NetworkScoreManager}.
@@ -391,6 +397,7 @@
isEmpty = callbackList == null
|| callbackList.getRegisteredCallbackCount() == 0;
}
+
if (isEmpty) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "No scorer registered for type " + entry.getKey()
@@ -399,18 +406,10 @@
continue;
}
- sendCallback(new Consumer<INetworkScoreCache>() {
- @Override
- public void accept(INetworkScoreCache networkScoreCache) {
- try {
- networkScoreCache.updateScores(entry.getValue());
- } catch (RemoteException e) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
- }
- }
- }
- }, Collections.singleton(callbackList));
+ final BiConsumer<INetworkScoreCache, Object> consumer =
+ new FilteringCacheUpdatingConsumer(mContext, entry.getValue(),
+ entry.getKey());
+ sendCacheUpdateCallback(consumer, Collections.singleton(callbackList));
}
return true;
@@ -419,6 +418,229 @@
}
}
+ /**
+ * A {@link BiConsumer} implementation that filters the given {@link ScoredNetwork}
+ * list (if needed) before invoking {@link INetworkScoreCache#updateScores(List)} on the
+ * accepted {@link INetworkScoreCache} implementation.
+ */
+ @VisibleForTesting
+ public static class FilteringCacheUpdatingConsumer
+ implements BiConsumer<INetworkScoreCache, Object> {
+ private final Context mContext;
+ private final List<ScoredNetwork> mScoredNetworkList;
+ private final int mNetworkType;
+ // TODO(jjoslin): 1/23/17 - Consider a Map if we implement more filters.
+ private Function<List<ScoredNetwork>, List<ScoredNetwork>> mCurrentNetworkFilter;
+ private Function<List<ScoredNetwork>, List<ScoredNetwork>> mScanResultsFilter;
+
+ public FilteringCacheUpdatingConsumer(Context context,
+ List<ScoredNetwork> scoredNetworkList, int networkType) {
+ this(context, scoredNetworkList, networkType, null, null);
+ }
+
+ @VisibleForTesting
+ public FilteringCacheUpdatingConsumer(Context context,
+ List<ScoredNetwork> scoredNetworkList, int networkType,
+ Function<List<ScoredNetwork>, List<ScoredNetwork>> currentNetworkFilter,
+ Function<List<ScoredNetwork>, List<ScoredNetwork>> scanResultsFilter) {
+ mContext = context;
+ mScoredNetworkList = scoredNetworkList;
+ mNetworkType = networkType;
+ mCurrentNetworkFilter = currentNetworkFilter;
+ mScanResultsFilter = scanResultsFilter;
+ }
+
+ @Override
+ public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
+ int filterType = NetworkScoreManager.CACHE_FILTER_NONE;
+ if (cookie instanceof Integer) {
+ filterType = (Integer) cookie;
+ }
+
+ try {
+ final List<ScoredNetwork> filteredNetworkList =
+ filterScores(mScoredNetworkList, filterType);
+ if (!filteredNetworkList.isEmpty()) {
+ networkScoreCache.updateScores(
+ Collections.unmodifiableList(filteredNetworkList));
+ }
+ } catch (RemoteException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Unable to update scores of type " + mNetworkType, e);
+ }
+ }
+ }
+
+ /**
+ * Applies the appropriate filter and returns the filtered results.
+ */
+ private List<ScoredNetwork> filterScores(List<ScoredNetwork> scoredNetworkList,
+ int filterType) {
+ switch (filterType) {
+ case NetworkScoreManager.CACHE_FILTER_NONE:
+ return scoredNetworkList;
+
+ case NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK:
+ if (mCurrentNetworkFilter == null) {
+ mCurrentNetworkFilter =
+ new CurrentNetworkScoreCacheFilter(new WifiInfoSupplier(mContext));
+ }
+ return mCurrentNetworkFilter.apply(scoredNetworkList);
+
+ case NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS:
+ if (mScanResultsFilter == null) {
+ mScanResultsFilter = new ScanResultsScoreCacheFilter(
+ new ScanResultsSupplier(mContext));
+ }
+ return mScanResultsFilter.apply(scoredNetworkList);
+
+ default:
+ Log.w(TAG, "Unknown filter type: " + filterType);
+ return scoredNetworkList;
+ }
+ }
+ }
+
+ /**
+ * Helper class that improves the testability of the cache filter Functions.
+ */
+ private static class WifiInfoSupplier implements Supplier<WifiInfo> {
+ private final Context mContext;
+
+ WifiInfoSupplier(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public WifiInfo get() {
+ WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
+ if (wifiManager != null) {
+ return wifiManager.getConnectionInfo();
+ }
+ Log.w(TAG, "WifiManager is null, failed to return the WifiInfo.");
+ return null;
+ }
+ }
+
+ /**
+ * Helper class that improves the testability of the cache filter Functions.
+ */
+ private static class ScanResultsSupplier implements Supplier<List<ScanResult>> {
+ private final Context mContext;
+
+ ScanResultsSupplier(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public List<ScanResult> get() {
+ WifiScanner wifiScanner = mContext.getSystemService(WifiScanner.class);
+ if (wifiScanner != null) {
+ return wifiScanner.getSingleScanResults();
+ }
+ Log.w(TAG, "WifiScanner is null, failed to return scan results.");
+ return Collections.emptyList();
+ }
+ }
+
+ /**
+ * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
+ * {@link ScoredNetwork} associated with the current network. If no network is connected the
+ * returned list will be empty.
+ * <p>
+ * Note: this filter performs some internal caching for consistency and performance. The
+ * current network is determined at construction time and never changed. Also, the
+ * last filtered list is saved so if the same input is provided multiple times in a row
+ * the computation is only done once.
+ */
+ @VisibleForTesting
+ public static class CurrentNetworkScoreCacheFilter
+ implements Function<List<ScoredNetwork>, List<ScoredNetwork>> {
+ private final NetworkKey mCurrentNetwork;
+ private Pair<List<ScoredNetwork>, Integer> mCache;
+
+ CurrentNetworkScoreCacheFilter(Supplier<WifiInfo> wifiInfoSupplier) {
+ mCurrentNetwork = NetworkKey.createFromWifiInfo(wifiInfoSupplier.get());
+ }
+
+ @Override
+ public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
+ if (mCurrentNetwork == null || scoredNetworks.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ final int inputListHash = scoredNetworks.hashCode();
+ if (mCache == null || mCache.second != inputListHash) {
+ ScoredNetwork currentScore = null;
+ for (int i = 0; i < scoredNetworks.size(); i++) {
+ final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
+ if (scoredNetwork.networkKey.equals(mCurrentNetwork)) {
+ currentScore = scoredNetwork;
+ break;
+ }
+ }
+
+ if (currentScore == null) {
+ mCache = Pair.create(Collections.emptyList(), inputListHash);
+ } else {
+ mCache = Pair.create(Collections.singletonList(currentScore), inputListHash);
+ }
+ }
+
+ return mCache.first;
+ }
+ }
+
+ /**
+ * Filters the given set of {@link ScoredNetwork}s and returns a new List containing only the
+ * {@link ScoredNetwork} associated with the current set of {@link ScanResult}s.
+ * If there are no {@link ScanResult}s the returned list will be empty.
+ * <p>
+ * Note: this filter performs some internal caching for consistency and performance. The
+ * current set of ScanResults is determined at construction time and never changed.
+ * Also, the last filtered list is saved so if the same input is provided multiple
+ * times in a row the computation is only done once.
+ */
+ @VisibleForTesting
+ public static class ScanResultsScoreCacheFilter
+ implements Function<List<ScoredNetwork>, List<ScoredNetwork>> {
+ private final List<NetworkKey> mScanResultKeys;
+ private Pair<List<ScoredNetwork>, Integer> mCache;
+
+ ScanResultsScoreCacheFilter(Supplier<List<ScanResult>> resultsSupplier) {
+ mScanResultKeys = new ArrayList<>();
+ List<ScanResult> scanResults = resultsSupplier.get();
+ for (int i = 0; i < scanResults.size(); i++) {
+ ScanResult scanResult = scanResults.get(i);
+ mScanResultKeys.add(NetworkKey.createFromScanResult(scanResult));
+ }
+ }
+
+ @Override
+ public List<ScoredNetwork> apply(List<ScoredNetwork> scoredNetworks) {
+ if (mScanResultKeys.isEmpty() || scoredNetworks.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ final int inputListHash = scoredNetworks.hashCode();
+ if (mCache == null || mCache.second != inputListHash) {
+ List<ScoredNetwork> filteredScores = new ArrayList<>();
+ for (int i = 0; i < scoredNetworks.size(); i++) {
+ final ScoredNetwork scoredNetwork = scoredNetworks.get(i);
+ for (int j = 0; j < mScanResultKeys.size(); j++) {
+ final NetworkKey scanResultKey = mScanResultKeys.get(j);
+ if (scanResultKey.equals(scoredNetwork.networkKey)) {
+ filteredScores.add(scoredNetwork);
+ }
+ }
+ }
+ mCache = Pair.create(filteredScores, inputListHash);
+ }
+
+ return mCache.first;
+ }
+ }
+
private boolean isCallerSystemUid() {
// REQUEST_NETWORK_SCORES is a signature only permission.
return mContext.checkCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES) ==
@@ -499,9 +721,9 @@
/** Clear scores. Callers are responsible for checking permissions as appropriate. */
private void clearInternal() {
- sendCallback(new Consumer<INetworkScoreCache>() {
+ sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
@Override
- public void accept(INetworkScoreCache networkScoreCache) {
+ public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
try {
networkScoreCache.clearScores();
} catch (RemoteException e) {
@@ -675,9 +897,9 @@
}
writer.println("Current scorer: " + currentScorer.packageName);
- sendCallback(new Consumer<INetworkScoreCache>() {
+ sendCacheUpdateCallback(new BiConsumer<INetworkScoreCache, Object>() {
@Override
- public void accept(INetworkScoreCache networkScoreCache) {
+ public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
try {
TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
} catch (IOException | RemoteException e) {
@@ -708,14 +930,15 @@
}
}
- private void sendCallback(Consumer<INetworkScoreCache> consumer,
+ private void sendCacheUpdateCallback(BiConsumer<INetworkScoreCache, Object> consumer,
Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) {
for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) {
synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList
final int count = callbackList.beginBroadcast();
try {
for (int i = 0; i < count; i++) {
- consumer.accept(callbackList.getBroadcastItem(i));
+ consumer.accept(callbackList.getBroadcastItem(i),
+ callbackList.getRegisteredCallbackCookie(i));
}
} finally {
callbackList.finishBroadcast();
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 1eb8b94..4d7cb2f 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -33,12 +33,10 @@
import android.app.KeyguardManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
-import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
@@ -3133,8 +3131,6 @@
applyUserRestrictionsLR(userId);
}
}
-
- maybeInitializeDemoMode(userId);
}
/**
@@ -3173,29 +3169,6 @@
scheduleWriteUser(userData);
}
- private void maybeInitializeDemoMode(int userId) {
- if (UserManager.isDeviceInDemoMode(mContext) && userId != UserHandle.USER_SYSTEM) {
- String demoLauncher =
- mContext.getResources().getString(
- com.android.internal.R.string.config_demoModeLauncherComponent);
- if (!TextUtils.isEmpty(demoLauncher)) {
- ComponentName componentToEnable = ComponentName.unflattenFromString(demoLauncher);
- String demoLauncherPkg = componentToEnable.getPackageName();
- try {
- final IPackageManager iPm = AppGlobals.getPackageManager();
- iPm.setComponentEnabledSetting(componentToEnable,
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED, /* flags= */ 0,
- /* userId= */ userId);
- iPm.setApplicationEnabledSetting(demoLauncherPkg,
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED, /* flags= */ 0,
- /* userId= */ userId, null);
- } catch (RemoteException re) {
- // Internal, shouldn't happen
- }
- }
- }
- }
-
/**
* Returns the next available user id, filling in any holes in the ids.
*/
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index b2372a3..fab309b 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -110,7 +110,7 @@
static void nativeInit(JNIEnv* env, jobject obj) {
gPowerManagerServiceObj = env->NewGlobalRef(obj);
- gPowerHal = IPower::getService("power");
+ gPowerHal = IPower::getService();
if (gPowerHal == nullptr) {
ALOGE("Couldn't load PowerHAL module");
}
diff --git a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
index 785c3fa..af19acb 100644
--- a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
+++ b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
@@ -58,6 +58,7 @@
import android.provider.CallLog;
import android.provider.MediaStore;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.KeyValueListParser;
import android.util.Slog;
import com.android.internal.os.BackgroundThread;
@@ -105,7 +106,8 @@
private static final String DEMO_SESSION_COUNT = "retail_demo_session_count";
private static final String DEMO_SESSION_DURATION = "retail_demo_session_duration";
- boolean mDeviceInDemoMode = false;
+ boolean mDeviceInDemoMode;
+ boolean mIsCarrierDemoMode;
int mCurrentUserId = UserHandle.USER_SYSTEM;
long mUserInactivityTimeout;
long mWarningDialogTimeout;
@@ -135,7 +137,8 @@
if (!mDeviceInDemoMode) {
return;
}
- switch (intent.getAction()) {
+ final String action = intent.getAction();
+ switch (action) {
case Intent.ACTION_SCREEN_OFF:
mHandler.removeMessages(MSG_TURN_SCREEN_ON);
mHandler.sendEmptyMessageDelayed(MSG_TURN_SCREEN_ON, SCREEN_WAKEUP_DELAY);
@@ -166,7 +169,7 @@
mInjector.acquireWakeLock();
break;
case MSG_INACTIVITY_TIME_OUT:
- if (isDemoLauncherDisabled()) {
+ if (!mIsCarrierDemoMode && isDemoLauncherDisabled()) {
Slog.i(TAG, "User inactivity timeout reached");
showInactivityCountdownDialog();
}
@@ -177,12 +180,30 @@
}
removeMessages(MSG_START_NEW_SESSION);
removeMessages(MSG_INACTIVITY_TIME_OUT);
- if (mCurrentUserId != UserHandle.USER_SYSTEM) {
+ if (!mIsCarrierDemoMode && mCurrentUserId != UserHandle.USER_SYSTEM) {
logSessionDuration();
}
- final UserInfo demoUser = mInjector.getUserManager().createUser(DEMO_USER_NAME,
- UserInfo.FLAG_DEMO | UserInfo.FLAG_EPHEMERAL);
- if (demoUser != null) {
+
+ final UserManager um = mInjector.getUserManager();
+ UserInfo demoUser = null;
+ if (mIsCarrierDemoMode) {
+ // Re-use the existing demo user in carrier demo mode.
+ for (UserInfo user : um.getUsers()) {
+ if (user.isDemo()) {
+ demoUser = user;
+ break;
+ }
+ }
+ }
+
+ if (demoUser == null) {
+ // User in carrier demo mode should survive reboots.
+ final int flags = UserInfo.FLAG_DEMO
+ | (mIsCarrierDemoMode ? 0 : UserInfo.FLAG_EPHEMERAL);
+ demoUser = um.createUser(DEMO_USER_NAME, flags);
+ }
+
+ if (demoUser != null && mCurrentUserId != demoUser.id) {
setupDemoUser(demoUser);
mInjector.switchUser(demoUser.id);
}
@@ -211,7 +232,7 @@
}
public void register() {
- ContentResolver cr = mInjector.getContentResolver();
+ final ContentResolver cr = mInjector.getContentResolver();
cr.registerContentObserver(mDeviceDemoModeUri, false, this, UserHandle.USER_SYSTEM);
cr.registerContentObserver(mDeviceProvisionedUri, false, this, UserHandle.USER_SYSTEM);
cr.registerContentObserver(mRetailDemoConstantsUri, false, this,
@@ -224,30 +245,31 @@
refreshTimeoutConstants();
return;
}
- if (mDeviceDemoModeUri.equals(uri)) {
- mDeviceInDemoMode = UserManager.isDeviceInDemoMode(getContext());
- if (mDeviceInDemoMode) {
+
+ // If device is provisioned and left demo mode - run the cleanup in demo folder
+ if (isDeviceProvisioned()) {
+ if (UserManager.isDeviceInDemoMode(getContext())) {
startDemoMode();
} else {
mInjector.systemPropertiesSet(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "0");
+
+ // Run on the bg thread to not block the fg thread
+ BackgroundThread.getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ if (!deletePreloadsFolderContents()) {
+ Slog.w(TAG, "Failed to delete preloads folder contents");
+ }
+ }
+ });
+
+ stopDemoMode();
+
if (mInjector.isWakeLockHeld()) {
mInjector.releaseWakeLock();
}
}
}
- // If device is provisioned and left demo mode - run the cleanup in demo folder
- if (!mDeviceInDemoMode && isDeviceProvisioned()) {
- // Run on the bg thread to not block the fg thread
- BackgroundThread.getHandler().post(new Runnable() {
- @Override
- public void run() {
- if (!deletePreloadsFolderContents()) {
- Slog.w(TAG, "Failed to delete preloads folder contents");
- }
- }
- });
- stopDemoMode();
- }
}
private void refreshTimeoutConstants() {
@@ -300,23 +322,22 @@
}
boolean isDemoLauncherDisabled() {
- IPackageManager pm = mInjector.getIPackageManager();
int enabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
- String demoLauncherComponent = getContext().getResources()
- .getString(R.string.config_demoModeLauncherComponent);
try {
- enabledState = pm.getComponentEnabledSetting(
- ComponentName.unflattenFromString(demoLauncherComponent),
- mCurrentUserId);
- } catch (RemoteException exc) {
- Slog.e(TAG, "Unable to talk to Package Manager", exc);
+ final IPackageManager iPm = mInjector.getIPackageManager();
+ final String demoLauncherComponent =
+ getContext().getString(R.string.config_demoModeLauncherComponent);
+ enabledState = iPm.getComponentEnabledSetting(
+ ComponentName.unflattenFromString(demoLauncherComponent), mCurrentUserId);
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Error retrieving demo launcher enabled setting", re);
}
return enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
}
private void setupDemoUser(UserInfo userInfo) {
- UserManager um = mInjector.getUserManager();
- UserHandle user = UserHandle.of(userInfo.id);
+ final UserManager um = mInjector.getUserManager();
+ final UserHandle user = UserHandle.of(userInfo.id);
um.setUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, true, user);
um.setUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true, user);
um.setUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, true, user);
@@ -327,6 +348,7 @@
um.setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, false, user);
// Disallow rebooting in safe mode - controlled by user 0
um.setUserRestriction(UserManager.DISALLOW_SAFE_BOOT, true, UserHandle.SYSTEM);
+
Settings.Secure.putIntForUser(mInjector.getContentResolver(),
Settings.Secure.SKIP_FIRST_USE_HINTS, 1, userInfo.id);
Settings.Global.putInt(mInjector.getContentResolver(),
@@ -334,6 +356,47 @@
grantRuntimePermissionToCamera(user);
clearPrimaryCallLog();
+
+ if (!mIsCarrierDemoMode) {
+ // Enable demo launcher.
+ final String demoLauncher = getContext().getString(
+ R.string.config_demoModeLauncherComponent);
+ if (!TextUtils.isEmpty(demoLauncher)) {
+ final ComponentName componentToEnable =
+ ComponentName.unflattenFromString(demoLauncher);
+ final String packageName = componentToEnable.getPackageName();
+ try {
+ final IPackageManager iPm = AppGlobals.getPackageManager();
+ iPm.setComponentEnabledSetting(componentToEnable,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0, userInfo.id);
+ iPm.setApplicationEnabledSetting(packageName,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0, userInfo.id, null);
+ } catch (RemoteException re) {
+ // Internal, shouldn't happen
+ }
+ }
+ } else {
+ // Set the carrier demo mode setting for the demo user.
+ final String carrierDemoModeSetting = getContext().getString(
+ R.string.config_carrierDemoModeSetting);
+ Settings.Secure.putIntForUser(getContext().getContentResolver(),
+ carrierDemoModeSetting, 1, userInfo.id);
+
+ // Enable packages for carrier demo mode.
+ final String packageList = getContext().getString(
+ R.string.config_carrierDemoModePackages);
+ final String[] packageNames = packageList == null ? new String[0]
+ : TextUtils.split(packageList, ",");
+ final IPackageManager iPm = AppGlobals.getPackageManager();
+ for (String packageName : packageNames) {
+ try {
+ iPm.setApplicationEnabledSetting(packageName,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0, userInfo.id, null);
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Error enabling application: " + packageName, re);
+ }
+ }
+ }
}
private void grantRuntimePermissionToCamera(UserHandle user) {
@@ -385,13 +448,17 @@
}
private void registerBroadcastReceiver() {
- if (mBroadcastReceiver == null) {
- final IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(ACTION_RESET_DEMO);
- mBroadcastReceiver = new IntentReceiver();
- getContext().registerReceiver(mBroadcastReceiver, filter);
+ if (mBroadcastReceiver != null) {
+ return;
}
+
+ final IntentFilter filter = new IntentFilter();
+ if (!mIsCarrierDemoMode) {
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ }
+ filter.addAction(ACTION_RESET_DEMO);
+ mBroadcastReceiver = new IntentReceiver();
+ getContext().registerReceiver(mBroadcastReceiver, filter);
}
private void unregisterBroadcastReceiver() {
@@ -427,6 +494,8 @@
}
private void startDemoMode() {
+ mDeviceInDemoMode = true;
+
mPreloadAppsInstaller = mInjector.getPreloadAppsInstaller();
mInjector.initializeWakeLock();
if (mCameraIdsWithFlash == null) {
@@ -434,6 +503,12 @@
}
registerBroadcastReceiver();
+ final String carrierDemoModeSetting =
+ getContext().getString(R.string.config_carrierDemoModeSetting);
+ mIsCarrierDemoMode = !TextUtils.isEmpty(carrierDemoModeSetting)
+ && (Settings.Secure.getInt(getContext().getContentResolver(),
+ carrierDemoModeSetting, 0) == 1);
+
mInjector.systemPropertiesSet(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "1");
mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
@@ -471,13 +546,12 @@
public void onBootPhase(int bootPhase) {
switch (bootPhase) {
case PHASE_THIRD_PARTY_APPS_CAN_START:
- SettingsObserver settingsObserver = new SettingsObserver(mHandler);
+ final SettingsObserver settingsObserver = new SettingsObserver(mHandler);
settingsObserver.register();
settingsObserver.refreshTimeoutConstants();
break;
case PHASE_BOOT_COMPLETED:
if (UserManager.isDeviceInDemoMode(getContext())) {
- mDeviceInDemoMode = true;
startDemoMode();
}
break;
@@ -497,33 +571,39 @@
Slog.wtf(TAG, "Should not allow switch to non-demo user in demo mode");
return;
}
- if (!mInjector.isWakeLockHeld()) {
+ if (!mIsCarrierDemoMode && !mInjector.isWakeLockHeld()) {
mInjector.acquireWakeLock();
}
mCurrentUserId = userId;
mInjector.getActivityManagerInternal().updatePersistentConfigurationForUser(
mInjector.getSystemUsersConfiguration(), userId);
+
mInjector.turnOffAllFlashLights(mCameraIdsWithFlash);
muteVolumeStreams();
if (!mInjector.getWifiManager().isWifiEnabled()) {
mInjector.getWifiManager().setWifiEnabled(true);
}
+
// Disable lock screen for demo users.
mInjector.getLockPatternUtils().setLockScreenDisabled(true, userId);
- mInjector.getNotificationManager().notifyAsUser(TAG,
- 1, mInjector.createResetNotification(), UserHandle.of(userId));
- synchronized (mActivityLock) {
- mUserUntouched = true;
- }
- mInjector.logSessionCount(1);
- mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mPreloadAppsInstaller.installApps(userId);
+ if (!mIsCarrierDemoMode) {
+ // Show reset notification (except in carrier demo mode).
+ mInjector.getNotificationManager().notifyAsUser(TAG,
+ 1, mInjector.createResetNotification(), UserHandle.of(userId));
+
+ synchronized (mActivityLock) {
+ mUserUntouched = true;
}
- });
+ mInjector.logSessionCount(1);
+ mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mPreloadAppsInstaller.installApps(userId);
+ }
+ });
+ }
}
private RetailDemoModeServiceInternal mLocalService = new RetailDemoModeServiceInternal() {
@@ -531,7 +611,7 @@
@Override
public void onUserActivity() {
- if (!mDeviceInDemoMode) {
+ if (!mDeviceInDemoMode || mIsCarrierDemoMode) {
return;
}
long timeOfActivity = SystemClock.uptimeMillis();
@@ -682,7 +762,7 @@
}
boolean isWakeLockHeld() {
- return mWakeLock.isHeld();
+ return mWakeLock != null && mWakeLock.isHeld();
}
void acquireWakeLock() {
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index 43c8957..4ca29cd 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -63,7 +63,10 @@
import android.net.ScoredNetwork;
import android.net.Uri;
import android.net.WifiKey;
+import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiSsid;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -82,6 +85,8 @@
import com.android.server.devicepolicy.MockUtils;
+import com.google.android.collect.Lists;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -96,9 +101,12 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
/**
* Tests for {@link NetworkScoreService}.
@@ -106,19 +114,27 @@
@RunWith(AndroidJUnit4.class)
@MediumTest
public class NetworkScoreServiceTest {
+ private static final String SSID = "ssid";
+ private static final String SSID_2 = "ssid_2";
+ private static final String SSID_3 = "ssid_3";
private static final ScoredNetwork SCORED_NETWORK =
- new ScoredNetwork(new NetworkKey(new WifiKey("\"ssid\"", "00:00:00:00:00:00")),
+ new ScoredNetwork(new NetworkKey(new WifiKey(quote(SSID), "00:00:00:00:00:00")),
+ null /* rssiCurve*/);
+ private static final ScoredNetwork SCORED_NETWORK_2 =
+ new ScoredNetwork(new NetworkKey(new WifiKey(quote(SSID_2), "00:00:00:00:00:00")),
null /* rssiCurve*/);
private static final NetworkScorerAppData NEW_SCORER =
new NetworkScorerAppData("newPackageName", 1, "newScoringServiceClass");
- @Mock private PackageManager mPackageManager;
@Mock private NetworkScorerAppManager mNetworkScorerAppManager;
@Mock private Context mContext;
@Mock private Resources mResources;
@Mock private INetworkScoreCache.Stub mNetworkScoreCache, mNetworkScoreCache2;
@Mock private IBinder mIBinder, mIBinder2;
@Mock private INetworkRecommendationProvider mRecommendationProvider;
+ @Mock private Function<List<ScoredNetwork>, List<ScoredNetwork>> mCurrentNetworkFilter;
+ @Mock private Function<List<ScoredNetwork>, List<ScoredNetwork>> mScanResultsFilter;
+ @Mock private WifiInfo mWifiInfo;
@Captor private ArgumentCaptor<List<ScoredNetwork>> mScoredNetworkCaptor;
private ContentResolver mContentResolver;
@@ -127,6 +143,11 @@
private RemoteCallback mRemoteCallback;
private OnResultListener mOnResultListener;
private HandlerThread mHandlerThread;
+ private List<ScanResult> mScanResults;
+
+ private static String quote(String str) {
+ return String.format("\"%s\"", str);
+ }
@Before
public void setUp() throws Exception {
@@ -136,6 +157,8 @@
mContentResolver = InstrumentationRegistry.getContext().getContentResolver();
when(mContext.getContentResolver()).thenReturn(mContentResolver);
when(mContext.getResources()).thenReturn(mResources);
+ when(mWifiInfo.getSSID()).thenReturn(SCORED_NETWORK.networkKey.wifiKey.ssid);
+ when(mWifiInfo.getBSSID()).thenReturn(SCORED_NETWORK.networkKey.wifiKey.bssid);
mHandlerThread = new HandlerThread("NetworkScoreServiceTest");
mHandlerThread.start();
mNetworkScoreService = new NetworkScoreService(mContext, mNetworkScorerAppManager,
@@ -150,6 +173,21 @@
Settings.Global.putLong(mContentResolver,
Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS, -1L);
mNetworkScoreService.refreshRecommendationRequestTimeoutMs();
+ populateScanResults();
+ }
+
+ private void populateScanResults() {
+ mScanResults = new ArrayList<>();
+ mScanResults.add(createScanResult(SSID, SCORED_NETWORK.networkKey.wifiKey.bssid));
+ mScanResults.add(createScanResult(SSID_2, SCORED_NETWORK_2.networkKey.wifiKey.bssid));
+ mScanResults.add(createScanResult(SSID_3, "10:10:00:00:10:10"));
+ }
+
+ private ScanResult createScanResult(String ssid, String bssid) {
+ ScanResult result = new ScanResult();
+ result.wifiSsid = WifiSsid.createFromAsciiEncoded(ssid);
+ result.BSSID = bssid;
+ return result;
}
@After
@@ -622,6 +660,173 @@
assertEquals(NEW_SCORER.packageName, mNetworkScoreService.getActiveScorerPackage());
}
+ @Test
+ public void testCacheUpdatingConsumer_nullFilter() throws Exception {
+ List<ScoredNetwork> scoredNetworkList = Lists.newArrayList(SCORED_NETWORK);
+ NetworkScoreService.FilteringCacheUpdatingConsumer consumer =
+ new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext,
+ new ArrayList<>(scoredNetworkList), NetworkKey.TYPE_WIFI,
+ mCurrentNetworkFilter, mScanResultsFilter);
+
+ consumer.accept(mNetworkScoreCache, null /*cookie*/);
+
+ verify(mNetworkScoreCache).updateScores(scoredNetworkList);
+ verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
+ }
+
+ @Test
+ public void testCacheUpdatingConsumer_noneFilter() throws Exception {
+ List<ScoredNetwork> scoredNetworkList = Lists.newArrayList(SCORED_NETWORK);
+ NetworkScoreService.FilteringCacheUpdatingConsumer
+ consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext,
+ new ArrayList<>(scoredNetworkList),
+ NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
+
+ consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_NONE);
+
+ verify(mNetworkScoreCache).updateScores(scoredNetworkList);
+ verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
+ }
+
+ @Test
+ public void testCacheUpdatingConsumer_unknownFilter() throws Exception {
+ List<ScoredNetwork> scoredNetworkList = Lists.newArrayList(SCORED_NETWORK);
+ NetworkScoreService.FilteringCacheUpdatingConsumer
+ consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext,
+ new ArrayList<>(scoredNetworkList),
+ NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
+
+ consumer.accept(mNetworkScoreCache, -1 /*cookie*/);
+
+ verify(mNetworkScoreCache).updateScores(scoredNetworkList);
+ verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
+ }
+
+ @Test
+ public void testCacheUpdatingConsumer_nonIntFilter() throws Exception {
+ List<ScoredNetwork> scoredNetworkList = Lists.newArrayList(SCORED_NETWORK);
+ NetworkScoreService.FilteringCacheUpdatingConsumer
+ consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext,
+ new ArrayList<>(scoredNetworkList),
+ NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
+
+ consumer.accept(mNetworkScoreCache, "not an int" /*cookie*/);
+
+ verify(mNetworkScoreCache).updateScores(scoredNetworkList);
+ verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
+ }
+
+ @Test
+ public void testCacheUpdatingConsumer_emptyScoreList() throws Exception {
+ NetworkScoreService.FilteringCacheUpdatingConsumer
+ consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext,
+ Collections.emptyList(),
+ NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
+
+ consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_NONE);
+
+ verifyZeroInteractions(mNetworkScoreCache, mCurrentNetworkFilter, mScanResultsFilter);
+ }
+
+ @Test
+ public void testCacheUpdatingConsumer_currentNetworkFilter() throws Exception {
+ List<ScoredNetwork> scoredNetworkList =
+ Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2);
+ NetworkScoreService.FilteringCacheUpdatingConsumer
+ consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext,
+ new ArrayList<>(scoredNetworkList),
+ NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
+
+ List<ScoredNetwork> filteredList = new ArrayList<>(scoredNetworkList);
+ filteredList.remove(SCORED_NETWORK);
+ when(mCurrentNetworkFilter.apply(scoredNetworkList)).thenReturn(filteredList);
+ consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK);
+
+ verify(mNetworkScoreCache).updateScores(filteredList);
+ verifyZeroInteractions(mScanResultsFilter);
+ }
+
+ @Test
+ public void testCacheUpdatingConsumer_scanResultsFilter() throws Exception {
+ List<ScoredNetwork> scoredNetworkList =
+ Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2);
+ NetworkScoreService.FilteringCacheUpdatingConsumer
+ consumer = new NetworkScoreService.FilteringCacheUpdatingConsumer(mContext,
+ new ArrayList<>(scoredNetworkList),
+ NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
+
+ List<ScoredNetwork> filteredList = new ArrayList<>(scoredNetworkList);
+ filteredList.remove(SCORED_NETWORK);
+ when(mScanResultsFilter.apply(scoredNetworkList)).thenReturn(filteredList);
+ consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS);
+
+ verify(mNetworkScoreCache).updateScores(filteredList);
+ verifyZeroInteractions(mCurrentNetworkFilter);
+ }
+
+ @Test
+ public void testCurrentNetworkScoreCacheFilter_nullWifiInfo() throws Exception {
+ NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter =
+ new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> null /*WifiInfo*/);
+
+ List<ScoredNetwork> actualList =
+ cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2));
+
+ assertTrue(actualList.isEmpty());
+ }
+
+ @Test
+ public void testCurrentNetworkScoreCacheFilter_scoreFiltered() throws Exception {
+ NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter =
+ new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> mWifiInfo);
+
+ List<ScoredNetwork> actualList =
+ cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2));
+
+ List<ScoredNetwork> expectedList = Collections.singletonList(SCORED_NETWORK);
+ assertEquals(expectedList, actualList);
+ }
+
+ @Test
+ public void testCurrentNetworkScoreCacheFilter_currentNetworkNotInList() throws Exception {
+ when(mWifiInfo.getSSID()).thenReturn("\"notInList\"");
+ NetworkScoreService.CurrentNetworkScoreCacheFilter cacheFilter =
+ new NetworkScoreService.CurrentNetworkScoreCacheFilter(() -> mWifiInfo);
+
+ List<ScoredNetwork> actualList =
+ cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2));
+
+ assertTrue(actualList.isEmpty());
+ }
+
+ @Test
+ public void testScanResultsScoreCacheFilter_emptyScanResults() throws Exception {
+ NetworkScoreService.ScanResultsScoreCacheFilter cacheFilter =
+ new NetworkScoreService.ScanResultsScoreCacheFilter(Collections::emptyList);
+
+ List<ScoredNetwork> actualList =
+ cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2));
+
+ assertTrue(actualList.isEmpty());
+ }
+
+ @Test
+ public void testScanResultsScoreCacheFilter_scoresFiltered() throws Exception {
+ NetworkScoreService.ScanResultsScoreCacheFilter cacheFilter =
+ new NetworkScoreService.ScanResultsScoreCacheFilter(() -> mScanResults);
+
+ ScoredNetwork unmatchedScore =
+ new ScoredNetwork(new NetworkKey(new WifiKey(quote("newSsid"),
+ "00:00:00:00:00:00")), null /* rssiCurve*/);
+
+ List<ScoredNetwork> actualList =
+ cacheFilter.apply(Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2,
+ unmatchedScore));
+
+ List<ScoredNetwork> expectedList = Lists.newArrayList(SCORED_NETWORK, SCORED_NETWORK_2);
+ assertEquals(expectedList, actualList);
+ }
+
// "injects" the mock INetworkRecommendationProvider into the NetworkScoreService.
private void injectProvider() {
final ComponentName componentName = new ComponentName(NEW_SCORER.packageName,
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 045d68c..cf5badc 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1223,6 +1223,7 @@
sp<ResourceTypeSet> colors;
sp<ResourceTypeSet> menus;
sp<ResourceTypeSet> mipmaps;
+ sp<ResourceTypeSet> fonts;
ASSIGN_IT(drawable);
ASSIGN_IT(layout);
@@ -1235,6 +1236,7 @@
ASSIGN_IT(color);
ASSIGN_IT(menu);
ASSIGN_IT(mipmap);
+ ASSIGN_IT(font);
assets->setResources(resources);
// now go through any resource overlays and collect their files
@@ -1257,6 +1259,7 @@
!applyFileOverlay(bundle, assets, &raws, "raw") ||
!applyFileOverlay(bundle, assets, &colors, "color") ||
!applyFileOverlay(bundle, assets, &menus, "menu") ||
+ !applyFileOverlay(bundle, assets, &fonts, "font") ||
!applyFileOverlay(bundle, assets, &mipmaps, "mipmap")) {
return UNKNOWN_ERROR;
}
@@ -1291,6 +1294,13 @@
}
}
+ if (fonts != NULL) {
+ err = makeFileResources(bundle, assets, &table, fonts, "font");
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+
if (layouts != NULL) {
err = makeFileResources(bundle, assets, &table, layouts, "layout");
if (err != NO_ERROR) {
@@ -1549,6 +1559,26 @@
err = NO_ERROR;
}
+ if (fonts != NULL) {
+ ResourceDirIterator it(fonts, String8("font"));
+ while ((err=it.next()) == NO_ERROR) {
+ // fonts can be resources other than xml.
+ if (it.getFile()->getPath().getPathExtension() == ".xml") {
+ String8 src = it.getFile()->getPrintableSource();
+ err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
+ it.getFile(), &table, xmlFlags);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
+ }
+ }
+
+ if (err < NO_ERROR) {
+ hasErrors = true;
+ }
+ err = NO_ERROR;
+ }
+
// Now compile any generated resources.
std::queue<CompileResourceWorkItem>& workQueue = table.getWorkQueue();
while (!workQueue.empty()) {
diff --git a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
index 65a49ea..98fd0f3 100644
--- a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
+++ b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
@@ -21,11 +21,17 @@
import android.net.wifi.hotspot2.pps.HomeSP;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import java.io.IOException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import org.xml.sax.SAXException;
@@ -131,16 +137,34 @@
private static final String NODE_FQDN = "FQDN";
private static final String NODE_FRIENDLY_NAME = "FriendlyName";
private static final String NODE_ROAMING_CONSORTIUM_OI = "RoamingConsortiumOI";
+ private static final String NODE_NETWORK_ID = "NetworkID";
+ private static final String NODE_SSID = "SSID";
+ private static final String NODE_HESSID = "HESSID";
+ private static final String NODE_ICON_URL = "IconURL";
+ private static final String NODE_HOME_OI_LIST = "HomeOIList";
+ private static final String NODE_HOME_OI = "HomeOI";
+ private static final String NODE_HOME_OI_REQUIRED = "HomeOIRequired";
+ private static final String NODE_OTHER_HOME_PARTNERS = "OtherHomePartners";
/**
* Fields under Credential subtree.
*/
private static final String NODE_CREDENTIAL = "Credential";
+ private static final String NODE_CREATION_DATE = "CreationDate";
+ private static final String NODE_EXPIRATION_DATE = "ExpirationDate";
private static final String NODE_USERNAME_PASSWORD = "UsernamePassword";
private static final String NODE_USERNAME = "Username";
private static final String NODE_PASSWORD = "Password";
+ private static final String NODE_MACHINE_MANAGED = "MachineManaged";
+ private static final String NODE_SOFT_TOKEN_APP = "SoftTokenApp";
+ private static final String NODE_ABLE_TO_SHARE = "AbleToShare";
private static final String NODE_EAP_METHOD = "EAPMethod";
private static final String NODE_EAP_TYPE = "EAPType";
+ private static final String NODE_VENDOR_ID = "VendorId";
+ private static final String NODE_VENDOR_TYPE = "VendorType";
+ private static final String NODE_INNER_EAP_TYPE = "InnerEAPType";
+ private static final String NODE_INNER_VENDOR_ID = "InnerVendorID";
+ private static final String NODE_INNER_VENDOR_TYPE = "InnerVendorType";
private static final String NODE_INNER_METHOD = "InnerMethod";
private static final String NODE_DIGITAL_CERTIFICATE = "DigitalCertificate";
private static final String NODE_CERTIFICATE_TYPE = "CertificateType";
@@ -148,6 +172,7 @@
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";
/**
* URN (Unique Resource Name) for PerProviderSubscription Management Object Tree.
@@ -558,6 +583,20 @@
homeSp.roamingConsortiumOIs =
parseRoamingConsortiumOI(getPpsNodeValue(child));
break;
+ case NODE_ICON_URL:
+ homeSp.iconUrl = getPpsNodeValue(child);
+ break;
+ case NODE_NETWORK_ID:
+ homeSp.homeNetworkIds = parseNetworkIds(child);
+ break;
+ case NODE_HOME_OI_LIST:
+ Pair<List<Long>, List<Long>> homeOIs = parseHomeOIList(child);
+ homeSp.matchAllOIs = convertFromLongList(homeOIs.first);
+ homeSp.matchAnyOIs = convertFromLongList(homeOIs.second);
+ break;
+ case NODE_OTHER_HOME_PARTNERS:
+ homeSp.otherHomePartners = parseOtherHomePartners(child);
+ break;
default:
throw new ParsingException("Unknown node under HomeSP: " + child.getName());
}
@@ -587,6 +626,192 @@
}
/**
+ * Parse configurations under PerProviderSubscription/HomeSP/NetworkID subtree.
+ *
+ * @param node PPSNode representing the root of the PerProviderSubscription/HomeSP/NetworkID
+ * subtree
+ * @return HashMap<String, Long> representing list of <SSID, HESSID> pair.
+ * @throws ParsingException
+ */
+ static private Map<String, Long> parseNetworkIds(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for NetworkID");
+ }
+
+ Map<String, Long> networkIds = new HashMap<>();
+ for (PPSNode child : node.getChildren()) {
+ Pair<String, Long> networkId = parseNetworkIdInstance(child);
+ networkIds.put(networkId.first, networkId.second);
+ }
+ return networkIds;
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/HomeSP/NetworkID/<X+> subtree.
+ * The instance name (<X+>) is irrelevant and must be unique for each instance, which
+ * is verified when the PPS tree is constructed {@link #buildPpsNode}.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/HomeSP/NetworkID/<X+> subtree
+ * @return Pair<String, Long> representing <SSID, HESSID> pair.
+ * @throws ParsingException
+ */
+ static private Pair<String, Long> parseNetworkIdInstance(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for NetworkID instance");
+ }
+
+ String ssid = null;
+ Long hessid = null;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_SSID:
+ ssid = getPpsNodeValue(child);
+ break;
+ case NODE_HESSID:
+ try {
+ hessid = Long.parseLong(getPpsNodeValue(child), 16);
+ } catch (NumberFormatException e) {
+ throw new ParsingException("Invalid HESSID: " + getPpsNodeValue(child));
+ }
+ break;
+ default:
+ throw new ParsingException("Unknown node under NetworkID instance: " +
+ child.getName());
+ }
+ }
+ if (ssid == null)
+ throw new ParsingException("NetworkID instance missing SSID");
+
+ return new Pair<String, Long>(ssid, hessid);
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/HomeSP/HomeOIList subtree.
+ *
+ * @param node PPSNode representing the root of the PerProviderSubscription/HomeSP/HomeOIList
+ * subtree
+ * @return Pair<List<Long>, List<Long>> containing both MatchAllOIs and MatchAnyOIs list.
+ * @throws ParsingException
+ */
+ private static Pair<List<Long>, List<Long>> parseHomeOIList(PPSNode node)
+ throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for HomeOIList");
+ }
+
+ List<Long> matchAllOIs = new ArrayList<Long>();
+ List<Long> matchAnyOIs = new ArrayList<Long>();
+ for (PPSNode child : node.getChildren()) {
+ Pair<Long, Boolean> homeOI = parseHomeOIInstance(child);
+ if (homeOI.second.booleanValue()) {
+ matchAllOIs.add(homeOI.first);
+ } else {
+ matchAnyOIs.add(homeOI.first);
+ }
+ }
+ return new Pair<List<Long>, List<Long>>(matchAllOIs, matchAnyOIs);
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/HomeSP/HomeOIList/<X+> subtree.
+ * The instance name (<X+>) is irrelevant and must be unique for each instance, which
+ * is verified when the PPS tree is constructed {@link #buildPpsNode}.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/HomeSP/HomeOIList/<X+> subtree
+ * @return Pair<Long, Boolean> containing a HomeOI and a HomeOIRequired flag
+ * @throws ParsingException
+ */
+ private static Pair<Long, Boolean> parseHomeOIInstance(PPSNode node) throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for HomeOI instance");
+ }
+
+ Long oi = null;
+ Boolean required = null;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_HOME_OI:
+ try {
+ oi = Long.valueOf(getPpsNodeValue(child), 16);
+ } catch (NumberFormatException e) {
+ throw new ParsingException("Invalid HomeOI: " + getPpsNodeValue(child));
+ }
+ break;
+ case NODE_HOME_OI_REQUIRED:
+ required = Boolean.valueOf(getPpsNodeValue(child));
+ break;
+ default:
+ throw new ParsingException("Unknown node under NetworkID instance: " +
+ child.getName());
+ }
+ }
+ if (oi == null) {
+ throw new ParsingException("HomeOI instance missing OI field");
+ }
+ if (required == null) {
+ throw new ParsingException("HomeOI instance missing required field");
+ }
+ return new Pair<Long, Boolean>(oi, required);
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/HomeSP/OtherHomePartners subtree.
+ * This contains a list of FQDN (Fully Qualified Domain Name) that are considered
+ * home partners.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/HomeSP/OtherHomePartners subtree
+ * @return String[] list of partner's FQDN
+ * @throws ParsingException
+ */
+ private static String[] parseOtherHomePartners(PPSNode node) throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for OtherHomePartners");
+ }
+ List<String> otherHomePartners = new ArrayList<String>();
+ for (PPSNode child : node.getChildren()) {
+ String fqdn = parseOtherHomePartnerInstance(child);
+ otherHomePartners.add(fqdn);
+ }
+ return otherHomePartners.toArray(new String[otherHomePartners.size()]);
+ }
+
+ /**
+ * Parse configurations under PerProviderSubscription/HomeSP/OtherHomePartners/<X+> subtree.
+ * The instance name (<X+>) is irrelevant and must be unique for each instance, which
+ * is verified when the PPS tree is constructed {@link #buildPpsNode}.
+ *
+ * @param node PPSNode representing the root of the
+ * PerProviderSubscription/HomeSP/OtherHomePartners/<X+> subtree
+ * @return String FQDN of the partner
+ * @throws ParsingException
+ */
+ private static String parseOtherHomePartnerInstance(PPSNode node) throws ParsingException {
+ if (node.isLeaf()) {
+ throw new ParsingException("Leaf node not expected for OtherHomePartner instance");
+ }
+ String fqdn = null;
+ for (PPSNode child : node.getChildren()) {
+ switch (child.getName()) {
+ case NODE_FQDN:
+ fqdn = getPpsNodeValue(child);
+ break;
+ default:
+ throw new ParsingException(
+ "Unknown node under OtherHomePartner instance: " + child.getName());
+ }
+ }
+ if (fqdn == null) {
+ throw new ParsingException("OtherHomePartner instance missing FQDN field");
+ }
+ return fqdn;
+ }
+
+ /**
* Parse configurations under PerProviderSubscription/Credential subtree.
*
* @param node PPSNode representing the root of the PerProviderSubscription/Credential subtree
@@ -601,6 +826,12 @@
Credential credential = new Credential();
for (PPSNode child: node.getChildren()) {
switch (child.getName()) {
+ case NODE_CREATION_DATE:
+ credential.creationTimeInMs = parseDate(getPpsNodeValue(child));
+ break;
+ case NODE_EXPIRATION_DATE:
+ credential.expirationTimeInMs = parseDate(getPpsNodeValue(child));
+ break;
case NODE_USERNAME_PASSWORD:
credential.userCredential = parseUserCredential(child);
break;
@@ -610,6 +841,10 @@
case NODE_REALM:
credential.realm = getPpsNodeValue(child);
break;
+ case NODE_CHECK_AAA_SERVER_CERT_STATUS:
+ credential.checkAAAServerCertStatus =
+ Boolean.parseBoolean(getPpsNodeValue(child));
+ break;
case NODE_SIM:
credential.simCredential = parseSimCredential(child);
break;
@@ -644,6 +879,15 @@
case NODE_PASSWORD:
userCred.password = getPpsNodeValue(child);
break;
+ case NODE_MACHINE_MANAGED:
+ userCred.machineManaged = Boolean.parseBoolean(getPpsNodeValue(child));
+ break;
+ case NODE_SOFT_TOKEN_APP:
+ userCred.softTokenApp = getPpsNodeValue(child);
+ break;
+ case NODE_ABLE_TO_SHARE:
+ userCred.ableToShare = Boolean.parseBoolean(getPpsNodeValue(child));
+ break;
case NODE_EAP_METHOD:
parseEAPMethod(child, userCred);
break;
@@ -678,6 +922,15 @@
case NODE_INNER_METHOD:
userCred.nonEapInnerMethod = getPpsNodeValue(child);
break;
+ case NODE_VENDOR_ID:
+ case NODE_VENDOR_TYPE:
+ case NODE_INNER_EAP_TYPE:
+ case NODE_INNER_VENDOR_ID:
+ case NODE_INNER_VENDOR_TYPE:
+ // Only EAP-TTLS is currently supported for user credential, which doesn't
+ // use any of these parameters.
+ Log.d(TAG, "Ignore unsupported EAP method parameter: " + child.getName());
+ break;
default:
throw new ParsingException("Unknown node under EAPMethod: " + child.getName());
}
@@ -770,6 +1023,22 @@
}
/**
+ * Convert a date string to the number of milliseconds since January 1, 1970, 00:00:00 GMT.
+ *
+ * @param dateStr String in the format of yyyy-MM-dd'T'HH:mm:ss'Z'
+ * @return number of milliseconds
+ * @throws ParsingException
+ */
+ private static long parseDate(String dateStr) throws ParsingException {
+ try {
+ DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+ return format.parse(dateStr).getTime();
+ } catch (ParseException pe) {
+ throw new ParsingException("Badly formatted time: " + dateStr);
+ }
+ }
+
+ /**
* Parse an integer string.
*
* @param value String of integer value
@@ -783,4 +1052,19 @@
throw new ParsingException("Invalid integer value: " + value);
}
}
+
+ /**
+ * Convert a List<Long> to a primitive long array long[].
+ *
+ * @param list List to be converted
+ * @return long[]
+ */
+ private static long[] convertFromLongList(List<Long> list) {
+ Long[] objectArray = list.toArray(new Long[list.size()]);
+ long[] primitiveArray = new long[objectArray.length];
+ for (int i = 0; i < objectArray.length; i++) {
+ primitiveArray[i] = objectArray[i].longValue();
+ }
+ return primitiveArray;
+ }
}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
index 790dfaf..3374f42d 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -23,6 +23,7 @@
import android.text.TextUtils;
import android.util.Log;
+import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
@@ -41,8 +42,6 @@
* In addition to the fields in the Credential subtree, this will also maintain necessary
* information for the private key and certificates associated with this credential.
*
- * Currently we only support the nodes that are used by Hotspot 2.0 Release 1.
- *
* @hide
*/
public final class Credential implements Parcelable {
@@ -52,7 +51,21 @@
* Max string length for realm. Refer to Credential/Realm node in Hotspot 2.0 Release 2
* Technical Specification Section 9.1 for more info.
*/
- private static final int MAX_REALM_LENGTH = 253;
+ private static final int MAX_REALM_BYTES = 253;
+
+ /**
+ * The time this credential is created. It is in the format of number
+ * of milliseconds since January 1, 1970, 00:00:00 GMT.
+ * Using Long.MIN_VALUE to indicate unset value.
+ */
+ public long creationTimeInMs = Long.MIN_VALUE;
+
+ /**
+ * The time this credential will expire. It is in the format of number
+ * of milliseconds since January 1, 1970, 00:00:00 GMT.
+ * Using Long.MIN_VALUE to indicate unset value.
+ */
+ public long expirationTimeInMs = Long.MIN_VALUE;
/**
* The realm associated with this credential. It will be used to determine
@@ -62,6 +75,13 @@
public String realm = null;
/**
+ * When set to true, the device should check AAA (Authentication, Authorization,
+ * and Accounting) server's certificate during EAP (Extensible Authentication
+ * Protocol) authentication.
+ */
+ public boolean checkAAAServerCertStatus = false;
+
+ /**
* Username-password based credential.
* Contains the fields under PerProviderSubscription/Credential/UsernamePassword subtree.
*/
@@ -70,13 +90,13 @@
* Maximum string length for username. Refer to Credential/UsernamePassword/Username
* node in Hotspot 2.0 Release 2 Technical Specification Section 9.1 for more info.
*/
- private static final int MAX_USERNAME_LENGTH = 63;
+ private static final int MAX_USERNAME_BYTES = 63;
/**
* Maximum string length for password. Refer to Credential/UsernamePassword/Password
* in Hotspot 2.0 Release 2 Technical Specification Section 9.1 for more info.
*/
- private static final int MAX_PASSWORD_LENGTH = 255;
+ private static final int MAX_PASSWORD_BYTES = 255;
/**
* Supported Non-EAP inner methods. Refer to
@@ -97,6 +117,21 @@
public String password = null;
/**
+ * Flag indicating if the password is machine managed.
+ */
+ public boolean machineManaged = false;
+
+ /**
+ * The name of the application used to generate the password.
+ */
+ public String softTokenApp = null;
+
+ /**
+ * Flag indicating if this credential is usable on other mobile devices as well.
+ */
+ public boolean ableToShare = false;
+
+ /**
* EAP (Extensible Authentication Protocol) method type.
* Refer to http://www.iana.org/assignments/eap-numbers/eap-numbers.xml#eap-numbers-4
* for valid values.
@@ -123,6 +158,9 @@
if (source != null) {
username = source.username;
password = source.password;
+ machineManaged = source.machineManaged;
+ softTokenApp = source.softTokenApp;
+ ableToShare = source.ableToShare;
eapType = source.eapType;
nonEapInnerMethod = source.nonEapInnerMethod;
}
@@ -137,6 +175,9 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(username);
dest.writeString(password);
+ dest.writeInt(machineManaged ? 1 : 0);
+ dest.writeString(softTokenApp);
+ dest.writeInt(ableToShare ? 1 : 0);
dest.writeInt(eapType);
dest.writeString(nonEapInnerMethod);
}
@@ -151,10 +192,13 @@
}
UserCredential that = (UserCredential) thatObject;
- return TextUtils.equals(username, that.username) &&
- TextUtils.equals(password, that.password) &&
- eapType == that.eapType &&
- TextUtils.equals(nonEapInnerMethod, that.nonEapInnerMethod);
+ return TextUtils.equals(username, that.username)
+ && TextUtils.equals(password, that.password)
+ && machineManaged == that.machineManaged
+ && TextUtils.equals(softTokenApp, that.softTokenApp)
+ && ableToShare == that.ableToShare
+ && eapType == that.eapType
+ && TextUtils.equals(nonEapInnerMethod, that.nonEapInnerMethod);
}
/**
@@ -167,8 +211,9 @@
Log.d(TAG, "Missing username");
return false;
}
- if (username.length() > MAX_USERNAME_LENGTH) {
- Log.d(TAG, "username exceeding maximum length: " + username.length());
+ if (username.getBytes(StandardCharsets.UTF_8).length > MAX_USERNAME_BYTES) {
+ Log.d(TAG, "username exceeding maximum length: "
+ + username.getBytes(StandardCharsets.UTF_8).length);
return false;
}
@@ -176,8 +221,9 @@
Log.d(TAG, "Missing password");
return false;
}
- if (password.length() > MAX_PASSWORD_LENGTH) {
- Log.d(TAG, "password exceeding maximum length: " + password.length());
+ if (password.getBytes(StandardCharsets.UTF_8).length > MAX_PASSWORD_BYTES) {
+ Log.d(TAG, "password exceeding maximum length: "
+ + password.getBytes(StandardCharsets.UTF_8).length);
return false;
}
@@ -202,6 +248,9 @@
UserCredential userCredential = new UserCredential();
userCredential.username = in.readString();
userCredential.password = in.readString();
+ userCredential.machineManaged = in.readInt() != 0;
+ userCredential.softTokenApp = in.readString();
+ userCredential.ableToShare = in.readInt() != 0;
userCredential.eapType = in.readInt();
userCredential.nonEapInnerMethod = in.readString();
return userCredential;
@@ -281,8 +330,8 @@
}
CertificateCredential that = (CertificateCredential) thatObject;
- return TextUtils.equals(certType, that.certType) &&
- Arrays.equals(certSha256FingerPrint, that.certSha256FingerPrint);
+ return TextUtils.equals(certType, that.certType)
+ && Arrays.equals(certSha256FingerPrint, that.certSha256FingerPrint);
}
/**
@@ -295,8 +344,8 @@
Log.d(TAG, "Unsupported certificate type: " + certType);
return false;
}
- if (certSha256FingerPrint == null ||
- certSha256FingerPrint.length != CERT_SHA256_FINGER_PRINT_LENGTH) {
+ if (certSha256FingerPrint == null
+ || certSha256FingerPrint.length != CERT_SHA256_FINGER_PRINT_LENGTH) {
Log.d(TAG, "Invalid SHA-256 fingerprint");
return false;
}
@@ -378,8 +427,8 @@
}
SimCredential that = (SimCredential) thatObject;
- return TextUtils.equals(imsi, that.imsi) &&
- eapType == that.eapType;
+ return TextUtils.equals(imsi, that.imsi)
+ && eapType == that.eapType;
}
@Override
@@ -400,8 +449,8 @@
if (!verifyImsi()) {
return false;
}
- if (eapType != EAPConstants.EAP_SIM && eapType != EAPConstants.EAP_AKA &&
- eapType != EAPConstants.EAP_AKA_PRIME) {
+ if (eapType != EAPConstants.EAP_SIM && eapType != EAPConstants.EAP_AKA
+ && eapType != EAPConstants.EAP_AKA_PRIME) {
Log.d(TAG, "Invalid EAP Type for SIM credential: " + eapType);
return false;
}
@@ -490,7 +539,10 @@
*/
public Credential(Credential source) {
if (source != null) {
+ creationTimeInMs = source.creationTimeInMs;
+ expirationTimeInMs = source.expirationTimeInMs;
realm = source.realm;
+ checkAAAServerCertStatus = source.checkAAAServerCertStatus;
if (source.userCredential != null) {
userCredential = new UserCredential(source.userCredential);
}
@@ -516,7 +568,10 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(creationTimeInMs);
+ dest.writeLong(expirationTimeInMs);
dest.writeString(realm);
+ dest.writeInt(checkAAAServerCertStatus ? 1 : 0);
dest.writeParcelable(userCredential, flags);
dest.writeParcelable(certCredential, flags);
dest.writeParcelable(simCredential, flags);
@@ -535,16 +590,19 @@
}
Credential that = (Credential) thatObject;
- return TextUtils.equals(realm, that.realm) &&
- (userCredential == null ? that.userCredential == null :
- userCredential.equals(that.userCredential)) &&
- (certCredential == null ? that.certCredential == null :
- certCredential.equals(that.certCredential)) &&
- (simCredential == null ? that.simCredential == null :
- simCredential.equals(that.simCredential)) &&
- isX509CertificateEquals(caCertificate, that.caCertificate) &&
- isX509CertificatesEquals(clientCertificateChain, that.clientCertificateChain) &&
- isPrivateKeyEquals(clientPrivateKey, that.clientPrivateKey);
+ return TextUtils.equals(realm, that.realm)
+ && creationTimeInMs == that.creationTimeInMs
+ && expirationTimeInMs == that.expirationTimeInMs
+ && checkAAAServerCertStatus == that.checkAAAServerCertStatus
+ && (userCredential == null ? that.userCredential == null
+ : userCredential.equals(that.userCredential))
+ && (certCredential == null ? that.certCredential == null
+ : certCredential.equals(that.certCredential))
+ && (simCredential == null ? that.simCredential == null
+ : simCredential.equals(that.simCredential))
+ && isX509CertificateEquals(caCertificate, that.caCertificate)
+ && isX509CertificatesEquals(clientCertificateChain, that.clientCertificateChain)
+ && isPrivateKeyEquals(clientPrivateKey, that.clientPrivateKey);
}
/**
@@ -557,8 +615,9 @@
Log.d(TAG, "Missing realm");
return false;
}
- if (realm.length() > MAX_REALM_LENGTH) {
- Log.d(TAG, "realm exceeding maximum length: " + realm.length());
+ if (realm.getBytes(StandardCharsets.UTF_8).length > MAX_REALM_BYTES) {
+ Log.d(TAG, "realm exceeding maximum length: "
+ + realm.getBytes(StandardCharsets.UTF_8).length);
return false;
}
@@ -588,7 +647,10 @@
@Override
public Credential createFromParcel(Parcel in) {
Credential credential = new Credential();
+ credential.creationTimeInMs = in.readLong();
+ credential.expirationTimeInMs = in.readLong();
credential.realm = in.readString();
+ credential.checkAAAServerCertStatus = in.readInt() != 0;
credential.userCredential = in.readParcelable(null);
credential.certCredential = in.readParcelable(null);
credential.simCredential = in.readParcelable(null);
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java b/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
index d4a5792..4ddf210 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
@@ -21,7 +21,11 @@
import android.text.TextUtils;
import android.util.Log;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
/**
* Class representing HomeSP subtree in PerProviderSubscription (PPS)
@@ -30,14 +34,22 @@
* 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 we only support the nodes that are used by Hotspot 2.0 Release 1.
- *
* @hide
*/
public final class HomeSP implements Parcelable {
private static final String TAG = "HomeSP";
/**
+ * Maximum number of bytes allowed for a SSID.
+ */
+ private static final int MAX_SSID_BYTES = 32;
+
+ /**
+ * Integer value used for indicating null value in the Parcel.
+ */
+ private static final int NULL_VALUE = -1;
+
+ /**
* FQDN (Fully Qualified Domain Name) of this home service provider.
*/
public String fqdn = null;
@@ -48,6 +60,55 @@
public String friendlyName = null;
/**
+ * Icon URL of this home service provider.
+ */
+ public String iconUrl = null;
+
+ /**
+ * <SSID, HESSID> duple of the networks that are consider home networks.
+ *
+ * According to the Section 9.1.2 of the Hotspot 2.0 Release 2 Technical Specification,
+ * all nodes in the PSS MO are encoded using UTF-8 unless stated otherwise. Thus, the SSID
+ * string is assumed to be encoded using UTF-8.
+ */
+ public Map<String, Long> homeNetworkIds = null;
+
+ /**
+ * Used for determining if this provider is a member of a given Hotspot provider.
+ * Every Organization Identifiers (OIs) in this list are required to match an OI in the
+ * the Roaming Consortium advertised by a Hotspot, in order to consider this provider
+ * as a member of that Hotspot provider (e.g. successful authentication with such Hotspot
+ * is possible).
+ *
+ * Refer to HomeSP/HomeOIList subtree in PerProviderSubscription (PPS) Management Object
+ * (MO) tree for more detail.
+ */
+ public long[] matchAllOIs = null;
+
+ /**
+ * Used for determining if this provider is a member of a given Hotspot provider.
+ * Matching of any Organization Identifiers (OIs) in this list with an OI in the
+ * Roaming Consortium advertised by a Hotspot, will consider this provider as a member
+ * of that Hotspot provider (e.g. successful authentication with such Hotspot
+ * is possible).
+ *
+ * {@link #matchAllOIs} will have precedence over this one, meaning this list will
+ * only be used for matching if {@link #matchAllOIs} is null or empty.
+ *
+ * Refer to HomeSP/HomeOIList subtree in PerProviderSubscription (PPS) Management Object
+ * (MO) tree for more detail.
+ */
+ public long[] matchAnyOIs = null;
+
+ /**
+ * List of FQDN (Fully Qualified Domain Name) of partner providers.
+ * These providers should also be regarded as home Hotspot operators.
+ * This relationship is most likely achieved via a commercial agreement or
+ * operator merges between the providers.
+ */
+ public String[] otherHomePartners = null;
+
+ /**
* List of Organization Identifiers (OIs) identifying a roaming consortium of
* which this provider is a member.
*/
@@ -64,13 +125,28 @@
* @param source The source to copy from
*/
public HomeSP(HomeSP source) {
- if (source != null) {
- fqdn = source.fqdn;
- friendlyName = source.friendlyName;
- if (source.roamingConsortiumOIs != null) {
- roamingConsortiumOIs = Arrays.copyOf(source.roamingConsortiumOIs,
- source.roamingConsortiumOIs.length);
- }
+ if (source == null) {
+ return;
+ }
+ fqdn = source.fqdn;
+ friendlyName = source.friendlyName;
+ iconUrl = source.iconUrl;
+ if (source.homeNetworkIds != null) {
+ homeNetworkIds = Collections.unmodifiableMap(source.homeNetworkIds);
+ }
+ if (source.matchAllOIs != null) {
+ matchAllOIs = Arrays.copyOf(source.matchAllOIs, source.matchAllOIs.length);
+ }
+ if (source.matchAnyOIs != null) {
+ matchAnyOIs = Arrays.copyOf(source.matchAnyOIs, source.matchAnyOIs.length);
+ }
+ if (source.otherHomePartners != null) {
+ otherHomePartners = Arrays.copyOf(source.otherHomePartners,
+ source.otherHomePartners.length);
+ }
+ if (source.roamingConsortiumOIs != null) {
+ roamingConsortiumOIs = Arrays.copyOf(source.roamingConsortiumOIs,
+ source.roamingConsortiumOIs.length);
}
}
@@ -83,6 +159,11 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(fqdn);
dest.writeString(friendlyName);
+ dest.writeString(iconUrl);
+ writeHomeNetworkIds(dest, homeNetworkIds);
+ dest.writeLongArray(matchAllOIs);
+ dest.writeLongArray(matchAnyOIs);
+ dest.writeStringArray(otherHomePartners);
dest.writeLongArray(roamingConsortiumOIs);
}
@@ -96,9 +177,15 @@
}
HomeSP that = (HomeSP) thatObject;
- return TextUtils.equals(fqdn, that.fqdn) &&
- TextUtils.equals(friendlyName, that.friendlyName) &&
- Arrays.equals(roamingConsortiumOIs, that.roamingConsortiumOIs);
+ return TextUtils.equals(fqdn, that.fqdn)
+ && TextUtils.equals(friendlyName, that.friendlyName)
+ && TextUtils.equals(iconUrl, that.iconUrl)
+ && (homeNetworkIds == null ? that.homeNetworkIds == null
+ : homeNetworkIds.equals(that.homeNetworkIds))
+ && Arrays.equals(matchAllOIs, that.matchAllOIs)
+ && Arrays.equals(matchAnyOIs, that.matchAnyOIs)
+ && Arrays.equals(otherHomePartners, that.otherHomePartners)
+ && Arrays.equals(roamingConsortiumOIs, that.roamingConsortiumOIs);
}
/**
@@ -115,6 +202,16 @@
Log.d(TAG, "Missing friendly name");
return false;
}
+ // Verify SSIDs specified in the NetworkID
+ if (homeNetworkIds != null) {
+ for (Map.Entry<String, Long> entry : homeNetworkIds.entrySet()) {
+ if (entry.getKey() == null ||
+ entry.getKey().getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) {
+ Log.d(TAG, "Invalid SSID in HomeNetworkIDs");
+ return false;
+ }
+ }
+ }
return true;
}
@@ -125,6 +222,11 @@
HomeSP homeSp = new HomeSP();
homeSp.fqdn = in.readString();
homeSp.friendlyName = in.readString();
+ homeSp.iconUrl = in.readString();
+ homeSp.homeNetworkIds = readHomeNetworkIds(in);
+ homeSp.matchAllOIs = in.createLongArray();
+ homeSp.matchAnyOIs = in.createLongArray();
+ homeSp.otherHomePartners = in.createStringArray();
homeSp.roamingConsortiumOIs = in.createLongArray();
return homeSp;
}
@@ -133,5 +235,51 @@
public HomeSP[] newArray(int size) {
return new HomeSP[size];
}
+
+ /**
+ * Helper function for reading a Home Network IDs map from a Parcel.
+ *
+ * @param in The Parcel to read from
+ * @return Map of home network IDs
+ */
+ private Map<String, Long> readHomeNetworkIds(Parcel in) {
+ int size = in.readInt();
+ if (size == NULL_VALUE) {
+ return null;
+ }
+ Map<String, Long> networkIds = new HashMap<>(size);
+ for (int i = 0; i < size; i++) {
+ String key = in.readString();
+ Long value = null;
+ long readValue = in.readLong();
+ if (readValue != NULL_VALUE) {
+ value = Long.valueOf(readValue);
+ }
+ networkIds.put(key, value);
+ }
+ return networkIds;
+ }
};
+
+ /**
+ * Helper function for writing Home Network IDs map to a Parcel.
+ *
+ * @param dest The Parcel to write to
+ * @param networkIds The map of home network IDs
+ */
+ private static void writeHomeNetworkIds(Parcel dest, Map<String, Long> networkIds) {
+ if (networkIds == null) {
+ dest.writeInt(NULL_VALUE);
+ return;
+ }
+ dest.writeInt(networkIds.size());
+ for (Map.Entry<String, Long> entry : networkIds.entrySet()) {
+ dest.writeString(entry.getKey());
+ if (entry.getValue() == null) {
+ dest.writeLong(NULL_VALUE);
+ } else {
+ dest.writeLong(entry.getValue());
+ }
+ }
+ }
}
diff --git a/wifi/tests/assets/pps/PerProviderSubscription.xml b/wifi/tests/assets/pps/PerProviderSubscription.xml
index 53d38ad..3969f69 100644
--- a/wifi/tests/assets/pps/PerProviderSubscription.xml
+++ b/wifi/tests/assets/pps/PerProviderSubscription.xml
@@ -23,14 +23,86 @@
<NodeName>RoamingConsortiumOI</NodeName>
<Value>112233,445566</Value>
</Node>
+ <Node>
+ <NodeName>IconURL</NodeName>
+ <Value>icon.test.com</Value>
+ </Node>
+ <Node>
+ <NodeName>NetworkID</NodeName>
+ <Node>
+ <NodeName>n001</NodeName>
+ <Node>
+ <NodeName>SSID</NodeName>
+ <Value>TestSSID</Value>
+ </Node>
+ <Node>
+ <NodeName>HESSID</NodeName>
+ <Value>12345678</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>n002</NodeName>
+ <Node>
+ <NodeName>SSID</NodeName>
+ <Value>NullHESSID</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>HomeOIList</NodeName>
+ <Node>
+ <NodeName>h001</NodeName>
+ <Node>
+ <NodeName>HomeOI</NodeName>
+ <Value>11223344</Value>
+ </Node>
+ <Node>
+ <NodeName>HomeOIRequired</NodeName>
+ <Value>true</Value>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>h002</NodeName>
+ <Node>
+ <NodeName>HomeOI</NodeName>
+ <Value>55667788</Value>
+ </Node>
+ <Node>
+ <NodeName>HomeOIRequired</NodeName>
+ <Value>false</Value>
+ </Node>
+ </Node>
+ </Node>
+ <Node>
+ <NodeName>OtherHomePartners</NodeName>
+ <Node>
+ <NodeName>o001</NodeName>
+ <Node>
+ <NodeName>FQDN</NodeName>
+ <Value>other.fqdn.com</Value>
+ </Node>
+ </Node>
+ </Node>
</Node>
<Node>
<NodeName>Credential</NodeName>
<Node>
+ <NodeName>CreationDate</NodeName>
+ <Value>2016-01-01T10:00:00Z</Value>
+ </Node>
+ <Node>
+ <NodeName>ExpirationDate</NodeName>
+ <Value>2016-02-01T10:00:00Z</Value>
+ </Node>
+ <Node>
<NodeName>Realm</NodeName>
<Value>shaken.stirred.com</Value>
</Node>
<Node>
+ <NodeName>CheckAAAServerCertStatus</NodeName>
+ <Value>true</Value>
+ </Node>
+ <Node>
<NodeName>UsernamePassword</NodeName>
<Node>
<NodeName>Username</NodeName>
@@ -41,6 +113,18 @@
<Value>Ym9uZDAwNw==</Value>
</Node>
<Node>
+ <NodeName>MachineManaged</NodeName>
+ <Value>true</Value>
+ </Node>
+ <Node>
+ <NodeName>SoftTokenApp</NodeName>
+ <Value>TestApp</Value>
+ </Node>
+ <Node>
+ <NodeName>AbleToShare</NodeName>
+ <Value>true</Value>
+ </Node>
+ <Node>
<NodeName>EAPMethod</NodeName>
<Node>
<NodeName>EAPType</NodeName>
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 10b0267..1c7508e 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
@@ -31,7 +31,10 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
import java.util.Arrays;
+import java.util.HashMap;
/**
* Unit tests for {@link android.net.wifi.hotspot2.omadm.PPSMOParser}.
@@ -77,7 +80,7 @@
*
* @return {@link PasspointConfiguration}
*/
- private PasspointConfiguration generateConfigurationFromPPSMOTree() {
+ private PasspointConfiguration generateConfigurationFromPPSMOTree() throws Exception {
PasspointConfiguration config = new PasspointConfiguration();
// HomeSP configuration.
@@ -85,13 +88,27 @@
config.homeSp.friendlyName = "Century House";
config.homeSp.fqdn = "mi6.co.uk";
config.homeSp.roamingConsortiumOIs = new long[] {0x112233L, 0x445566L};
+ config.homeSp.iconUrl = "icon.test.com";
+ config.homeSp.homeNetworkIds = new HashMap<>();
+ config.homeSp.homeNetworkIds.put("TestSSID", 0x12345678L);
+ config.homeSp.homeNetworkIds.put("NullHESSID", null);
+ config.homeSp.matchAllOIs = new long[] {0x11223344};
+ config.homeSp.matchAnyOIs = new long[] {0x55667788};
+ 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();
config.credential.realm = "shaken.stirred.com";
+ config.credential.checkAAAServerCertStatus = true;
config.credential.userCredential = new Credential.UserCredential();
config.credential.userCredential.username = "james";
config.credential.userCredential.password = "Ym9uZDAwNw==";
+ config.credential.userCredential.machineManaged = true;
+ config.credential.userCredential.softTokenApp = "TestApp";
+ config.credential.userCredential.ableToShare = true;
config.credential.userCredential.eapType = 21;
config.credential.userCredential.nonEapInnerMethod = "MS-CHAP-V2";
config.credential.certCredential = new Credential.CertificateCredential();
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
index 9c8b749..f571c7f 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
@@ -37,6 +37,17 @@
*/
@SmallTest
public class CredentialTest {
+ /**
+ * Helper function for generating Credential for testing.
+ *
+ * @param userCred Instance of UserCredential
+ * @param certCred Instance of CertificateCredential
+ * @param simCred Instance of SimCredential
+ * @param caCert CA certificate
+ * @param clientCertificateChain Chain of client certificates
+ * @param clientPrivateKey Client private key
+ * @return {@link Credential}
+ */
private static Credential createCredential(Credential.UserCredential userCred,
Credential.CertificateCredential certCred,
Credential.SimCredential simCred,
@@ -44,7 +55,10 @@
X509Certificate[] clientCertificateChain,
PrivateKey clientPrivateKey) {
Credential cred = new Credential();
+ cred.creationTimeInMs = 123455L;
+ cred.expirationTimeInMs = 2310093L;
cred.realm = "realm";
+ cred.checkAAAServerCertStatus = true;
cred.userCredential = userCred;
cred.certCredential = certCred;
cred.simCredential = simCred;
@@ -54,6 +68,11 @@
return cred;
}
+ /**
+ * Helper function for generating certificate credential for testing.
+ *
+ * @return {@link Credential}
+ */
private static Credential createCredentialWithCertificateCredential() {
Credential.CertificateCredential certCred = new Credential.CertificateCredential();
certCred.certType = "x509v3";
@@ -62,6 +81,11 @@
new X509Certificate[] {FakeKeys.CLIENT_CERT}, FakeKeys.RSA_KEY1);
}
+ /**
+ * Helper function for generating SIM credential for testing.
+ *
+ * @return {@link Credential}
+ */
private static Credential createCredentialWithSimCredential() {
Credential.SimCredential simCred = new Credential.SimCredential();
simCred.imsi = "1234*";
@@ -69,10 +93,18 @@
return createCredential(null, null, simCred, null, null, null);
}
+ /**
+ * Helper function for generating user credential for testing.
+ *
+ * @return {@link Credential}
+ */
private static Credential createCredentialWithUserCredential() {
Credential.UserCredential userCred = new Credential.UserCredential();
userCred.username = "username";
userCred.password = "password";
+ userCred.machineManaged = true;
+ userCred.ableToShare = true;
+ userCred.softTokenApp = "TestApp";
userCred.eapType = EAPConstants.EAP_TTLS;
userCred.nonEapInnerMethod = "MS-CHAP";
return createCredential(userCred, null, null, FakeKeys.CA_CERT0,
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java
index c707993..45fdbea 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java
@@ -24,19 +24,71 @@
import org.junit.Test;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* Unit tests for {@link android.net.wifi.hotspot2.pps.HomeSP}.
*/
@SmallTest
public class HomeSPTest {
- private static HomeSP createHomeSp() {
+
+ /**
+ * Helper function for creating a map of home network IDs for testing.
+ *
+ * @return Map of home network IDs
+ */
+ private static Map<String, Long> createHomeNetworkIds() {
+ Map<String, Long> homeNetworkIds = new HashMap<>();
+ homeNetworkIds.put("ssid", 0x1234L);
+ homeNetworkIds.put("nullhessid", null);
+ return homeNetworkIds;
+ }
+
+ /**
+ * Helper function for creating a HomeSP for testing.
+ *
+ * @param homeNetworkIds The map of home network IDs associated with HomeSP
+ * @return {@link HomeSP}
+ */
+ private static HomeSP createHomeSp(Map<String, Long> homeNetworkIds) {
HomeSP homeSp = new HomeSP();
homeSp.fqdn = "fqdn";
homeSp.friendlyName = "friendly name";
+ homeSp.iconUrl = "icon.url";
+ homeSp.homeNetworkIds = homeNetworkIds;
+ homeSp.matchAllOIs = new long[] {0x11L, 0x22L};
+ homeSp.matchAnyOIs = new long[] {0x33L, 0x44L};
+ homeSp.otherHomePartners = new String[] {"partner1", "partner2"};
homeSp.roamingConsortiumOIs = new long[] {0x55, 0x66};
return homeSp;
}
+ /**
+ * Helper function for creating a HomeSP with home network IDs for testing.
+ *
+ * @return {@link HomeSP}
+ */
+ private static HomeSP createHomeSpWithHomeNetworkIds() {
+ return createHomeSp(createHomeNetworkIds());
+ }
+
+ /**
+ * Helper function for creating a HomeSP without home network IDs for testing.
+ *
+ * @return {@link HomeSP}
+ */
+ private static HomeSP createHomeSpWithoutHomeNetworkIds() {
+ return createHomeSp(null);
+ }
+
+ /**
+ * Helper function for verifying HomeSP after parcel write then read.
+ * @param writeHomeSp
+ * @throws Exception
+ */
private static void verifyParcel(HomeSP writeHomeSp) throws Exception {
Parcel parcel = Parcel.obtain();
writeHomeSp.writeToParcel(parcel, 0);
@@ -57,13 +109,23 @@
}
/**
- * Verify parcel read/write for a valid HomeSP.
+ * Verify parcel read/write for a HomeSP containing Home Network IDs.
*
* @throws Exception
*/
@Test
- public void verifyParcelWithValidHomeSP() throws Exception {
- verifyParcel(createHomeSp());
+ public void verifyParcelWithHomeNetworkIds() throws Exception {
+ verifyParcel(createHomeSpWithHomeNetworkIds());
+ }
+
+ /**
+ * Verify parcel read/write for a HomeSP without Home Network IDs.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void verifyParcelWithoutHomeNetworkIds() throws Exception {
+ verifyParcel(createHomeSpWithoutHomeNetworkIds());
}
/**
@@ -120,6 +182,49 @@
}
/**
+ * Verify that a HomeSP is valid when the optional Home Network IDs are
+ * provided.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateHomeSpWithHomeNetworkIds() throws Exception {
+ HomeSP homeSp = createHomeSpWithHomeNetworkIds();
+ assertTrue(homeSp.validate());
+ }
+
+ /**
+ * Verify that a HomeSP is valid when the optional Home Network IDs are
+ * not provided.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateHomeSpWithoutHomeNetworkIds() throws Exception {
+ HomeSP homeSp = createHomeSpWithoutHomeNetworkIds();
+ assertTrue(homeSp.validate());
+ }
+
+ /**
+ * Verify that a HomeSP is invalid when the optional Home Network IDs
+ * contained an invalid SSID (exceeding maximum number of bytes).
+ *
+ * @throws Exception
+ */
+ @Test
+ public void validateHomeSpWithInvalidHomeNetworkIds() throws Exception {
+ HomeSP homeSp = new HomeSP();
+ homeSp.fqdn = "fqdn";
+ homeSp.friendlyName = "friendly name";
+ homeSp.homeNetworkIds = new HashMap<>();
+ byte[] rawSsidBytes = new byte[33];
+ Arrays.fill(rawSsidBytes, (byte) 'a');
+ homeSp.homeNetworkIds.put(
+ StringFactory.newStringFromBytes(rawSsidBytes, StandardCharsets.UTF_8), 0x1234L);
+ assertFalse(homeSp.validate());
+ }
+
+ /**
* Verify that copy constructor works when pass in a null source.
*
* @throws Exception
@@ -138,10 +243,7 @@
*/
@Test
public void validateCopyConstructorFromValidSource() throws Exception {
- HomeSP sourceSp = new HomeSP();
- sourceSp.fqdn = "fqdn";
- sourceSp.friendlyName = "friendlyName";
- sourceSp.roamingConsortiumOIs = new long[] {0x55, 0x66};
+ HomeSP sourceSp = createHomeSpWithHomeNetworkIds();
HomeSP copySp = new HomeSP(sourceSp);
assertTrue(copySp.equals(sourceSp));
}