Merge "Add support for Split APK dependcies"
diff --git a/api/current.txt b/api/current.txt
index c5bde1d..18e4adf 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5018,6 +5018,7 @@
method public android.graphics.drawable.Icon getLargeIcon();
method public android.graphics.drawable.Icon getSmallIcon();
method public java.lang.String getSortKey();
+ method public long getTimeout();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.media.AudioAttributes AUDIO_ATTRIBUTES_DEFAULT;
field public static final java.lang.String CATEGORY_ALARM = "alarm";
@@ -5246,6 +5247,7 @@
method public android.app.Notification.Builder setSubText(java.lang.CharSequence);
method public android.app.Notification.Builder setTicker(java.lang.CharSequence);
method public deprecated android.app.Notification.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews);
+ method public android.app.Notification.Builder setTimeout(long);
method public android.app.Notification.Builder setUsesChronometer(boolean);
method public android.app.Notification.Builder setVibrate(long[]);
method public android.app.Notification.Builder setVisibility(int);
@@ -6122,16 +6124,18 @@
method public java.util.List<java.lang.String> getAffiliationIds(android.content.ComponentName);
method public java.lang.String getAlwaysOnVpnPackage(android.content.ComponentName);
method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
- method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
+ method public deprecated java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
method public boolean getAutoTimeRequired();
method public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(android.content.ComponentName);
method public boolean getBluetoothContactSharingDisabled(android.content.ComponentName);
method public boolean getCameraDisabled(android.content.ComponentName);
- method public java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
+ method public deprecated java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
method public boolean getCrossProfileCallerIdDisabled(android.content.ComponentName);
method public boolean getCrossProfileContactsSearchDisabled(android.content.ComponentName);
method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
method public int getCurrentFailedPasswordAttempts();
+ method public java.util.List<java.lang.String> getDelegatePackages(android.content.ComponentName, java.lang.String);
+ method public java.util.List<java.lang.String> getDelegatedScopes(android.content.ComponentName, java.lang.String);
method public java.lang.CharSequence getDeviceOwnerLockScreenInfo();
method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
method public int getKeyguardDisabledFeatures(android.content.ComponentName);
@@ -6176,7 +6180,7 @@
method public boolean isAdminActive(android.content.ComponentName);
method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
method public boolean isBackupServiceEnabled(android.content.ComponentName);
- method public boolean isCallerApplicationRestrictionsManagingPackage();
+ method public deprecated boolean isCallerApplicationRestrictionsManagingPackage();
method public boolean isDeviceOwnerApp(java.lang.String);
method public boolean isLockTaskPermitted(java.lang.String);
method public boolean isManagedProfile(android.content.ComponentName);
@@ -6204,14 +6208,15 @@
method public void setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String, boolean) throws android.content.pm.PackageManager.NameNotFoundException, java.lang.UnsupportedOperationException;
method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean);
method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle);
- method public void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public deprecated void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public void setAutoTimeRequired(android.content.ComponentName, boolean);
method public void setBackupServiceEnabled(android.content.ComponentName, boolean);
method public void setBluetoothContactSharingDisabled(android.content.ComponentName, boolean);
method public void setCameraDisabled(android.content.ComponentName, boolean);
- method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
+ method public deprecated void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean);
+ method public void setDelegatedScopes(android.content.ComponentName, java.lang.String, java.util.List<java.lang.String>);
method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence);
method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
@@ -6259,6 +6264,7 @@
method public void uninstallCaCert(android.content.ComponentName, byte[]);
method public void wipeData(int);
field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
+ field public static final java.lang.String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
field public static final java.lang.String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
field public static final java.lang.String ACTION_PROVISIONING_SUCCESSFUL = "android.app.action.PROVISIONING_SUCCESSFUL";
@@ -6268,6 +6274,8 @@
field public static final java.lang.String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD";
field public static final java.lang.String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION";
field public static final java.lang.String ACTION_SYSTEM_UPDATE_POLICY_CHANGED = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED";
+ field public static final java.lang.String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions";
+ field public static final java.lang.String DELEGATION_CERT_INSTALL = "delegation-cert-install";
field public static final int ENCRYPTION_STATUS_ACTIVATING = 2; // 0x2
field public static final int ENCRYPTION_STATUS_ACTIVE = 3; // 0x3
field public static final int ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY = 4; // 0x4
@@ -6275,6 +6283,7 @@
field public static final int ENCRYPTION_STATUS_INACTIVE = 1; // 0x1
field public static final int ENCRYPTION_STATUS_UNSUPPORTED = 0; // 0x0
field public static final java.lang.String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION";
+ field public static final java.lang.String EXTRA_DELEGATION_SCOPES = "android.app.extra.DELEGATION_SCOPES";
field public static final java.lang.String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN";
field public static final java.lang.String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE = "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE";
field public static final java.lang.String EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE = "android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE";
@@ -14213,6 +14222,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();
@@ -14226,6 +14236,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
@@ -14303,6 +14314,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;
@@ -14334,6 +14356,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[]);
@@ -30759,6 +30784,7 @@
method public android.preference.Preference.OnPreferenceChangeListener getOnPreferenceChangeListener();
method public android.preference.Preference.OnPreferenceClickListener getOnPreferenceClickListener();
method public int getOrder();
+ method public android.preference.PreferenceGroup getParent();
method protected boolean getPersistedBoolean(boolean);
method protected float getPersistedFloat(float);
method protected int getPersistedInt(int);
@@ -35937,9 +35963,9 @@
field public static final int REASON_PACKAGE_SUSPENDED = 14; // 0xe
field public static final int REASON_PROFILE_TURNED_OFF = 15; // 0xf
field public static final int REASON_SNOOZED = 18; // 0x12
+ field public static final int REASON_TIMEOUT = 19; // 0x13
field public static final int REASON_UNAUTOBUNDLED = 16; // 0x10
field public static final int REASON_USER_STOPPED = 6; // 0x6
- field public static final int REASON_USER_SWITCH = 19; // 0x13
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
field public static final int SUPPRESSED_EFFECT_SCREEN_OFF = 1; // 0x1
field public static final int SUPPRESSED_EFFECT_SCREEN_ON = 2; // 0x2
@@ -62060,6 +62086,9 @@
method public static <E> java.util.Collection<E> checkedCollection(java.util.Collection<E>, java.lang.Class<E>);
method public static <E> java.util.List<E> checkedList(java.util.List<E>, java.lang.Class<E>);
method public static <K, V> java.util.Map<K, V> checkedMap(java.util.Map<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+ method public static <K, V> java.util.NavigableMap<K, V> checkedNavigableMap(java.util.NavigableMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+ method public static <E> java.util.NavigableSet<E> checkedNavigableSet(java.util.NavigableSet<E>, java.lang.Class<E>);
+ method public static <E> java.util.Queue<E> checkedQueue(java.util.Queue<E>, java.lang.Class<E>);
method public static <E> java.util.Set<E> checkedSet(java.util.Set<E>, java.lang.Class<E>);
method public static <K, V> java.util.SortedMap<K, V> checkedSortedMap(java.util.SortedMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
method public static <E> java.util.SortedSet<E> checkedSortedSet(java.util.SortedSet<E>, java.lang.Class<E>);
@@ -62070,7 +62099,11 @@
method public static final <T> java.util.List<T> emptyList();
method public static <T> java.util.ListIterator<T> emptyListIterator();
method public static final <K, V> java.util.Map<K, V> emptyMap();
+ method public static final <K, V> java.util.NavigableMap<K, V> emptyNavigableMap();
+ method public static <E> java.util.NavigableSet<E> emptyNavigableSet();
method public static final <T> java.util.Set<T> emptySet();
+ method public static final <K, V> java.util.SortedMap<K, V> emptySortedMap();
+ method public static <E> java.util.SortedSet<E> emptySortedSet();
method public static <T> java.util.Enumeration<T> enumeration(java.util.Collection<T>);
method public static <T> void fill(java.util.List<? super T>, T);
method public static int frequency(java.util.Collection<?>, java.lang.Object);
@@ -62099,12 +62132,16 @@
method public static <T> java.util.Collection<T> synchronizedCollection(java.util.Collection<T>);
method public static <T> java.util.List<T> synchronizedList(java.util.List<T>);
method public static <K, V> java.util.Map<K, V> synchronizedMap(java.util.Map<K, V>);
+ method public static <K, V> java.util.NavigableMap<K, V> synchronizedNavigableMap(java.util.NavigableMap<K, V>);
+ method public static <T> java.util.NavigableSet<T> synchronizedNavigableSet(java.util.NavigableSet<T>);
method public static <T> java.util.Set<T> synchronizedSet(java.util.Set<T>);
method public static <K, V> java.util.SortedMap<K, V> synchronizedSortedMap(java.util.SortedMap<K, V>);
method public static <T> java.util.SortedSet<T> synchronizedSortedSet(java.util.SortedSet<T>);
method public static <T> java.util.Collection<T> unmodifiableCollection(java.util.Collection<? extends T>);
method public static <T> java.util.List<T> unmodifiableList(java.util.List<? extends T>);
method public static <K, V> java.util.Map<K, V> unmodifiableMap(java.util.Map<? extends K, ? extends V>);
+ method public static <K, V> java.util.NavigableMap<K, V> unmodifiableNavigableMap(java.util.NavigableMap<K, ? extends V>);
+ method public static <T> java.util.NavigableSet<T> unmodifiableNavigableSet(java.util.NavigableSet<T>);
method public static <T> java.util.Set<T> unmodifiableSet(java.util.Set<? extends T>);
method public static <K, V> java.util.SortedMap<K, V> unmodifiableSortedMap(java.util.SortedMap<K, ? extends V>);
method public static <T> java.util.SortedSet<T> unmodifiableSortedSet(java.util.SortedSet<T>);
diff --git a/api/system-current.txt b/api/system-current.txt
index 9da65a3..a192c92 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5178,6 +5178,7 @@
method public static java.lang.Class<? extends android.app.Notification.Style> getNotificationStyleClass(java.lang.String);
method public android.graphics.drawable.Icon getSmallIcon();
method public java.lang.String getSortKey();
+ method public long getTimeout();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.media.AudioAttributes AUDIO_ATTRIBUTES_DEFAULT;
field public static final java.lang.String CATEGORY_ALARM = "alarm";
@@ -5408,6 +5409,7 @@
method public android.app.Notification.Builder setSubText(java.lang.CharSequence);
method public android.app.Notification.Builder setTicker(java.lang.CharSequence);
method public deprecated android.app.Notification.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews);
+ method public android.app.Notification.Builder setTimeout(long);
method public android.app.Notification.Builder setUsesChronometer(boolean);
method public android.app.Notification.Builder setVibrate(long[]);
method public android.app.Notification.Builder setVisibility(int);
@@ -6318,16 +6320,18 @@
method public java.util.List<java.lang.String> getAffiliationIds(android.content.ComponentName);
method public java.lang.String getAlwaysOnVpnPackage(android.content.ComponentName);
method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
- method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
+ method public deprecated java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
method public boolean getAutoTimeRequired();
method public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(android.content.ComponentName);
method public boolean getBluetoothContactSharingDisabled(android.content.ComponentName);
method public boolean getCameraDisabled(android.content.ComponentName);
- method public java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
+ method public deprecated java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
method public boolean getCrossProfileCallerIdDisabled(android.content.ComponentName);
method public boolean getCrossProfileContactsSearchDisabled(android.content.ComponentName);
method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
method public int getCurrentFailedPasswordAttempts();
+ method public java.util.List<java.lang.String> getDelegatePackages(android.content.ComponentName, java.lang.String);
+ method public java.util.List<java.lang.String> getDelegatedScopes(android.content.ComponentName, java.lang.String);
method public deprecated java.lang.String getDeviceInitializerApp();
method public deprecated android.content.ComponentName getDeviceInitializerComponent();
method public java.lang.String getDeviceOwner();
@@ -6383,7 +6387,7 @@
method public boolean isAdminActive(android.content.ComponentName);
method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
method public boolean isBackupServiceEnabled(android.content.ComponentName);
- method public boolean isCallerApplicationRestrictionsManagingPackage();
+ method public deprecated boolean isCallerApplicationRestrictionsManagingPackage();
method public boolean isDeviceManaged();
method public boolean isDeviceOwnerApp(java.lang.String);
method public boolean isDeviceProvisioned();
@@ -6418,14 +6422,15 @@
method public void setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String, boolean) throws android.content.pm.PackageManager.NameNotFoundException, java.lang.UnsupportedOperationException;
method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean);
method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle);
- method public void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public deprecated void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public void setAutoTimeRequired(android.content.ComponentName, boolean);
method public void setBackupServiceEnabled(android.content.ComponentName, boolean);
method public void setBluetoothContactSharingDisabled(android.content.ComponentName, boolean);
method public void setCameraDisabled(android.content.ComponentName, boolean);
- method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
+ method public deprecated void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean);
+ method public void setDelegatedScopes(android.content.ComponentName, java.lang.String, java.util.List<java.lang.String>);
method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence);
method public void setDeviceProvisioningConfigApplied();
method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
@@ -6474,6 +6479,7 @@
method public void uninstallCaCert(android.content.ComponentName, byte[]);
method public void wipeData(int);
field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
+ field public static final java.lang.String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
field public static final java.lang.String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
field public static final java.lang.String ACTION_PROVISIONING_SUCCESSFUL = "android.app.action.PROVISIONING_SUCCESSFUL";
@@ -6486,6 +6492,8 @@
field public static final java.lang.String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
field public static final java.lang.String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION";
field public static final java.lang.String ACTION_SYSTEM_UPDATE_POLICY_CHANGED = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED";
+ field public static final java.lang.String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions";
+ field public static final java.lang.String DELEGATION_CERT_INSTALL = "delegation-cert-install";
field public static final int ENCRYPTION_STATUS_ACTIVATING = 2; // 0x2
field public static final int ENCRYPTION_STATUS_ACTIVE = 3; // 0x3
field public static final int ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY = 4; // 0x4
@@ -6493,6 +6501,7 @@
field public static final int ENCRYPTION_STATUS_INACTIVE = 1; // 0x1
field public static final int ENCRYPTION_STATUS_UNSUPPORTED = 0; // 0x0
field public static final java.lang.String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION";
+ field public static final java.lang.String EXTRA_DELEGATION_SCOPES = "android.app.extra.DELEGATION_SCOPES";
field public static final java.lang.String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN";
field public static final java.lang.String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
field public static final java.lang.String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE = "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE";
@@ -14771,6 +14780,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();
@@ -14786,6 +14796,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
@@ -14867,6 +14878,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;
@@ -14898,6 +14920,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[]);
@@ -33592,6 +33617,7 @@
method public android.preference.Preference.OnPreferenceChangeListener getOnPreferenceChangeListener();
method public android.preference.Preference.OnPreferenceClickListener getOnPreferenceClickListener();
method public int getOrder();
+ method public android.preference.PreferenceGroup getParent();
method protected boolean getPersistedBoolean(boolean);
method protected float getPersistedFloat(float);
method protected int getPersistedInt(int);
@@ -38414,6 +38440,18 @@
package android.security.keystore {
+ public abstract class AttestationUtils {
+ method public static java.security.cert.X509Certificate[] attestDeviceIds(android.content.Context, int[], byte[]) throws android.security.keystore.DeviceIdAttestationException;
+ field public static final int ID_TYPE_IMEI = 2; // 0x2
+ field public static final int ID_TYPE_MEID = 3; // 0x3
+ field public static final int ID_TYPE_SERIAL = 1; // 0x1
+ }
+
+ public class DeviceIdAttestationException extends java.lang.Exception {
+ ctor public DeviceIdAttestationException(java.lang.String);
+ ctor public DeviceIdAttestationException(java.lang.String, java.lang.Throwable);
+ }
+
public class KeyExpiredException extends java.security.InvalidKeyException {
ctor public KeyExpiredException();
ctor public KeyExpiredException(java.lang.String);
@@ -38946,9 +38984,9 @@
field public static final int REASON_PACKAGE_SUSPENDED = 14; // 0xe
field public static final int REASON_PROFILE_TURNED_OFF = 15; // 0xf
field public static final int REASON_SNOOZED = 18; // 0x12
+ field public static final int REASON_TIMEOUT = 19; // 0x13
field public static final int REASON_UNAUTOBUNDLED = 16; // 0x10
field public static final int REASON_USER_STOPPED = 6; // 0x6
- field public static final int REASON_USER_SWITCH = 19; // 0x13
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
field public static final int SUPPRESSED_EFFECT_SCREEN_OFF = 1; // 0x1
field public static final int SUPPRESSED_EFFECT_SCREEN_ON = 2; // 0x2
@@ -65794,6 +65832,9 @@
method public static <E> java.util.Collection<E> checkedCollection(java.util.Collection<E>, java.lang.Class<E>);
method public static <E> java.util.List<E> checkedList(java.util.List<E>, java.lang.Class<E>);
method public static <K, V> java.util.Map<K, V> checkedMap(java.util.Map<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+ method public static <K, V> java.util.NavigableMap<K, V> checkedNavigableMap(java.util.NavigableMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+ method public static <E> java.util.NavigableSet<E> checkedNavigableSet(java.util.NavigableSet<E>, java.lang.Class<E>);
+ method public static <E> java.util.Queue<E> checkedQueue(java.util.Queue<E>, java.lang.Class<E>);
method public static <E> java.util.Set<E> checkedSet(java.util.Set<E>, java.lang.Class<E>);
method public static <K, V> java.util.SortedMap<K, V> checkedSortedMap(java.util.SortedMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
method public static <E> java.util.SortedSet<E> checkedSortedSet(java.util.SortedSet<E>, java.lang.Class<E>);
@@ -65804,7 +65845,11 @@
method public static final <T> java.util.List<T> emptyList();
method public static <T> java.util.ListIterator<T> emptyListIterator();
method public static final <K, V> java.util.Map<K, V> emptyMap();
+ method public static final <K, V> java.util.NavigableMap<K, V> emptyNavigableMap();
+ method public static <E> java.util.NavigableSet<E> emptyNavigableSet();
method public static final <T> java.util.Set<T> emptySet();
+ method public static final <K, V> java.util.SortedMap<K, V> emptySortedMap();
+ method public static <E> java.util.SortedSet<E> emptySortedSet();
method public static <T> java.util.Enumeration<T> enumeration(java.util.Collection<T>);
method public static <T> void fill(java.util.List<? super T>, T);
method public static int frequency(java.util.Collection<?>, java.lang.Object);
@@ -65833,12 +65878,16 @@
method public static <T> java.util.Collection<T> synchronizedCollection(java.util.Collection<T>);
method public static <T> java.util.List<T> synchronizedList(java.util.List<T>);
method public static <K, V> java.util.Map<K, V> synchronizedMap(java.util.Map<K, V>);
+ method public static <K, V> java.util.NavigableMap<K, V> synchronizedNavigableMap(java.util.NavigableMap<K, V>);
+ method public static <T> java.util.NavigableSet<T> synchronizedNavigableSet(java.util.NavigableSet<T>);
method public static <T> java.util.Set<T> synchronizedSet(java.util.Set<T>);
method public static <K, V> java.util.SortedMap<K, V> synchronizedSortedMap(java.util.SortedMap<K, V>);
method public static <T> java.util.SortedSet<T> synchronizedSortedSet(java.util.SortedSet<T>);
method public static <T> java.util.Collection<T> unmodifiableCollection(java.util.Collection<? extends T>);
method public static <T> java.util.List<T> unmodifiableList(java.util.List<? extends T>);
method public static <K, V> java.util.Map<K, V> unmodifiableMap(java.util.Map<? extends K, ? extends V>);
+ method public static <K, V> java.util.NavigableMap<K, V> unmodifiableNavigableMap(java.util.NavigableMap<K, ? extends V>);
+ method public static <T> java.util.NavigableSet<T> unmodifiableNavigableSet(java.util.NavigableSet<T>);
method public static <T> java.util.Set<T> unmodifiableSet(java.util.Set<? extends T>);
method public static <K, V> java.util.SortedMap<K, V> unmodifiableSortedMap(java.util.SortedMap<K, ? extends V>);
method public static <T> java.util.SortedSet<T> unmodifiableSortedSet(java.util.SortedSet<T>);
diff --git a/api/test-current.txt b/api/test-current.txt
index c9f1d1c..47440e6 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5028,6 +5028,7 @@
method public android.graphics.drawable.Icon getLargeIcon();
method public android.graphics.drawable.Icon getSmallIcon();
method public java.lang.String getSortKey();
+ method public long getTimeout();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.media.AudioAttributes AUDIO_ATTRIBUTES_DEFAULT;
field public static final java.lang.String CATEGORY_ALARM = "alarm";
@@ -5256,6 +5257,7 @@
method public android.app.Notification.Builder setSubText(java.lang.CharSequence);
method public android.app.Notification.Builder setTicker(java.lang.CharSequence);
method public deprecated android.app.Notification.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews);
+ method public android.app.Notification.Builder setTimeout(long);
method public android.app.Notification.Builder setUsesChronometer(boolean);
method public android.app.Notification.Builder setVibrate(long[]);
method public android.app.Notification.Builder setVisibility(int);
@@ -6139,16 +6141,18 @@
method public java.util.List<java.lang.String> getAffiliationIds(android.content.ComponentName);
method public java.lang.String getAlwaysOnVpnPackage(android.content.ComponentName);
method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
- method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
+ method public deprecated java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
method public boolean getAutoTimeRequired();
method public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(android.content.ComponentName);
method public boolean getBluetoothContactSharingDisabled(android.content.ComponentName);
method public boolean getCameraDisabled(android.content.ComponentName);
- method public java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
+ method public deprecated java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
method public boolean getCrossProfileCallerIdDisabled(android.content.ComponentName);
method public boolean getCrossProfileContactsSearchDisabled(android.content.ComponentName);
method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
method public int getCurrentFailedPasswordAttempts();
+ method public java.util.List<java.lang.String> getDelegatePackages(android.content.ComponentName, java.lang.String);
+ method public java.util.List<java.lang.String> getDelegatedScopes(android.content.ComponentName, java.lang.String);
method public java.lang.CharSequence getDeviceOwnerLockScreenInfo();
method public java.lang.CharSequence getDeviceOwnerOrganizationName();
method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
@@ -6197,7 +6201,7 @@
method public boolean isAdminActive(android.content.ComponentName);
method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
method public boolean isBackupServiceEnabled(android.content.ComponentName);
- method public boolean isCallerApplicationRestrictionsManagingPackage();
+ method public deprecated boolean isCallerApplicationRestrictionsManagingPackage();
method public boolean isDeviceManaged();
method public boolean isDeviceOwnerApp(java.lang.String);
method public boolean isLockTaskPermitted(java.lang.String);
@@ -6226,14 +6230,15 @@
method public void setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String, boolean) throws android.content.pm.PackageManager.NameNotFoundException, java.lang.UnsupportedOperationException;
method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean);
method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle);
- method public void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public deprecated void setApplicationRestrictionsManagingPackage(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
method public void setAutoTimeRequired(android.content.ComponentName, boolean);
method public void setBackupServiceEnabled(android.content.ComponentName, boolean);
method public void setBluetoothContactSharingDisabled(android.content.ComponentName, boolean);
method public void setCameraDisabled(android.content.ComponentName, boolean);
- method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
+ method public deprecated void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean);
+ method public void setDelegatedScopes(android.content.ComponentName, java.lang.String, java.util.List<java.lang.String>);
method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence);
method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
@@ -6281,6 +6286,7 @@
method public void uninstallCaCert(android.content.ComponentName, byte[]);
method public void wipeData(int);
field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
+ field public static final java.lang.String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
field public static final java.lang.String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
field public static final java.lang.String ACTION_PROVISIONING_SUCCESSFUL = "android.app.action.PROVISIONING_SUCCESSFUL";
@@ -6290,6 +6296,8 @@
field public static final java.lang.String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD";
field public static final java.lang.String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION";
field public static final java.lang.String ACTION_SYSTEM_UPDATE_POLICY_CHANGED = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED";
+ field public static final java.lang.String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions";
+ field public static final java.lang.String DELEGATION_CERT_INSTALL = "delegation-cert-install";
field public static final int ENCRYPTION_STATUS_ACTIVATING = 2; // 0x2
field public static final int ENCRYPTION_STATUS_ACTIVE = 3; // 0x3
field public static final int ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY = 4; // 0x4
@@ -6297,6 +6305,7 @@
field public static final int ENCRYPTION_STATUS_INACTIVE = 1; // 0x1
field public static final int ENCRYPTION_STATUS_UNSUPPORTED = 0; // 0x0
field public static final java.lang.String EXTRA_ADD_EXPLANATION = "android.app.extra.ADD_EXPLANATION";
+ field public static final java.lang.String EXTRA_DELEGATION_SCOPES = "android.app.extra.DELEGATION_SCOPES";
field public static final java.lang.String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN";
field public static final java.lang.String EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE = "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE";
field public static final java.lang.String EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE = "android.app.extra.PROVISIONING_ADMIN_EXTRAS_BUNDLE";
@@ -14245,6 +14254,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();
@@ -14258,6 +14268,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
@@ -14335,6 +14346,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;
@@ -14366,6 +14388,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[]);
@@ -30872,6 +30897,7 @@
method public android.preference.Preference.OnPreferenceChangeListener getOnPreferenceChangeListener();
method public android.preference.Preference.OnPreferenceClickListener getOnPreferenceClickListener();
method public int getOrder();
+ method public android.preference.PreferenceGroup getParent();
method protected boolean getPersistedBoolean(boolean);
method protected float getPersistedFloat(float);
method protected int getPersistedInt(int);
@@ -35531,6 +35557,18 @@
package android.security.keystore {
+ public abstract class AttestationUtils {
+ method public static java.security.cert.X509Certificate[] attestDeviceIds(android.content.Context, int[], byte[]) throws android.security.keystore.DeviceIdAttestationException;
+ field public static final int ID_TYPE_IMEI = 2; // 0x2
+ field public static final int ID_TYPE_MEID = 3; // 0x3
+ field public static final int ID_TYPE_SERIAL = 1; // 0x1
+ }
+
+ public class DeviceIdAttestationException extends java.lang.Exception {
+ ctor public DeviceIdAttestationException(java.lang.String);
+ ctor public DeviceIdAttestationException(java.lang.String, java.lang.Throwable);
+ }
+
public class KeyExpiredException extends java.security.InvalidKeyException {
ctor public KeyExpiredException();
ctor public KeyExpiredException(java.lang.String);
@@ -36058,9 +36096,9 @@
field public static final int REASON_PACKAGE_SUSPENDED = 14; // 0xe
field public static final int REASON_PROFILE_TURNED_OFF = 15; // 0xf
field public static final int REASON_SNOOZED = 18; // 0x12
+ field public static final int REASON_TIMEOUT = 19; // 0x13
field public static final int REASON_UNAUTOBUNDLED = 16; // 0x10
field public static final int REASON_USER_STOPPED = 6; // 0x6
- field public static final int REASON_USER_SWITCH = 19; // 0x13
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService";
field public static final int SUPPRESSED_EFFECT_SCREEN_OFF = 1; // 0x1
field public static final int SUPPRESSED_EFFECT_SCREEN_ON = 2; // 0x2
@@ -62373,6 +62411,9 @@
method public static <E> java.util.Collection<E> checkedCollection(java.util.Collection<E>, java.lang.Class<E>);
method public static <E> java.util.List<E> checkedList(java.util.List<E>, java.lang.Class<E>);
method public static <K, V> java.util.Map<K, V> checkedMap(java.util.Map<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+ method public static <K, V> java.util.NavigableMap<K, V> checkedNavigableMap(java.util.NavigableMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+ method public static <E> java.util.NavigableSet<E> checkedNavigableSet(java.util.NavigableSet<E>, java.lang.Class<E>);
+ method public static <E> java.util.Queue<E> checkedQueue(java.util.Queue<E>, java.lang.Class<E>);
method public static <E> java.util.Set<E> checkedSet(java.util.Set<E>, java.lang.Class<E>);
method public static <K, V> java.util.SortedMap<K, V> checkedSortedMap(java.util.SortedMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
method public static <E> java.util.SortedSet<E> checkedSortedSet(java.util.SortedSet<E>, java.lang.Class<E>);
@@ -62383,7 +62424,11 @@
method public static final <T> java.util.List<T> emptyList();
method public static <T> java.util.ListIterator<T> emptyListIterator();
method public static final <K, V> java.util.Map<K, V> emptyMap();
+ method public static final <K, V> java.util.NavigableMap<K, V> emptyNavigableMap();
+ method public static <E> java.util.NavigableSet<E> emptyNavigableSet();
method public static final <T> java.util.Set<T> emptySet();
+ method public static final <K, V> java.util.SortedMap<K, V> emptySortedMap();
+ method public static <E> java.util.SortedSet<E> emptySortedSet();
method public static <T> java.util.Enumeration<T> enumeration(java.util.Collection<T>);
method public static <T> void fill(java.util.List<? super T>, T);
method public static int frequency(java.util.Collection<?>, java.lang.Object);
@@ -62412,12 +62457,16 @@
method public static <T> java.util.Collection<T> synchronizedCollection(java.util.Collection<T>);
method public static <T> java.util.List<T> synchronizedList(java.util.List<T>);
method public static <K, V> java.util.Map<K, V> synchronizedMap(java.util.Map<K, V>);
+ method public static <K, V> java.util.NavigableMap<K, V> synchronizedNavigableMap(java.util.NavigableMap<K, V>);
+ method public static <T> java.util.NavigableSet<T> synchronizedNavigableSet(java.util.NavigableSet<T>);
method public static <T> java.util.Set<T> synchronizedSet(java.util.Set<T>);
method public static <K, V> java.util.SortedMap<K, V> synchronizedSortedMap(java.util.SortedMap<K, V>);
method public static <T> java.util.SortedSet<T> synchronizedSortedSet(java.util.SortedSet<T>);
method public static <T> java.util.Collection<T> unmodifiableCollection(java.util.Collection<? extends T>);
method public static <T> java.util.List<T> unmodifiableList(java.util.List<? extends T>);
method public static <K, V> java.util.Map<K, V> unmodifiableMap(java.util.Map<? extends K, ? extends V>);
+ method public static <K, V> java.util.NavigableMap<K, V> unmodifiableNavigableMap(java.util.NavigableMap<K, ? extends V>);
+ method public static <T> java.util.NavigableSet<T> unmodifiableNavigableSet(java.util.NavigableSet<T>);
method public static <T> java.util.Set<T> unmodifiableSet(java.util.Set<? extends T>);
method public static <K, V> java.util.SortedMap<K, V> unmodifiableSortedMap(java.util.SortedMap<K, ? extends V>);
method public static <T> java.util.SortedSet<T> unmodifiableSortedSet(java.util.SortedSet<T>);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 82917d2..4172ed7 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1022,6 +1022,7 @@
private Icon mLargeIcon;
private String mChannelId;
+ private long mTimeout;
/**
* Structure to encapsulate a named action that can be shown as part of this notification.
@@ -1766,6 +1767,7 @@
if (parcel.readInt() != 0) {
mChannelId = parcel.readString();
}
+ mTimeout = parcel.readLong();
}
@Override
@@ -1872,6 +1874,7 @@
that.color = this.color;
that.mChannelId = this.mChannelId;
+ that.mTimeout = this.mTimeout;
if (!heavy) {
that.lightenPayload(); // will clean out extras
@@ -2128,6 +2131,7 @@
} else {
parcel.writeInt(0);
}
+ parcel.writeLong(mTimeout);
}
/**
@@ -2325,6 +2329,13 @@
}
/**
+ * Returns the time at which this notification should be canceled, if it's not canceled already.
+ */
+ public long getTimeout() {
+ return mTimeout;
+ }
+
+ /**
* The small icon representing this notification in the status bar and content view.
*
* @return the small icon representing this notification.
@@ -2532,6 +2543,15 @@
}
/**
+ * Specifies the time at which this notification should be canceled, if it is not already
+ * canceled.
+ */
+ public Builder setTimeout(long when) {
+ mN.mTimeout = when;
+ return this;
+ }
+
+ /**
* Add a timestamp pertaining to the notification (usually the time the event occurred).
*
* For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index aa56be6..f06d19c 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1132,6 +1132,23 @@
= "android.app.action.SHOW_DEVICE_MONITORING_DIALOG";
/**
+ * Broadcast Action: Sent after application delegation scopes are changed. The new list of
+ * delegation scopes will be sent in an extra identified by the {@link #EXTRA_DELEGATION_SCOPES}
+ * key.
+ *
+ * <p class=”note”> Note: This is a protected intent that can only be sent by the system.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED =
+ "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
+
+ /**
+ * A list of Strings corresponding to the delegation scopes given to an app in the
+ * {@link #ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED} broadcast.
+ */
+ public static final String EXTRA_DELEGATION_SCOPES = "android.app.extra.DELEGATION_SCOPES";
+
+ /**
* Flag used by {@link #addCrossProfileIntentFilter} to allow activities in
* the parent profile to access intents sent from the managed profile.
* That is, when an app in the managed profile calls
@@ -1194,6 +1211,19 @@
public static final int PERMISSION_GRANT_STATE_DENIED = 2;
/**
+ * Delegation of certificate installation and management. This scope grants access to the
+ * {@link #getInstalledCaCerts}, {@link #hasCaCertInstalled}, {@link #installCaCert},
+ * {@link #uninstallCaCert}, {@link #uninstallAllUserCaCerts} and {@link #installKeyPair} APIs.
+ */
+ public static final String DELEGATION_CERT_INSTALL = "delegation-cert-install";
+
+ /**
+ * Delegation of application restrictions management. This scope grants access to the
+ * {@link #setApplicationRestrictions} and {@link #getApplicationRestrictions} APIs.
+ */
+ public static final String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions";
+
+ /**
* No management for current user in-effect. This is the default.
* @hide
*/
@@ -3246,6 +3276,10 @@
/**
* Installs the given certificate as a user CA.
*
+ * The caller must be a profile or device owner on that user, or a delegate package given the
+ * {@link #DELEGATION_CERT_INSTALL} scope via {@link #setDelegatedScopes}; otherwise a
+ * security exception will be thrown.
+ *
* @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
* {@code null} if calling from a delegated certificate installer.
* @param certBuffer encoded form of the certificate to install.
@@ -3254,12 +3288,14 @@
* interrupted, true otherwise.
* @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
* owner.
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_CERT_INSTALL
*/
public boolean installCaCert(@Nullable ComponentName admin, byte[] certBuffer) {
throwIfParentInstance("installCaCert");
if (mService != null) {
try {
- return mService.installCaCert(admin, certBuffer);
+ return mService.installCaCert(admin, mContext.getPackageName(), certBuffer);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3270,18 +3306,24 @@
/**
* Uninstalls the given certificate from trusted user CAs, if present.
*
+ * The caller must be a profile or device owner on that user, or a delegate package given the
+ * {@link #DELEGATION_CERT_INSTALL} scope via {@link #setDelegatedScopes}; otherwise a
+ * security exception will be thrown.
+ *
* @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
* {@code null} if calling from a delegated certificate installer.
* @param certBuffer encoded form of the certificate to remove.
* @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
* owner.
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_CERT_INSTALL
*/
public void uninstallCaCert(@Nullable ComponentName admin, byte[] certBuffer) {
throwIfParentInstance("uninstallCaCert");
if (mService != null) {
try {
final String alias = getCaCertAlias(certBuffer);
- mService.uninstallCaCerts(admin, new String[] {alias});
+ mService.uninstallCaCerts(admin, mContext.getPackageName(), new String[] {alias});
} catch (CertificateException e) {
Log.w(TAG, "Unable to parse certificate", e);
} catch (RemoteException e) {
@@ -3306,7 +3348,7 @@
throwIfParentInstance("getInstalledCaCerts");
if (mService != null) {
try {
- mService.enforceCanManageCaCerts(admin);
+ mService.enforceCanManageCaCerts(admin, mContext.getPackageName());
final TrustedCertificateStore certStore = new TrustedCertificateStore();
for (String alias : certStore.userAliases()) {
try {
@@ -3335,8 +3377,8 @@
throwIfParentInstance("uninstallAllUserCaCerts");
if (mService != null) {
try {
- mService.uninstallCaCerts(admin, new TrustedCertificateStore().userAliases()
- .toArray(new String[0]));
+ mService.uninstallCaCerts(admin, mContext.getPackageName(),
+ new TrustedCertificateStore().userAliases() .toArray(new String[0]));
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -3356,7 +3398,7 @@
throwIfParentInstance("hasCaCertInstalled");
if (mService != null) {
try {
- mService.enforceCanManageCaCerts(admin);
+ mService.enforceCanManageCaCerts(admin, mContext.getPackageName());
return getCaCertAlias(certBuffer) != null;
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
@@ -3388,6 +3430,8 @@
* @return {@code true} if the keys were installed, {@code false} otherwise.
* @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
* owner.
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_CERT_INSTALL
*/
public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey,
@NonNull Certificate cert, @NonNull String alias) {
@@ -3419,6 +3463,8 @@
* @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
* owner.
* @see android.security.KeyChain#getCertificateChain
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_CERT_INSTALL
*/
public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey,
@NonNull Certificate[] certs, @NonNull String alias, boolean requestAccess) {
@@ -3431,8 +3477,8 @@
}
final byte[] pkcs8Key = KeyFactory.getInstance(privKey.getAlgorithm())
.getKeySpec(privKey, PKCS8EncodedKeySpec.class).getEncoded();
- return mService.installKeyPair(admin, pkcs8Key, pemCert, pemChain, alias,
- requestAccess);
+ return mService.installKeyPair(admin, mContext.getPackageName(), pkcs8Key, pemCert,
+ pemChain, alias, requestAccess);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
@@ -3453,11 +3499,13 @@
* @return {@code true} if the private key alias no longer exists, {@code false} otherwise.
* @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
* owner.
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_CERT_INSTALL
*/
public boolean removeKeyPair(@Nullable ComponentName admin, @NonNull String alias) {
throwIfParentInstance("removeKeyPair");
try {
- return mService.removeKeyPair(admin, alias);
+ return mService.removeKeyPair(admin, mContext.getPackageName(), alias);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3493,7 +3541,11 @@
* @param installerPackage The package name of the certificate installer which will be given
* access. If {@code null} is given the current package will be cleared.
* @throws SecurityException if {@code admin} is not a device or a profile owner.
+ *
+ * @deprecated From {@link android.os.Build.VERSION_CODES#O}. Use {@link #setDelegatedScopes}
+ * with the {@link #DELEGATION_CERT_INSTALL} scope instead.
*/
+ @Deprecated
public void setCertInstallerPackage(@NonNull ComponentName admin, @Nullable String
installerPackage) throws SecurityException {
throwIfParentInstance("setCertInstallerPackage");
@@ -3507,14 +3559,19 @@
}
/**
- * Called by a profile owner or device owner to retrieve the certificate installer for the user.
- * null if none is set.
+ * Called by a profile owner or device owner to retrieve the certificate installer for the user,
+ * or {@code null} if none is set. If there are multiple delegates this function will return one
+ * of them.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @return The package name of the current delegated certificate installer, or {@code null} if
* none is set.
* @throws SecurityException if {@code admin} is not a device or a profile owner.
+ *
+ * @deprecated From {@link android.os.Build.VERSION_CODES#O}. Use {@link #getDelegatePackages}
+ * with the {@link #DELEGATION_CERT_INSTALL} scope instead.
*/
+ @Deprecated
public @Nullable String getCertInstallerPackage(@NonNull ComponentName admin)
throws SecurityException {
throwIfParentInstance("getCertInstallerPackage");
@@ -3529,6 +3586,83 @@
}
/**
+ * Called by a profile owner or device owner to grant access to privileged APIs to another app.
+ * Granted APIs are determined by {@code scopes}, which is a list of the {@code DELEGATION_*}
+ * constants.
+ * <p>
+ * Delegated scopes are a per-user state. The delegated access is persistent until it is later
+ * cleared by calling this method with an empty {@code scopes} list or uninstalling the
+ * {@code delegatePackage}.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param delegatePackage The package name of the app which will be given access.
+ * @param scopes The groups of privileged APIs whose access should be granted to
+ * {@code delegatedPackage}.
+ * @throws SecurityException if {@code admin} is not a device or a profile owner.
+ */
+ public void setDelegatedScopes(@NonNull ComponentName admin, @NonNull String delegatePackage,
+ @NonNull List<String> scopes) {
+ throwIfParentInstance("setDelegatedScopes");
+ if (mService != null) {
+ try {
+ mService.setDelegatedScopes(admin, delegatePackage, scopes);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Called by a profile owner or device owner to retrieve a list of the scopes given to a
+ * delegate package. Other apps can use this method to retrieve their own delegated scopes by
+ * passing {@code null} for {@code admin} and their own package name as
+ * {@code delegatedPackage}.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+ * {@code null} if the caller is {@code delegatedPackage}.
+ * @param delegatedPackage The package name of the app whose scopes should be retrieved.
+ * @return A list containing the scopes given to {@code delegatedPackage}.
+ * @throws SecurityException if {@code admin} is not a device or a profile owner.
+ */
+ @NonNull
+ public List<String> getDelegatedScopes(@NonNull ComponentName admin,
+ @NonNull String delegatedPackage) {
+ throwIfParentInstance("getDelegatedScopes");
+ if (mService != null) {
+ try {
+ return mService.getDelegatedScopes(admin, delegatedPackage);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Called by a profile owner or device owner to retrieve a list of delegate packages that were
+ * granted a delegation scope.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param delegationScope The scope whose delegates should be retrieved.
+ * @return A list of package names of the current delegated packages for
+ {@code delegationScope}.
+ * @throws SecurityException if {@code admin} is not a device or a profile owner.
+ */
+ @Nullable
+ public List<String> getDelegatePackages(@NonNull ComponentName admin,
+ @NonNull String delegationScope) {
+ throwIfParentInstance("getDelegatePackages");
+ if (mService != null) {
+ try {
+ return mService.getDelegatePackages(admin, delegationScope);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
* Called by a device or profile owner to configure an always-on VPN connection through a
* specific application for the current user.
*
@@ -4686,7 +4820,11 @@
* APIs. If {@code null} is given the current package will be cleared.
* @throws SecurityException if {@code admin} is not a device or profile owner.
* @throws NameNotFoundException if {@code packageName} is not found
+ *
+ * @deprecated From {@link android.os.Build.VERSION_CODES#O}. Use {@link #setDelegatedScopes}
+ * with the {@link #DELEGATION_APP_RESTRICTIONS} scope instead.
*/
+ @Deprecated
public void setApplicationRestrictionsManagingPackage(@NonNull ComponentName admin,
@Nullable String packageName) throws NameNotFoundException {
throwIfParentInstance("setApplicationRestrictionsManagingPackage");
@@ -4703,14 +4841,20 @@
/**
* Called by a profile owner or device owner to retrieve the application restrictions managing
- * package for the current user, or {@code null} if none is set.
+ * package for the current user, or {@code null} if none is set. If there are multiple
+ * delegates this function will return one of them.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @return The package name allowed to manage application restrictions on the current user, or
* {@code null} if none is set.
* @throws SecurityException if {@code admin} is not a device or profile owner.
+ *
+ * @deprecated From {@link android.os.Build.VERSION_CODES#O}. Use {@link #getDelegatePackages}
+ * with the {@link #DELEGATION_APP_RESTRICTIONS} scope instead.
*/
- public @Nullable String getApplicationRestrictionsManagingPackage(
+ @Deprecated
+ @Nullable
+ public String getApplicationRestrictionsManagingPackage(
@NonNull ComponentName admin) {
throwIfParentInstance("getApplicationRestrictionsManagingPackage");
if (mService != null) {
@@ -4730,12 +4874,17 @@
*
* <p>This is done by comparing the calling Linux uid with the uid of the package specified by
* that method.
+ *
+ * @deprecated From {@link android.os.Build.VERSION_CODES#O}. Use {@link #getDelegatedScopes}
+ * instead.
*/
+ @Deprecated
public boolean isCallerApplicationRestrictionsManagingPackage() {
throwIfParentInstance("isCallerApplicationRestrictionsManagingPackage");
if (mService != null) {
try {
- return mService.isCallerApplicationRestrictionsManagingPackage();
+ return mService.isCallerApplicationRestrictionsManagingPackage(
+ mContext.getPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -4747,8 +4896,8 @@
* Sets the application restrictions for a given target application running in the calling user.
* <p>
* The caller must be a profile or device owner on that user, or the package allowed to manage
- * application restrictions via {@link #setApplicationRestrictionsManagingPackage}; otherwise a
- * security exception will be thrown.
+ * application restrictions via {@link #setDelegatedScopes} with the
+ * {@link #DELEGATION_APP_RESTRICTIONS} scope; otherwise a security exception will be thrown.
* <p>
* The provided {@link Bundle} consists of key-value pairs, where the types of values may be:
* <ul>
@@ -4775,7 +4924,8 @@
* @param settings A {@link Bundle} to be parsed by the receiving application, conveying a new
* set of active restrictions.
* @throws SecurityException if {@code admin} is not a device or profile owner.
- * @see #setApplicationRestrictionsManagingPackage
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_APP_RESTRICTIONS
* @see UserManager#KEY_RESTRICTIONS_PENDING
*/
@WorkerThread
@@ -4784,7 +4934,8 @@
throwIfParentInstance("setApplicationRestrictions");
if (mService != null) {
try {
- mService.setApplicationRestrictions(admin, packageName, settings);
+ mService.setApplicationRestrictions(admin, mContext.getPackageName(), packageName,
+ settings);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -5523,8 +5674,8 @@
* user.
* <p>
* The caller must be a profile or device owner on that user, or the package allowed to manage
- * application restrictions via {@link #setApplicationRestrictionsManagingPackage}; otherwise a
- * security exception will be thrown.
+ * application restrictions via {@link #setDelegatedScopes} with the
+ * {@link #DELEGATION_APP_RESTRICTIONS} scope; otherwise a security exception will be thrown.
*
* <p>NOTE: The method performs disk I/O and shouldn't be called on the main thread
*
@@ -5535,7 +5686,8 @@
* {@link DevicePolicyManager#setApplicationRestrictions} was called, or an empty
* {@link Bundle} if no restrictions have been set.
* @throws SecurityException if {@code admin} is not a device or profile owner.
- * @see #setApplicationRestrictionsManagingPackage
+ * @see #setDelegatedScopes
+ * @see #DELEGATION_APP_RESTRICTIONS
*/
@WorkerThread
public @NonNull Bundle getApplicationRestrictions(
@@ -5543,7 +5695,8 @@
throwIfParentInstance("getApplicationRestrictions");
if (mService != null) {
try {
- return mService.getApplicationRestrictions(admin, packageName);
+ return mService.getApplicationRestrictions(admin, mContext.getPackageName(),
+ packageName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 80ef557..983de56 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -153,17 +153,21 @@
String[] setPackagesSuspended(in ComponentName admin, in String[] packageNames, boolean suspended);
boolean isPackageSuspended(in ComponentName admin, String packageName);
- boolean installCaCert(in ComponentName admin, in byte[] certBuffer);
- void uninstallCaCerts(in ComponentName admin, in String[] aliases);
- void enforceCanManageCaCerts(in ComponentName admin);
+ boolean installCaCert(in ComponentName admin, String callerPackage, in byte[] certBuffer);
+ void uninstallCaCerts(in ComponentName admin, String callerPackage, in String[] aliases);
+ void enforceCanManageCaCerts(in ComponentName admin, in String callerPackage);
boolean approveCaCert(in String alias, int userHandle, boolean approval);
boolean isCaCertApproved(in String alias, int userHandle);
- boolean installKeyPair(in ComponentName who, in byte[] privKeyBuffer, in byte[] certBuffer,
- in byte[] certChainBuffer, String alias, boolean requestAccess);
- boolean removeKeyPair(in ComponentName who, String alias);
+ boolean installKeyPair(in ComponentName who, in String callerPackage, in byte[] privKeyBuffer,
+ in byte[] certBuffer, in byte[] certChainBuffer, String alias, boolean requestAccess);
+ boolean removeKeyPair(in ComponentName who, in String callerPackage, String alias);
void choosePrivateKeyAlias(int uid, in Uri uri, in String alias, IBinder aliasCallback);
+ void setDelegatedScopes(in ComponentName who, in String delegatePackage, in List<String> scopes);
+ List<String> getDelegatedScopes(in ComponentName who, String delegatePackage);
+ List<String> getDelegatePackages(in ComponentName who, String scope);
+
void setCertInstallerPackage(in ComponentName who, String installerPackage);
String getCertInstallerPackage(in ComponentName who);
@@ -173,11 +177,11 @@
void addPersistentPreferredActivity(in ComponentName admin, in IntentFilter filter, in ComponentName activity);
void clearPackagePersistentPreferredActivities(in ComponentName admin, String packageName);
- void setApplicationRestrictions(in ComponentName who, in String packageName, in Bundle settings);
- Bundle getApplicationRestrictions(in ComponentName who, in String packageName);
+ void setApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName, in Bundle settings);
+ Bundle getApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName);
boolean setApplicationRestrictionsManagingPackage(in ComponentName admin, in String packageName);
String getApplicationRestrictionsManagingPackage(in ComponentName admin);
- boolean isCallerApplicationRestrictionsManagingPackage();
+ boolean isCallerApplicationRestrictionsManagingPackage(in String callerPackage);
void setRestrictionsProvider(in ComponentName who, in ComponentName provider);
ComponentName getRestrictionsProvider(int userHandle);
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/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 1c2588a..12f5396 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -43,6 +43,7 @@
void setUserEnabled(int userHandle);
void evictCredentialEncryptionKey(int userHandle);
boolean removeUser(int userHandle);
+ boolean removeUserEvenWhenDisallowed(int userHandle);
void setUserName(int userHandle, String name);
void setUserIcon(int userHandle, in Bitmap icon);
ParcelFileDescriptor getUserIcon(int userHandle);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index efacb20..c5c380c 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2082,6 +2082,22 @@
}
/**
+ * Similar to {@link #removeUser(int)} except bypassing the checking of
+ * {@link UserManager#DISALLOW_REMOVE_USER}
+ * or {@link UserManager#DISALLOW_REMOVE_MANAGED_PROFILE}.
+ *
+ * @see {@link #removeUser(int)}
+ * @hide
+ */
+ public boolean removeUserEvenWhenDisallowed(@UserIdInt int userHandle) {
+ try {
+ return mService.removeUserEvenWhenDisallowed(userHandle);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Updates the user's name.
* Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
*
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 8303bca..089eaec 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -145,6 +145,8 @@
private List<Preference> mDependents;
+ private PreferenceGroup mParentGroup;
+
private boolean mBaseMethodCalled;
/**
@@ -1238,6 +1240,16 @@
registerDependency();
}
+ /**
+ * Assigns a {@link PreferenceGroup} as the parent of this Preference. Set null to remove
+ * the current parent.
+ *
+ * @param parentGroup Parent preference group of this Preference or null if none.
+ */
+ void assignParent(@Nullable PreferenceGroup parentGroup) {
+ mParentGroup = parentGroup;
+ }
+
private void registerDependency() {
if (TextUtils.isEmpty(mDependencyKey)) return;
@@ -1401,6 +1413,17 @@
}
/**
+ * Returns the {@link PreferenceGroup} which is this Preference assigned to or null if this
+ * preference is not assigned to any group or is a root Preference.
+ *
+ * @return The parent PreferenceGroup or null if not attached to any.
+ */
+ @Nullable
+ public PreferenceGroup getParent() {
+ return mParentGroup;
+ }
+
+ /**
* Called when this Preference is being removed from the hierarchy. You
* should remove any references to this Preference that you know about. Make
* sure to call through to the superclass implementation.
diff --git a/core/java/android/preference/PreferenceGroup.java b/core/java/android/preference/PreferenceGroup.java
index f17506b..f135b26 100644
--- a/core/java/android/preference/PreferenceGroup.java
+++ b/core/java/android/preference/PreferenceGroup.java
@@ -29,14 +29,14 @@
* A container for multiple
* {@link Preference} objects. It is a base class for Preference objects that are
* parents, such as {@link PreferenceCategory} and {@link PreferenceScreen}.
- *
+ *
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For information about building a settings UI with Preferences,
* read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>
* guide.</p>
* </div>
- *
+ *
* @attr ref android.R.styleable#PreferenceGroup_orderingFromXml
*/
public abstract class PreferenceGroup extends Preference implements GenericInflater.Parent<Preference> {
@@ -80,7 +80,7 @@
* <p>
* If this is called after preferences are added, they will not be
* re-ordered in the order they were added, hence call this method early on.
- *
+ *
* @param orderingAsAdded Whether to order according to the order added.
* @see Preference#setOrder(int)
*/
@@ -90,7 +90,7 @@
/**
* Whether this group is ordering preferences in the order they are added.
- *
+ *
* @return Whether this group orders based on the order the children are added.
* @see #setOrderingAsAdded(boolean)
*/
@@ -115,7 +115,7 @@
/**
* Returns the {@link Preference} at a particular index.
- *
+ *
* @param index The index of the {@link Preference} to retrieve.
* @return The {@link Preference}.
*/
@@ -126,7 +126,7 @@
/**
* Adds a {@link Preference} at the correct position based on the
* preference's order.
- *
+ *
* @param preference The preference to add.
* @return Whether the preference is now in this group.
*/
@@ -135,7 +135,7 @@
// Exists
return true;
}
-
+
if (preference.getOrder() == Preference.DEFAULT_ORDER) {
if (mOrderingAsAdded) {
preference.setOrder(mCurrentPreferenceOrder++);
@@ -161,11 +161,12 @@
}
preference.onAttachedToHierarchy(getPreferenceManager());
-
+ preference.assignParent(this);
+
if (mAttachedToActivity) {
preference.onAttachedToActivity();
}
-
+
notifyHierarchyChanged();
return true;
@@ -173,7 +174,7 @@
/**
* Removes a {@link Preference} from this group.
- *
+ *
* @param preference The preference to remove.
* @return Whether the preference was found and removed.
*/
@@ -186,10 +187,13 @@
private boolean removePreferenceInt(Preference preference) {
synchronized(this) {
preference.onPrepareForRemoval();
+ if (preference.getParent() == this) {
+ preference.assignParent(null);
+ }
return mPreferenceList.remove(preference);
}
}
-
+
/**
* Removes all {@link Preference Preferences} from this group.
*/
@@ -202,10 +206,10 @@
}
notifyHierarchyChanged();
}
-
+
/**
* Prepares a {@link Preference} to be added to the group.
- *
+ *
* @param preference The preference to add.
* @return Whether to allow adding the preference (true), or not (false).
*/
@@ -223,7 +227,7 @@
* <p>
* This will recursively search for the preference into children that are
* also {@link PreferenceGroup PreferenceGroups}.
- *
+ *
* @param key The key of the preference to retrieve.
* @return The {@link Preference} with the key, or null.
*/
@@ -239,7 +243,7 @@
if (curKey != null && curKey.equals(key)) {
return preference;
}
-
+
if (preference instanceof PreferenceGroup) {
final Preference returnedPreference = ((PreferenceGroup)preference)
.findPreference(key);
@@ -255,14 +259,14 @@
/**
* Whether this preference group should be shown on the same screen as its
* contained preferences.
- *
+ *
* @return True if the contained preferences should be shown on the same
* screen as this preference.
*/
protected boolean isOnSameScreenAsChildren() {
return true;
}
-
+
@Override
protected void onAttachedToActivity() {
super.onAttachedToActivity();
@@ -270,7 +274,7 @@
// Mark as attached so if a preference is later added to this group, we
// can tell it we are already attached
mAttachedToActivity = true;
-
+
// Dispatch to all contained preferences
final int preferenceCount = getPreferenceCount();
for (int i = 0; i < preferenceCount; i++) {
@@ -281,7 +285,7 @@
@Override
protected void onPrepareForRemoval() {
super.onPrepareForRemoval();
-
+
// We won't be attached to the activity anymore
mAttachedToActivity = false;
}
@@ -297,7 +301,7 @@
getPreference(i).onParentChanged(this, disableDependents);
}
}
-
+
void sortPreferences() {
synchronized (this) {
Collections.sort(mPreferenceList);
@@ -314,7 +318,7 @@
getPreference(i).dispatchSaveInstanceState(container);
}
}
-
+
@Override
protected void dispatchRestoreInstanceState(Bundle container) {
super.dispatchRestoreInstanceState(container);
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index 22f1bed..5461e6b 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -83,6 +83,12 @@
public static final int KM_TAG_ROOT_OF_TRUST = KM_BYTES | 704;
public static final int KM_TAG_UNIQUE_ID = KM_BYTES | 707;
public static final int KM_TAG_ATTESTATION_CHALLENGE = KM_BYTES | 708;
+ public static final int KM_TAG_ATTESTATION_ID_BRAND = KM_BYTES | 710;
+ public static final int KM_TAG_ATTESTATION_ID_DEVICE = KM_BYTES | 711;
+ public static final int KM_TAG_ATTESTATION_ID_PRODUCT = KM_BYTES | 712;
+ public static final int KM_TAG_ATTESTATION_ID_SERIAL = KM_BYTES | 713;
+ public static final int KM_TAG_ATTESTATION_ID_IMEI = KM_BYTES | 714;
+ public static final int KM_TAG_ATTESTATION_ID_MEID = KM_BYTES | 715;
public static final int KM_TAG_ASSOCIATED_DATA = KM_BYTES | 1000;
public static final int KM_TAG_NONCE = KM_BYTES | 1001;
@@ -204,6 +210,7 @@
public static final int KM_ERROR_INVALID_MAC_LENGTH = -57;
public static final int KM_ERROR_MISSING_MIN_MAC_LENGTH = -58;
public static final int KM_ERROR_UNSUPPORTED_MIN_MAC_LENGTH = -59;
+ public static final int KM_ERROR_CANNOT_ATTEST_IDS = -66;
public static final int KM_ERROR_UNIMPLEMENTED = -100;
public static final int KM_ERROR_VERSION_MISMATCH = -101;
public static final int KM_ERROR_UNKNOWN_ERROR = -1000;
@@ -249,6 +256,7 @@
"Caller-provided IV not permitted");
sErrorCodeToString.put(KM_ERROR_INVALID_MAC_LENGTH,
"Invalid MAC or authentication tag length");
+ sErrorCodeToString.put(KM_ERROR_CANNOT_ATTEST_IDS, "Unable to attest device ids");
sErrorCodeToString.put(KM_ERROR_UNIMPLEMENTED, "Not implemented");
sErrorCodeToString.put(KM_ERROR_UNKNOWN_ERROR, "Unknown error");
}
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 517b305..417be60 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -183,8 +183,8 @@
public static final int REASON_CHANNEL_BANNED = 17;
/** Notification was snoozed. */
public static final int REASON_SNOOZED = 18;
- /** Notification no longer visible because of user switch */
- public static final int REASON_USER_SWITCH = 19;
+ /** Notification was canceled due to timeout */
+ public static final int REASON_TIMEOUT = 19;
/**
* The full trim of the StatusBarNotification including all its features.
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index ac8f413..0c863fd 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -39,27 +39,60 @@
namespace android {
-static jlong FontFamily_create(JNIEnv* env, jobject clazz, jstring lang, jint variant) {
- if (lang == NULL) {
- return (jlong)new minikin::FontFamily(variant);
+struct NativeFamilyBuilder {
+ uint32_t langId;
+ int variant;
+ std::vector<minikin::Font> fonts;
+};
+
+static jlong FontFamily_initBuilder(JNIEnv* env, jobject clazz, jstring lang, jint variant) {
+ NativeFamilyBuilder* builder = new NativeFamilyBuilder();
+ if (lang != nullptr) {
+ ScopedUtfChars str(env, lang);
+ builder->langId = minikin::FontStyle::registerLanguageList(str.c_str());
+ } else {
+ builder->langId = minikin::FontStyle::registerLanguageList("");
}
- ScopedUtfChars str(env, lang);
- uint32_t langId = minikin::FontStyle::registerLanguageList(str.c_str());
- return (jlong)new minikin::FontFamily(langId, variant);
+ builder->variant = variant;
+ return reinterpret_cast<jlong>(builder);
}
-static void FontFamily_unref(JNIEnv* env, jobject clazz, jlong familyPtr) {
+static jlong FontFamily_create(jlong builderPtr) {
+ if (builderPtr == 0) {
+ return 0;
+ }
+ NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
+ minikin::FontFamily* family = new minikin::FontFamily(
+ builder->langId, builder->variant, std::move(builder->fonts));
+ delete builder;
+ return reinterpret_cast<jlong>(family);
+}
+
+static void FontFamily_abort(jlong builderPtr) {
+ NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
+ minikin::Font::clearElementsWithLock(&builder->fonts);
+ delete builder;
+}
+
+static void FontFamily_unref(jlong familyPtr) {
minikin::FontFamily* fontFamily = reinterpret_cast<minikin::FontFamily*>(familyPtr);
fontFamily->Unref();
}
-static jboolean addSkTypeface(minikin::FontFamily* family, sk_sp<SkTypeface> face,
- const void* fontData, size_t fontSize, int ttcIndex) {
+static void addSkTypeface(jlong builderPtr, sk_sp<SkTypeface> face, const void* fontData,
+ size_t fontSize, int ttcIndex) {
minikin::MinikinFont* minikinFont =
new MinikinFontSkia(std::move(face), fontData, fontSize, ttcIndex);
- bool result = family->addFont(minikinFont);
+ NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
+ int weight;
+ bool italic;
+ if (!minikin::FontFamily::analyzeStyle(minikinFont, &weight, &italic)) {
+ ALOGE("analyzeStyle failed. Using default style");
+ weight = 400;
+ italic = false;
+ }
+ builder->fonts.push_back(minikin::Font(minikinFont, minikin::FontStyle(weight / 100, italic)));
minikinFont->Unref();
- return result;
}
static void release_global_ref(const void* /*data*/, void* context) {
@@ -85,7 +118,7 @@
}
}
-static jboolean FontFamily_addFont(JNIEnv* env, jobject clazz, jlong familyPtr, jobject bytebuf,
+static jboolean FontFamily_addFont(JNIEnv* env, jobject clazz, jlong builderPtr, jobject bytebuf,
jint ttcIndex) {
NPE_CHECK_RETURN_ZERO(env, bytebuf);
const void* fontPtr = env->GetDirectBufferAddress(bytebuf);
@@ -112,8 +145,8 @@
ALOGE("addFont failed to create font");
return false;
}
- minikin::FontFamily* fontFamily = reinterpret_cast<minikin::FontFamily*>(familyPtr);
- return addSkTypeface(fontFamily, std::move(face), fontPtr, (size_t)fontSize, ttcIndex);
+ addSkTypeface(builderPtr, std::move(face), fontPtr, (size_t)fontSize, ttcIndex);
+ return true;
}
static struct {
@@ -126,7 +159,7 @@
jfieldID mStyleValue;
} gAxisClassInfo;
-static jboolean FontFamily_addFontWeightStyle(JNIEnv* env, jobject clazz, jlong familyPtr,
+static jboolean FontFamily_addFontWeightStyle(JNIEnv* env, jobject clazz, jlong builderPtr,
jobject font, jint ttcIndex, jobject listOfAxis, jint weight, jboolean isItalic) {
NPE_CHECK_RETURN_ZERO(env, font);
@@ -178,10 +211,11 @@
ALOGE("addFont failed to create font, invalid request");
return false;
}
- minikin::FontFamily* fontFamily = reinterpret_cast<minikin::FontFamily*>(familyPtr);
minikin::MinikinFont* minikinFont =
- new MinikinFontSkia(std::move(face), fontPtr, (size_t)fontSize, ttcIndex);
- fontFamily->addFont(minikinFont, minikin::FontStyle(weight / 100, isItalic));
+ new MinikinFontSkia(std::move(face), fontPtr, fontSize, ttcIndex);
+ NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
+ builder->fonts.push_back(minikin::Font(minikinFont,
+ minikin::FontStyle(weight / 100, isItalic)));
minikinFont->Unref();
return true;
}
@@ -190,7 +224,7 @@
delete static_cast<Asset*>(context);
}
-static jboolean FontFamily_addFontFromAssetManager(JNIEnv* env, jobject, jlong familyPtr,
+static jboolean FontFamily_addFontFromAssetManager(JNIEnv* env, jobject, jlong builderPtr,
jobject jassetMgr, jstring jpath, jint cookie, jboolean isAsset) {
NPE_CHECK_RETURN_ZERO(env, jassetMgr);
NPE_CHECK_RETURN_ZERO(env, jpath);
@@ -233,14 +267,17 @@
ALOGE("addFontFromAsset failed to create font %s", str.c_str());
return false;
}
- minikin::FontFamily* fontFamily = reinterpret_cast<minikin::FontFamily*>(familyPtr);
- return addSkTypeface(fontFamily, std::move(face), buf, bufSize, /* ttcIndex */ 0);
+
+ addSkTypeface(builderPtr, std::move(face), buf, bufSize, 0 /* ttc index */);
+ return true;
}
///////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gFontFamilyMethods[] = {
- { "nCreateFamily", "(Ljava/lang/String;I)J", (void*)FontFamily_create },
+ { "nInitBuilder", "(Ljava/lang/String;I)J", (void*)FontFamily_initBuilder },
+ { "nCreateFamily", "(J)J", (void*)FontFamily_create },
+ { "nAbort", "(J)V", (void*)FontFamily_abort },
{ "nUnrefFamily", "(J)V", (void*)FontFamily_unref },
{ "nAddFont", "(JLjava/nio/ByteBuffer;I)Z", (void*)FontFamily_addFont },
{ "nAddFontWeightStyle", "(JLjava/nio/ByteBuffer;ILjava/util/List;IZ)Z",
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/AndroidManifest.xml b/core/res/AndroidManifest.xml
index bfbea5b..094e2b8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -443,6 +443,7 @@
<protected-broadcast android:name="com.android.server.telecom.intent.action.CALLS_ADD_ENTRY" />
<protected-broadcast android:name="com.android.settings.location.MODE_CHANGING" />
+ <protected-broadcast android:name="NotificationManagerService.TIMEOUT" />
<protected-broadcast android:name="ScheduleConditionProvider.EVALUATE" />
<protected-broadcast android:name="EventConditionProvider.EVALUATE" />
<protected-broadcast android:name="SnoozeHelper.EVALUATE" />
@@ -514,6 +515,7 @@
<!-- Added in O -->
<!-- TODO: temporary broadcast used by AutoFillManagerServiceImpl; will be removed -->
<protected-broadcast android:name="com.android.internal.autofill.action.REQUEST_AUTOFILL" />
+ <protected-broadcast android:name="android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml
index 516f252..182ba24 100644
--- a/core/res/res/drawable-nodpi/platlogo.xml
+++ b/core/res/res/drawable-nodpi/platlogo.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2016 The Android Open Source Project
+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.
@@ -14,24 +14,27 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="512dp"
- android:height="512dp"
+ android:width="480dp"
+ android:height="480dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
- android:fillColor="#FFc7d4b6"
- android:pathData="M32.0,12.5l0.0,28.0l12.0,-5.0l0.0,-28.0z"/>
+ android:pathData="M25.0,25.0m-20.5,0.0a20.5,20.5,0,1,1,41.0,0.0a20.5,20.5,0,1,1,-41.0,0.0"
+ android:fillAlpha="0.066"
+ android:fillColor="#000000"/>
<path
- android:fillColor="#FFfbd3cb"
- android:pathData="M4.0,40.5l12.0,-5.0l0.0,-11.0l-12.0,-12.0z"/>
+ android:pathData="M24.0,24.0m-20.0,0.0a20.0,20.0,0,1,1,40.0,0.0a20.0,20.0,0,1,1,-40.0,0.0"
+ android:fillColor="#FFC107"/>
<path
- android:fillColor="#40000000"
- android:pathData="M44.0,35.5l-12.0,-12.0l0.0,-4.0z"/>
+ android:pathData="M44,24.2010101 L33.9004889,14.101499 L14.101499,33.9004889 L24.2010101,44 C29.2525804,43.9497929 34.2887564,41.9975027 38.1431296,38.1431296 C41.9975027,34.2887564 43.9497929,29.2525804 44,24.2010101 Z"
+ android:fillColor="#FE9F00"/>
<path
- android:fillColor="#40000000"
- android:pathData="M4.0,12.5l12.0,12.0l0.0,4.0z"/>
+ android:pathData="M24.0,24.0m-14.0,0.0a14.0,14.0,0,1,1,28.0,0.0a14.0,14.0,0,1,1,-28.0,0.0"
+ android:fillColor="#FED44F"/>
<path
- android:fillColor="#FFe0e0d6"
- android:pathData="M32.0,23.5l-16.0,-16.0l-12.0,5.0l0.0,0.0l12.0,12.0l16.0,16.0l12.0,-5.0l0.0,0.0z"/>
+ android:pathData="M37.7829445,26.469236 L29.6578482,18.3441397 L18.3441397,29.6578482 L26.469236,37.7829445 C29.1911841,37.2979273 31.7972024,36.0037754 33.9004889,33.9004889 C36.0037754,31.7972024 37.2979273,29.1911841 37.7829445,26.469236 Z"
+ android:fillColor="#FFC107"/>
+ <path
+ android:pathData="M24.0,24.0m-8.0,0.0a8.0,8.0,0,1,1,16.0,0.0a8.0,8.0,0,1,1,-16.0,0.0"
+ android:fillColor="#FFFFFF"/>
</vector>
-
diff --git a/core/res/res/drawable-nodpi/stat_sys_adb.xml b/core/res/res/drawable-nodpi/stat_sys_adb.xml
index 5043cba..89e42e6 100644
--- a/core/res/res/drawable-nodpi/stat_sys_adb.xml
+++ b/core/res/res/drawable-nodpi/stat_sys_adb.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2016 The Android Open Source Project
+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.
@@ -16,21 +16,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0">
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
<path
- android:fillColor="#A0FFFFFF"
- android:pathData="M32.0,12.5l0.0,28.0l12.0,-5.0l0.0,-28.0z"/>
+ android:fillColor="#FF000000"
+ android:pathData="M12.0,12.0m-10.0,0.0a10.0,10.0,0,1,1,20.0,0.0a10.0,10.0,0,1,1,-20.0,0.0"
+ android:fillAlpha="0.25"/>
<path
- android:fillColor="#A0FFFFFF"
- android:pathData="M4.0,40.5l12.0,-5.0l0.0,-11.0l-12.0,-12.0z"/>
+ android:fillColor="#FF000000"
+ android:pathData="M12,22 C6.4771525,22 2,17.5228475 2,12 C2,6.4771525 6.4771525,2 12,2 C17.5228475,2 22,6.4771525 22,12 C22,17.5228475 17.5228475,22 12,22 Z M12,18.5 C15.5898509,18.5 18.5,15.5898509 18.5,12 C18.5,8.41014913 15.5898509,5.5 12,5.5 C8.41014913,5.5 5.5,8.41014913 5.5,12 C5.5,15.5898509 8.41014913,18.5 12,18.5 Z"/>
<path
- android:fillColor="#40000000"
- android:pathData="M44.0,35.5l-12.0,-12.0l0.0,-4.0z"/>
- <path
- android:fillColor="#40000000"
- android:pathData="M4.0,12.5l12.0,12.0l0.0,4.0z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M32.0,23.5l-16.0,-16.0l-12.0,5.0l0.0,0.0l12.0,12.0l16.0,16.0l12.0,-5.0l0.0,0.0z"/>
+ android:fillColor="#FF000000"
+ android:pathData="M12,18.5 C8.41014913,18.5 5.5,15.5898509 5.5,12 C5.5,8.41014913 8.41014913,5.5 12,5.5 C15.5898509,5.5 18.5,8.41014913 18.5,12 C18.5,15.5898509 15.5898509,18.5 12,18.5 Z M12,15 C13.6568542,15 15,13.6568542 15,12 C15,10.3431458 13.6568542,9 12,9 C10.3431458,9 9,10.3431458 9,12 C9,13.6568542 10.3431458,15 12,15 Z"
+ android:fillAlpha="0.25"/>
</vector>
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/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index 8673e0b..2c93c32 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -19,6 +19,7 @@
import android.content.res.AssetManager;
import android.text.FontConfig;
import android.util.Log;
+import dalvik.annotation.optimization.CriticalNative;
import java.io.FileInputStream;
import java.io.IOException;
@@ -40,11 +41,11 @@
*/
public long mNativePtr;
+ // Points native font family builder. Must be zero after freezing this family.
+ private long mBuilderPtr;
+
public FontFamily() {
- mNativePtr = nCreateFamily(null, 0);
- if (mNativePtr == 0) {
- throw new IllegalStateException("error creating native FontFamily");
- }
+ mBuilderPtr = nInitBuilder(null, 0);
}
public FontFamily(String lang, String variant) {
@@ -54,27 +55,48 @@
} else if ("elegant".equals(variant)) {
varEnum = 2;
}
- mNativePtr = nCreateFamily(lang, varEnum);
- if (mNativePtr == 0) {
- throw new IllegalStateException("error creating native FontFamily");
+ mBuilderPtr = nInitBuilder(lang, varEnum);
+ }
+
+ public void freeze() {
+ if (mBuilderPtr == 0) {
+ throw new IllegalStateException("This FontFamily is already frozen");
}
+ mNativePtr = nCreateFamily(mBuilderPtr);
+ mBuilderPtr = 0;
+ }
+
+ public void abortCreation() {
+ if (mBuilderPtr == 0) {
+ throw new IllegalStateException("This FontFamily is already frozen or abandoned");
+ }
+ nAbort(mBuilderPtr);
+ mBuilderPtr = 0;
}
@Override
protected void finalize() throws Throwable {
try {
- nUnrefFamily(mNativePtr);
+ if (mNativePtr != 0) {
+ nUnrefFamily(mNativePtr);
+ }
+ if (mBuilderPtr != 0) {
+ nAbort(mBuilderPtr);
+ }
} finally {
super.finalize();
}
}
public boolean addFont(String path, int ttcIndex) {
+ if (mBuilderPtr == 0) {
+ throw new IllegalStateException("Unable to call addFont after freezing.");
+ }
try (FileInputStream file = new FileInputStream(path)) {
FileChannel fileChannel = file.getChannel();
long fontSize = fileChannel.size();
ByteBuffer fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
- return nAddFont(mNativePtr, fontBuffer, ttcIndex);
+ return nAddFont(mBuilderPtr, fontBuffer, ttcIndex);
} catch (IOException e) {
Log.e(TAG, "Error mapping font file " + path);
return false;
@@ -83,20 +105,34 @@
public boolean addFontWeightStyle(ByteBuffer font, int ttcIndex, List<FontConfig.Axis> axes,
int weight, boolean style) {
- return nAddFontWeightStyle(mNativePtr, font, ttcIndex, axes, weight, style);
+ if (mBuilderPtr == 0) {
+ throw new IllegalStateException("Unable to call addFontWeightStyle after freezing.");
+ }
+ return nAddFontWeightStyle(mBuilderPtr, font, ttcIndex, axes, weight, style);
}
public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie,
boolean isAsset) {
- return nAddFontFromAssetManager(mNativePtr, mgr, path, cookie, isAsset);
+ if (mBuilderPtr == 0) {
+ throw new IllegalStateException("Unable to call addFontFromAsset after freezing.");
+ }
+ return nAddFontFromAssetManager(mBuilderPtr, mgr, path, cookie, isAsset);
}
- private static native long nCreateFamily(String lang, int variant);
+ private static native long nInitBuilder(String lang, int variant);
+
+ @CriticalNative
+ private static native long nCreateFamily(long mBuilderPtr);
+
+ @CriticalNative
+ private static native void nAbort(long mBuilderPtr);
+
+ @CriticalNative
private static native void nUnrefFamily(long nativePtr);
- private static native boolean nAddFont(long nativeFamily, ByteBuffer font, int ttcIndex);
- private static native boolean nAddFontWeightStyle(long nativeFamily, ByteBuffer font,
+ private static native boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex);
+ private static native boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font,
int ttcIndex, List<FontConfig.Axis> listOfAxis,
int weight, boolean isItalic);
- private static native boolean nAddFontFromAssetManager(long nativeFamily, AssetManager mgr,
+ private static native boolean nAddFontFromAssetManager(long builderPtr, AssetManager mgr,
String path, int cookie, boolean isAsset);
}
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 4e863e3..38e8061 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -237,6 +237,7 @@
IoUtils.closeQuietly(fd);
}
}
+ fontFamily.freeze();
callback.onTypefaceRetrieved(Typeface.createFromFamiliesWithDefault(
new FontFamily[] {fontFamily}));
}
@@ -373,10 +374,13 @@
FontFamily fontFamily = new FontFamily();
if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */)) {
+ fontFamily.freeze();
FontFamily[] families = { fontFamily };
typeface = createFromFamiliesWithDefault(families);
sDynamicTypefaceCache.put(key, typeface);
return typeface;
+ } else {
+ fontFamily.abortCreation();
}
}
}
@@ -422,8 +426,11 @@
if (sFallbackFonts != null) {
FontFamily fontFamily = new FontFamily();
if (fontFamily.addFont(path, 0 /* ttcIndex */)) {
+ fontFamily.freeze();
FontFamily[] families = { fontFamily };
return createFromFamiliesWithDefault(families);
+ } else {
+ fontFamily.abortCreation();
}
}
throw new RuntimeException("Font not found " + path);
@@ -492,6 +499,7 @@
Log.e(TAG, "Error creating font " + font.getFontName() + "#" + font.getTtcIndex());
}
}
+ fontFamily.freeze();
return fontFamily;
}
diff --git a/keystore/java/android/security/keystore/AttestationUtils.java b/keystore/java/android/security/keystore/AttestationUtils.java
new file mode 100644
index 0000000..e36632a
--- /dev/null
+++ b/keystore/java/android/security/keystore/AttestationUtils.java
@@ -0,0 +1,228 @@
+/*
+ * 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.security.keystore;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.os.Build;
+import android.os.Process;
+import android.security.KeyStore;
+import android.security.KeyStoreException;
+import android.security.keymaster.KeyCharacteristics;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterCertificateChain;
+import android.security.keymaster.KeymasterDefs;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.RSAKeyGenParameterSpec;
+import java.util.Collection;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Utilities for attesting the device's hardware identifiers.
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+public abstract class AttestationUtils {
+ private static AtomicInteger sSequenceNumber = new AtomicInteger(0);
+
+ private AttestationUtils() {
+ }
+
+ /**
+ * Specifies that the device should attest its serial number. For use with
+ * {@link #attestDeviceIds}.
+ *
+ * @see #attestDeviceIds
+ */
+ public static final int ID_TYPE_SERIAL = 1;
+
+ /**
+ * Specifies that the device should attest its IMEIs. For use with {@link #attestDeviceIds}.
+ *
+ * @see #attestDeviceIds
+ */
+ public static final int ID_TYPE_IMEI = 2;
+
+ /**
+ * Specifies that the device should attest its MEIDs. For use with {@link #attestDeviceIds}.
+ *
+ * @see #attestDeviceIds
+ */
+ public static final int ID_TYPE_MEID = 3;
+
+ /**
+ * Performs attestation of the device's identifiers. This method returns a certificate chain
+ * whose first element contains the requested device identifiers in an extension. The device's
+ * brand, device and product are always also included in the attestation. If the device supports
+ * attestation in secure hardware, the chain will be rooted at a trustworthy CA key. Otherwise,
+ * the chain will be rooted at an untrusted certificate. See
+ * <a href="https://developer.android.com/training/articles/security-key-attestation.html">
+ * Key Attestation</a> for the format of the certificate extension.
+ * <p>
+ * Attestation will only be successful when all of the following are true:
+ * 1) The device has been set up to support device identifier attestation at the factory.
+ * 2) The user has not permanently disabled device identifier attestation.
+ * 3) You have permission to access the device identifiers you are requesting attestation for.
+ * <p>
+ * For privacy reasons, you cannot distinguish between (1) and (2). If attestation is
+ * unsuccessful, the device may not support it in general or the user may have permanently
+ * disabled it.
+ * <p>
+ * The caller must hold {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE}
+ * permission.
+ *
+ * @param context the context to use for retrieving device identifiers.
+ * @param idTypes the types of device identifiers to attest.
+ * @param attestationChallenge a blob to include in the certificate alongside the device
+ * identifiers.
+ *
+ * @return a certificate chain containing the requested device identifiers in the first element
+ *
+ * @exception SecurityException if you are not permitted to obtain an attestation of the
+ * device's identifiers.
+ * @exception DeviceIdAttestationException if the attestation operation fails.
+ */
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @NonNull public static X509Certificate[] attestDeviceIds(Context context,
+ @NonNull int[] idTypes, @NonNull byte[] attestationChallenge) throws
+ DeviceIdAttestationException {
+ // Check method arguments, retrieve requested device IDs and prepare attestation arguments.
+ if (idTypes == null) {
+ throw new NullPointerException("Missing id types");
+ }
+ if (attestationChallenge == null) {
+ throw new NullPointerException("Missing attestation challenge");
+ }
+ final KeymasterArguments attestArgs = new KeymasterArguments();
+ attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, attestationChallenge);
+ final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length);
+ for (int idType : idTypes) {
+ idTypesSet.add(idType);
+ }
+ TelephonyManager telephonyService = null;
+ if (idTypesSet.contains(ID_TYPE_IMEI) || idTypesSet.contains(ID_TYPE_MEID)) {
+ telephonyService = (TelephonyManager) context.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ if (telephonyService == null) {
+ throw new DeviceIdAttestationException("Unable to access telephony service");
+ }
+ }
+ for (final Integer idType : idTypesSet) {
+ switch (idType) {
+ case ID_TYPE_SERIAL:
+ attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL,
+ Build.getSerial().getBytes(StandardCharsets.UTF_8));
+ break;
+ case ID_TYPE_IMEI: {
+ final String imei = telephonyService.getImei(0);
+ if (imei == null) {
+ throw new DeviceIdAttestationException("Unable to retrieve IMEI");
+ }
+ attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI,
+ imei.getBytes(StandardCharsets.UTF_8));
+ break;
+ }
+ case ID_TYPE_MEID: {
+ final String meid = telephonyService.getDeviceId();
+ if (meid == null) {
+ throw new DeviceIdAttestationException("Unable to retrieve MEID");
+ }
+ attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID,
+ meid.getBytes(StandardCharsets.UTF_8));
+ break;
+ }
+ default:
+ throw new IllegalArgumentException("Unknown device ID type " + idType);
+ }
+ }
+ attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND,
+ Build.BRAND.getBytes(StandardCharsets.UTF_8));
+ attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE,
+ Build.DEVICE.getBytes(StandardCharsets.UTF_8));
+ attestArgs.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT,
+ Build.PRODUCT.getBytes(StandardCharsets.UTF_8));
+
+ final KeyStore keyStore = KeyStore.getInstance();
+ final String keyAlias = "android_internal_device_id_attestation-"
+ + Process.myPid() + "-" + sSequenceNumber.incrementAndGet();
+ // Clear any leftover temporary key.
+ if (!keyStore.delete(keyAlias)) {
+ throw new DeviceIdAttestationException("Unable to remove temporary key");
+ }
+ try {
+ // Generate a temporary key.
+ final KeymasterArguments generateArgs = new KeymasterArguments();
+ generateArgs.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_VERIFY);
+ generateArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
+ generateArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
+ generateArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_NONE);
+ generateArgs.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
+ generateArgs.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048);
+ generateArgs.addUnsignedLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT,
+ RSAKeyGenParameterSpec.F4);
+ int errorCode = keyStore.generateKey(keyAlias, generateArgs, null, 0,
+ new KeyCharacteristics());
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw new DeviceIdAttestationException("Unable to create temporary key",
+ KeyStore.getKeyStoreException(errorCode));
+ }
+
+ // Perform attestation.
+ final KeymasterCertificateChain outChain = new KeymasterCertificateChain();
+ errorCode = keyStore.attestKey(keyAlias, attestArgs, outChain);
+ if (errorCode != KeyStore.NO_ERROR) {
+ throw new DeviceIdAttestationException("Unable to perform attestation",
+ KeyStore.getKeyStoreException(errorCode));
+ }
+
+ // Extract certificate chain.
+ final Collection<byte[]> rawChain = outChain.getCertificates();
+ if (rawChain.size() < 2) {
+ throw new DeviceIdAttestationException("Attestation certificate chain contained "
+ + rawChain.size() + " entries. At least two are required.");
+ }
+ final ByteArrayOutputStream concatenatedRawChain = new ByteArrayOutputStream();
+ try {
+ for (final byte[] cert : rawChain) {
+ concatenatedRawChain.write(cert);
+ }
+ return CertificateFactory.getInstance("X.509").generateCertificates(
+ new ByteArrayInputStream(concatenatedRawChain.toByteArray()))
+ .toArray(new X509Certificate[0]);
+ } catch (Exception e) {
+ throw new DeviceIdAttestationException("Unable to construct certificate chain", e);
+ }
+ } finally {
+ // Remove temporary key.
+ keyStore.delete(keyAlias);
+ }
+ }
+}
diff --git a/keystore/java/android/security/keystore/DeviceIdAttestationException.java b/keystore/java/android/security/keystore/DeviceIdAttestationException.java
new file mode 100644
index 0000000..e18d193
--- /dev/null
+++ b/keystore/java/android/security/keystore/DeviceIdAttestationException.java
@@ -0,0 +1,45 @@
+/*
+ * 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.security.keystore;
+
+/**
+ * Thrown when {@link AttestationUtils} is unable to attest the given device ids.
+ *
+ * @hide
+ */
+public class DeviceIdAttestationException extends Exception {
+ /**
+ * Constructs a new {@code DeviceIdAttestationException} with the current stack trace and the
+ * specified detail message.
+ *
+ * @param detailMessage the detail message for this exception.
+ */
+ public DeviceIdAttestationException(String detailMessage) {
+ super(detailMessage);
+ }
+
+ /**
+ * Constructs a new {@code DeviceIdAttestationException} with the current stack trace, the
+ * specified detail message and the specified cause.
+ *
+ * @param message the detail message for this exception.
+ * @param cause the cause of this exception, may be {@code null}.
+ */
+ public DeviceIdAttestationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index e068900..acacd76 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -288,22 +288,34 @@
{
AutoMutex _l(mLock);
const String8 paths[2] = { String8(targetApkPath), String8(overlayApkPath) };
- ResTable tables[2];
+ Asset* assets[2] = {NULL, NULL};
+ bool ret = false;
+ {
+ ResTable tables[2];
- for (int i = 0; i < 2; ++i) {
- asset_path ap;
- ap.type = kFileTypeRegular;
- ap.path = paths[i];
- Asset* ass = openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap);
- if (ass == NULL) {
- ALOGW("failed to find resources.arsc in %s\n", ap.path.string());
- return false;
+ for (int i = 0; i < 2; ++i) {
+ asset_path ap;
+ ap.type = kFileTypeRegular;
+ ap.path = paths[i];
+ assets[i] = openNonAssetInPathLocked("resources.arsc",
+ Asset::ACCESS_BUFFER, ap);
+ if (assets[i] == NULL) {
+ ALOGW("failed to find resources.arsc in %s\n", ap.path.string());
+ goto exit;
+ }
+ if (tables[i].add(assets[i]) != NO_ERROR) {
+ ALOGW("failed to add %s to resource table", paths[i].string());
+ goto exit;
+ }
}
- tables[i].add(ass);
+ ret = tables[0].createIdmap(tables[1], targetCrc, overlayCrc,
+ targetApkPath, overlayApkPath, (void**)outData, outSize) == NO_ERROR;
}
- return tables[0].createIdmap(tables[1], targetCrc, overlayCrc,
- targetApkPath, overlayApkPath, (void**)outData, outSize) == NO_ERROR;
+exit:
+ delete assets[0];
+ delete assets[1];
+ return ret;
}
bool AssetManager::addDefaultAssets()
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 89e2a01..e54bc36 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -367,7 +367,7 @@
// (see https://code.google.com/p/skia/issues/detail?id=1303)
bool SkiaCanvas::getClipBounds(SkRect* outRect) const {
SkIRect ibounds;
- if (!mCanvas->getClipDeviceBounds(&ibounds)) {
+ if (!mCanvas->getDeviceClipBounds(&ibounds)) {
return false;
}
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index ca43156..9041b44 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -130,9 +130,9 @@
sk_sp<SkTypeface> typeface = SkTypeface::MakeFromStream(fontData.release());
LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", kRobotoFont);
- minikin::FontFamily* family = new minikin::FontFamily();
minikin::MinikinFont* font = new MinikinFontSkia(std::move(typeface), data, st.st_size, 0);
- family->addFont(font);
+ minikin::FontFamily* family = new minikin::FontFamily(
+ std::vector<minikin::Font>({ minikin::Font(font, minikin::FontStyle()) }));
font->Unref();
std::vector<minikin::FontFamily*> typefaces = { family };
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index 6ca8d8b..ea302a1 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -59,8 +59,7 @@
SkImageInfo canvasInfo = canvas->imageInfo();
SkMatrix44 mat4(canvas->getTotalMatrix());
- SkIRect ibounds;
- canvas->getClipDeviceBounds(&ibounds);
+ SkIRect ibounds = canvas->getDeviceClipBounds();
DrawGlInfo info;
info.clipLeft = ibounds.fLeft;
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.h b/libs/hwui/pipeline/skia/GLFunctorDrawable.h
index bf39dad..012c948 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.h
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.h
@@ -37,9 +37,9 @@
public:
GLFunctorDrawable(Functor* functor, GlFunctorLifecycleListener* listener, SkCanvas* canvas)
: mFunctor(functor)
- , mListener(listener) {
- canvas->getClipBounds(&mBounds);
- }
+ , mListener(listener)
+ , mBounds(canvas->getLocalClipBounds())
+ {}
virtual ~GLFunctorDrawable();
void syncFunctor() const;
@@ -51,7 +51,7 @@
private:
Functor* mFunctor;
sp<GlFunctorLifecycleListener> mListener;
- SkRect mBounds;
+ const SkRect mBounds;
};
}; // namespace skiapipeline
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 79daa3f..0916d72 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -193,9 +193,7 @@
}
SkRect TestUtils::getClipBounds(const SkCanvas* canvas) {
- SkIRect bounds;
- (void)canvas->getClipDeviceBounds(&bounds);
- return SkRect::Make(bounds);
+ return SkRect::Make(canvas->getDeviceClipBounds());
}
SkRect TestUtils::getLocalClipBounds(const SkCanvas* canvas) {
diff --git a/packages/SystemUI/res/drawable-nodpi/icon.xml b/packages/SystemUI/res/drawable-nodpi/icon.xml
index 5e08fcb..abafb68 100644
--- a/packages/SystemUI/res/drawable-nodpi/icon.xml
+++ b/packages/SystemUI/res/drawable-nodpi/icon.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2016 The Android Open Source Project
+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.
@@ -19,20 +19,22 @@
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
- android:fillColor="#00796B"
- android:pathData="M32.0,12.5l0.0,28.0l12.0,-5.0l0.0,-28.0z"/>
+ android:pathData="M25.0,25.0m-20.5,0.0a20.5,20.5,0,1,1,41.0,0.0a20.5,20.5,0,1,1,-41.0,0.0"
+ android:fillAlpha="0.066"
+ android:fillColor="#000000"/>
<path
- android:fillColor="#00796B"
- android:pathData="M4.0,40.5l12.0,-5.0l0.0,-11.0l-12.0,-12.0z"/>
+ android:pathData="M24.0,24.0m-20.0,0.0a20.0,20.0,0,1,1,40.0,0.0a20.0,20.0,0,1,1,-40.0,0.0"
+ android:fillColor="#FFC107"/>
<path
- android:fillColor="#40000000"
- android:pathData="M44.0,35.5l-12.0,-12.0l0.0,-4.0z"/>
+ android:pathData="M44,24.2010101 L33.9004889,14.101499 L14.101499,33.9004889 L24.2010101,44 C29.2525804,43.9497929 34.2887564,41.9975027 38.1431296,38.1431296 C41.9975027,34.2887564 43.9497929,29.2525804 44,24.2010101 Z"
+ android:fillColor="#FE9F00"/>
<path
- android:fillColor="#40000000"
- android:pathData="M4.0,12.5l12.0,12.0l0.0,4.0z"/>
+ android:pathData="M24.0,24.0m-14.0,0.0a14.0,14.0,0,1,1,28.0,0.0a14.0,14.0,0,1,1,-28.0,0.0"
+ android:fillColor="#FED44F"/>
<path
- android:fillColor="#4DB6AC"
- android:pathData="M32.0,23.5l-16.0,-16.0l-12.0,5.0l0.0,0.0l12.0,12.0l16.0,16.0l12.0,-5.0l0.0,0.0z"/>
+ android:pathData="M37.7829445,26.469236 L29.6578482,18.3441397 L18.3441397,29.6578482 L26.469236,37.7829445 C29.1911841,37.2979273 31.7972024,36.0037754 33.9004889,33.9004889 C36.0037754,31.7972024 37.2979273,29.1911841 37.7829445,26.469236 Z"
+ android:fillColor="#FFC107"/>
+ <path
+ android:pathData="M24.0,24.0m-8.0,0.0a8.0,8.0,0,1,1,16.0,0.0a8.0,8.0,0,1,1,-16.0,0.0"
+ android:fillColor="#FFFFFF"/>
</vector>
-
-
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 71dda2d..2fe9e77 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -217,8 +217,16 @@
return;
}
mTempWarning = false;
- mNoMan.cancelAsUser(TAG_TEMPERATURE, SystemMessage.NOTE_HIGH_TEMP,
- UserHandle.ALL);
+ dismissTemperatureWarningInternal();
+ }
+
+ /**
+ * Internal only version of {@link #dismissTemperatureWarning()} that simply dismisses
+ * the notification. As such, the notification will not show again until
+ * {@link #dismissTemperatureWarning()} is called.
+ */
+ private void dismissTemperatureWarningInternal() {
+ mNoMan.cancelAsUser(TAG_TEMPERATURE, SystemMessage.NOTE_HIGH_TEMP, UserHandle.ALL);
}
@Override
@@ -390,10 +398,10 @@
} else if (action.equals(ACTION_DISMISSED_WARNING)) {
dismissLowBatteryWarning();
} else if (ACTION_CLICKED_TEMP_WARNING.equals(action)) {
- dismissTemperatureWarning();
+ dismissTemperatureWarningInternal();
showTemperatureDialog();
} else if (ACTION_DISMISSED_TEMP_WARNING.equals(action)) {
- dismissTemperatureWarning();
+ dismissTemperatureWarningInternal();
}
}
}
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/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 189474b..a5742c2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -12044,14 +12044,12 @@
final long ident = Binder.clearCallingIdentity();
try {
if (mUserController.shouldConfirmCredentials(userId)) {
- final int currentUserId = mUserController.getCurrentUserIdLocked();
- if (!mKeyguardController.isKeyguardLocked()) {
- // If the device is not locked, we will prompt for credentials immediately.
- mStackSupervisor.lockAllProfileTasks(userId);
- } else {
+ if (mKeyguardController.isKeyguardLocked()) {
// Showing launcher to avoid user entering credential twice.
+ final int currentUserId = mUserController.getCurrentUserIdLocked();
startHomeActivityLocked(currentUserId, "notifyLockedProfile");
}
+ mStackSupervisor.lockAllProfileTasks(userId);
}
} finally {
Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 997222c..1feae3d 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -482,7 +482,7 @@
UserManager um = UserManager.get(mContext);
// Allow current user or profiles of the current user...
- for (int profileId : um.getEnabledProfileIds(userId)) {
+ for (int profileId : um.getEnabledProfileIds(mCurrentUserId)) {
if (profileId == userId) {
return true;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 72faa81..45bdb9c 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -32,6 +32,7 @@
import static android.service.notification.NotificationListenerService.REASON_PACKAGE_SUSPENDED;
import static android.service.notification.NotificationListenerService.REASON_PROFILE_TURNED_OFF;
import static android.service.notification.NotificationListenerService.REASON_SNOOZED;
+import static android.service.notification.NotificationListenerService.REASON_TIMEOUT;
import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED;
import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED;
import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
@@ -50,6 +51,7 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
@@ -165,6 +167,7 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -229,6 +232,12 @@
private static final long DELAY_FOR_ASSISTANT_TIME = 100;
+ private static final String ACTION_NOTIFICATION_TIMEOUT =
+ NotificationManagerService.class.getSimpleName() + ".TIMEOUT";
+ private static final int REQUEST_CODE_TIMEOUT = 1;
+ private static final String SCHEME_TIMEOUT = "timeout";
+ private static final String EXTRA_KEY = "key";
+
private IActivityManager mAm;
private IPackageManager mPackageManager;
private PackageManager mPackageManagerClient;
@@ -237,6 +246,7 @@
@Nullable StatusBarManagerInternal mStatusBar;
Vibrator mVibrator;
private WindowManagerInternal mWindowManagerInternal;
+ private AlarmManager mAlarmManager;
final IBinder mForegroundToken = new Binder();
private Handler mHandler;
@@ -682,6 +692,29 @@
updateLightsLocked();
}
+ private final BroadcastReceiver mNotificationTimeoutReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+ if (ACTION_NOTIFICATION_TIMEOUT.equals(action)) {
+ final NotificationRecord record;
+ synchronized (mNotificationLock) {
+ record = findNotificationByKeyLocked(intent.getStringExtra(EXTRA_KEY));
+ }
+ if (record != null) {
+ cancelNotification(record.sbn.getUid(), record.sbn.getInitialPid(),
+ record.sbn.getPackageName(), record.sbn.getTag(),
+ record.sbn.getId(), 0,
+ Notification.FLAG_FOREGROUND_SERVICE, true, record.getUserId(),
+ REASON_TIMEOUT, null);
+ }
+ }
+ }
+ };
+
private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -966,6 +999,7 @@
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
mAppUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
+ mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
mHandler = new WorkerHandler(looper);
mRankingThread.start();
@@ -1132,6 +1166,10 @@
getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, sdFilter, null,
null);
+ IntentFilter timeoutFilter = new IntentFilter(ACTION_NOTIFICATION_TIMEOUT);
+ timeoutFilter.addDataScheme(SCHEME_TIMEOUT);
+ getContext().registerReceiver(mNotificationTimeoutReceiver, timeoutFilter);
+
mSettingsObserver = new SettingsObserver(mHandler);
mArchive = new Archive(resources.getInteger(
@@ -1683,6 +1721,9 @@
/**
* Public API for getting a list of current notifications for the calling package/uid.
*
+ * Note that since notification posting is done asynchronously, this will not return
+ * notifications that are in the process of being posted.
+ *
* @returns A list of all the package's notifications, in natural order.
*/
@Override
@@ -2990,9 +3031,6 @@
// setup local book-keeping
final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
- synchronized (mNotificationLock) {
- mEnqueuedNotifications.add(r);
- }
mHandler.post(new EnqueueNotificationRunnable(userId, r));
idOut[0] = id;
@@ -3010,6 +3048,9 @@
@Override
public void run() {
synchronized (mNotificationLock) {
+ mEnqueuedNotifications.add(r);
+ scheduleTimeoutLocked(r);
+
if (mSnoozeHelper.isSnoozed(userId, r.sbn.getPackageName(), r.getKey())) {
// TODO: log to event log
if (DBG) {
@@ -3239,6 +3280,22 @@
}
@VisibleForTesting
+ void scheduleTimeoutLocked(NotificationRecord record) {
+ if (record.getNotification().getTimeout() > System.currentTimeMillis()) {
+ final PendingIntent pi = PendingIntent.getBroadcast(getContext(),
+ REQUEST_CODE_TIMEOUT,
+ new Intent(ACTION_NOTIFICATION_TIMEOUT)
+ .setData(new Uri.Builder().scheme(SCHEME_TIMEOUT)
+ .appendPath(record.getKey()).build())
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .putExtra(EXTRA_KEY, record.getKey()),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ mAlarmManager.setExactAndAllowWhileIdle(
+ AlarmManager.RTC_WAKEUP, record.getNotification().getTimeout(), pi);
+ }
+ }
+
+ @VisibleForTesting
void buzzBeepBlinkLocked(NotificationRecord record) {
boolean buzz = false;
boolean beep = false;
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 2a5a25f..8998128 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -41,6 +41,7 @@
import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
+import android.util.TimeUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.EventLogTags;
@@ -318,6 +319,7 @@
pw.println(prefix + " vibrate=" + Arrays.toString(notification.vibrate));
pw.println(prefix + String.format(" led=0x%08x onMs=%d offMs=%d",
notification.ledARGB, notification.ledOnMS, notification.ledOffMS));
+ pw.println(prefix + " timeout=" + TimeUtils.formatForLogging(notification.getTimeout()));
if (notification.actions != null && notification.actions.length > 0) {
pw.println(prefix + " actions={");
final int N = notification.actions.length;
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 0130e30..fc66bb3 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -295,6 +295,15 @@
}
}
+ public void removeIdmap(String overlayApkPath) throws InstallerException {
+ if (!checkBeforeRemote()) return;
+ try {
+ mInstalld.removeIdmap(overlayApkPath);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
public void rmdex(String codePath, String instructionSet) throws InstallerException {
assertValidInstructionSet(instructionSet);
if (!checkBeforeRemote()) return;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 1eb8b94..984120f 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;
@@ -2347,6 +2345,12 @@
}
@Override
+ public boolean removeUserEvenWhenDisallowed(@UserIdInt int userHandle) {
+ checkManageOrCreateUsersPermission("Only the system can remove users");
+ return removeUserUnchecked(userHandle);
+ }
+
+ @Override
public UserInfo createUser(String name, int flags) {
checkManageOrCreateUsersPermission(flags);
return createUserInternal(name, flags, UserHandle.USER_NULL);
@@ -3133,8 +3137,6 @@
applyUserRestrictionsLR(userId);
}
}
-
- maybeInitializeDemoMode(userId);
}
/**
@@ -3173,29 +3175,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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f3b0131..420cfe9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -33,6 +33,8 @@
import static android.app.admin.DevicePolicyManager.CODE_USER_HAS_PROFILE_OWNER;
import static android.app.admin.DevicePolicyManager.CODE_USER_NOT_RUNNING;
import static android.app.admin.DevicePolicyManager.CODE_USER_SETUP_COMPLETED;
+import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
+import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
@@ -259,6 +261,12 @@
private static final String ATTR_APPLICATION_RESTRICTIONS_MANAGER
= "application-restrictions-manager";
+ // Comprehensive list of delegations.
+ private static final String DELEGATIONS[] = {
+ DELEGATION_CERT_INSTALL,
+ DELEGATION_APP_RESTRICTIONS
+ };
+
/**
* System property whose value is either "true" or "false", indicating whether
* device owner is present.
@@ -468,12 +476,11 @@
ComponentName mRestrictionsProvider;
- String mDelegatedCertInstallerPackage;
+ // Map of delegate package to delegation scopes
+ final ArrayMap<String, List<String>> mDelegationMap = new ArrayMap<>();
boolean doNotAskCredentialsOnBoot = false;
- String mApplicationRestrictionsManagingPackage;
-
Set<String> mAffiliationIds = new ArraySet<>();
long mLastSecurityLogRetrievalTime = -1;
@@ -1397,7 +1404,7 @@
}
private void handlePackagesChanged(String packageName, int userHandle) {
- boolean removed = false;
+ boolean removedAdmin = false;
if (VERBOSE_LOG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle);
DevicePolicyData policy = getUserData(userHandle);
synchronized (this) {
@@ -1413,32 +1420,36 @@
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
userHandle) == null) {
- removed = true;
+ removedAdmin = true;
policy.mAdminList.remove(i);
policy.mAdminMap.remove(aa.info.getComponent());
}
}
} catch (RemoteException re) {
- // Shouldn't happen
+ // Shouldn't happen.
}
}
- if (removed) {
+ if (removedAdmin) {
validatePasswordOwnerLocked(policy);
- saveSettingsLocked(policy.mUserHandle);
}
- // Check if delegated cert installer or app restrictions managing packages are removed.
- if (isRemovedPackage(packageName, policy.mDelegatedCertInstallerPackage, userHandle)) {
- policy.mDelegatedCertInstallerPackage = null;
- saveSettingsLocked(policy.mUserHandle);
+ boolean removedDelegate = false;
+
+ // Check if a delegate was removed.
+ for (int i = policy.mDelegationMap.size() - 1; i >= 0; i--) {
+ final String delegatePackage = policy.mDelegationMap.keyAt(i);
+ if (isRemovedPackage(packageName, delegatePackage, userHandle)) {
+ policy.mDelegationMap.removeAt(i);
+ removedDelegate = true;
+ }
}
- if (isRemovedPackage(
- packageName, policy.mApplicationRestrictionsManagingPackage, userHandle)) {
- policy.mApplicationRestrictionsManagingPackage = null;
+
+ // Persist updates if the removed package was an admin or delegate.
+ if (removedAdmin || removedDelegate) {
saveSettingsLocked(policy.mUserHandle);
}
}
- if (removed) {
+ if (removedAdmin) {
// The removed admin might have disabled camera, so update user restrictions.
pushUserRestrictions(userHandle);
}
@@ -1609,6 +1620,11 @@
mContext.getSystemService(PowerManager.class).reboot(reason);
}
+ void recoverySystemRebootWipeUserData(boolean shutdown, String reason, boolean force)
+ throws IOException {
+ RecoverySystem.rebootWipeUserData(mContext, shutdown, reason, force);
+ }
+
boolean systemPropertiesGetBoolean(String key, boolean def) {
return SystemProperties.getBoolean(key, def);
}
@@ -2372,13 +2388,19 @@
out.attribute(null, ATTR_PERMISSION_POLICY,
Integer.toString(policy.mPermissionPolicy));
}
- if (policy.mDelegatedCertInstallerPackage != null) {
- out.attribute(null, ATTR_DELEGATED_CERT_INSTALLER,
- policy.mDelegatedCertInstallerPackage);
- }
- if (policy.mApplicationRestrictionsManagingPackage != null) {
- out.attribute(null, ATTR_APPLICATION_RESTRICTIONS_MANAGER,
- policy.mApplicationRestrictionsManagingPackage);
+
+ // Serialize delegations.
+ for (int i = 0; i < policy.mDelegationMap.size(); ++i) {
+ final String delegatePackage = policy.mDelegationMap.keyAt(i);
+ final List<String> scopes = policy.mDelegationMap.valueAt(i);
+
+ // Every "delegation" tag serializes the information of one delegate-scope pair.
+ for (String scope : scopes) {
+ out.startTag(null, "delegation");
+ out.attribute(null, "delegatePackage", delegatePackage);
+ out.attribute(null, "scope", scope);
+ out.endTag(null, "delegation");
+ }
}
final int N = policy.mAdminList.size();
@@ -2562,10 +2584,36 @@
if (!TextUtils.isEmpty(permissionPolicy)) {
policy.mPermissionPolicy = Integer.parseInt(permissionPolicy);
}
- policy.mDelegatedCertInstallerPackage = parser.getAttributeValue(null,
- ATTR_DELEGATED_CERT_INSTALLER);
- policy.mApplicationRestrictionsManagingPackage = parser.getAttributeValue(null,
- ATTR_APPLICATION_RESTRICTIONS_MANAGER);
+ // Check for delegation compatibility with pre-O.
+ // TODO(edmanp) remove in P.
+ {
+ final String certDelegate = parser.getAttributeValue(null,
+ ATTR_DELEGATED_CERT_INSTALLER);
+ if (certDelegate != null) {
+ List<String> scopes = policy.mDelegationMap.get(certDelegate);
+ if (scopes == null) {
+ scopes = new ArrayList<>();
+ policy.mDelegationMap.put(certDelegate, scopes);
+ }
+ if (!scopes.contains(DELEGATION_CERT_INSTALL)) {
+ scopes.add(DELEGATION_CERT_INSTALL);
+ needsRewrite = true;
+ }
+ }
+ final String appRestrictionsDelegate = parser.getAttributeValue(null,
+ ATTR_APPLICATION_RESTRICTIONS_MANAGER);
+ if (appRestrictionsDelegate != null) {
+ List<String> scopes = policy.mDelegationMap.get(appRestrictionsDelegate);
+ if (scopes == null) {
+ scopes = new ArrayList<>();
+ policy.mDelegationMap.put(appRestrictionsDelegate, scopes);
+ }
+ if (!scopes.contains(DELEGATION_APP_RESTRICTIONS)) {
+ scopes.add(DELEGATION_APP_RESTRICTIONS);
+ needsRewrite = true;
+ }
+ }
+ }
type = parser.next();
int outerDepth = parser.getDepth();
@@ -2600,6 +2648,23 @@
} catch (RuntimeException e) {
Slog.w(LOG_TAG, "Failed loading admin " + name, e);
}
+ } else if ("delegation".equals(tag)) {
+ // Parse delegation info.
+ final String delegatePackage = parser.getAttributeValue(null,
+ "delegatePackage");
+ final String scope = parser.getAttributeValue(null, "scope");
+
+ // Get a reference to the scopes list for the delegatePackage.
+ List<String> scopes = policy.mDelegationMap.get(delegatePackage);
+ // Or make a new list if none was found.
+ if (scopes == null) {
+ scopes = new ArrayList<>();
+ policy.mDelegationMap.put(delegatePackage, scopes);
+ }
+ // Add the new scope to the list of delegatePackage if it's not already there.
+ if (!scopes.contains(scope)) {
+ scopes.add(scope);
+ }
} else if ("failed-password-attempts".equals(tag)) {
policy.mFailedPasswordAttempts = Integer.parseInt(
parser.getAttributeValue(null, "value"));
@@ -4522,9 +4587,9 @@
}
@Override
- public void enforceCanManageCaCerts(ComponentName who) {
+ public void enforceCanManageCaCerts(ComponentName who, String callerPackage) {
if (who == null) {
- if (!isCallerDelegatedCertInstaller()) {
+ if (!isCallerDelegate(callerPackage, DELEGATION_CERT_INSTALL)) {
mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null);
}
} else {
@@ -4538,35 +4603,6 @@
}
}
- private void enforceCanManageInstalledKeys(ComponentName who) {
- if (who == null) {
- if (!isCallerDelegatedCertInstaller()) {
- throw new SecurityException("who == null, but caller is not cert installer");
- }
- } else {
- enforceProfileOrDeviceOwner(who);
- }
- }
-
- private boolean isCallerDelegatedCertInstaller() {
- final int callingUid = mInjector.binderGetCallingUid();
- final int userHandle = UserHandle.getUserId(callingUid);
- synchronized (this) {
- final DevicePolicyData policy = getUserData(userHandle);
- if (policy.mDelegatedCertInstallerPackage == null) {
- return false;
- }
-
- try {
- int uid = mInjector.getPackageManager().getPackageUidAsUser(
- policy.mDelegatedCertInstallerPackage, userHandle);
- return uid == callingUid;
- } catch (NameNotFoundException e) {
- return false;
- }
- }
- }
-
@Override
public boolean approveCaCert(String alias, int userId, boolean approval) {
enforceManageUsers();
@@ -4608,8 +4644,9 @@
}
@Override
- public boolean installCaCert(ComponentName admin, byte[] certBuffer) throws RemoteException {
- enforceCanManageCaCerts(admin);
+ public boolean installCaCert(ComponentName admin, String callerPackage, byte[] certBuffer)
+ throws RemoteException {
+ enforceCanManageCaCerts(admin, callerPackage);
byte[] pemCert;
try {
@@ -4651,8 +4688,8 @@
}
@Override
- public void uninstallCaCerts(ComponentName admin, String[] aliases) {
- enforceCanManageCaCerts(admin);
+ public void uninstallCaCerts(ComponentName admin, String callerPackage, String[] aliases) {
+ enforceCanManageCaCerts(admin, callerPackage);
final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
final long id = mInjector.binderClearCallingIdentity();
@@ -4676,9 +4713,11 @@
}
@Override
- public boolean installKeyPair(ComponentName who, byte[] privKey, byte[] cert, byte[] chain,
- String alias, boolean requestAccess) {
- enforceCanManageInstalledKeys(who);
+ public boolean installKeyPair(ComponentName who, String callerPackage, byte[] privKey,
+ byte[] cert, byte[] chain, String alias, boolean requestAccess) {
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+ DELEGATION_CERT_INSTALL);
+
final int callingUid = mInjector.binderGetCallingUid();
final long id = mInjector.binderClearCallingIdentity();
@@ -4709,8 +4748,9 @@
}
@Override
- public boolean removeKeyPair(ComponentName who, String alias) {
- enforceCanManageInstalledKeys(who);
+ public boolean removeKeyPair(ComponentName who, String callerPackage, String alias) {
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+ DELEGATION_CERT_INSTALL);
final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
final long id = Binder.clearCallingIdentity();
@@ -4795,33 +4835,267 @@
}.execute();
}
+ /**
+ * Set the scopes of a device owner or profile owner delegate.
+ *
+ * @param who the device owner or profile owner.
+ * @param delegatePackage the name of the delegate package.
+ * @param scopes the list of delegation scopes to be given to the delegate package.
+ */
@Override
- public void setCertInstallerPackage(ComponentName who, String installerPackage)
- throws SecurityException {
- int userHandle = UserHandle.getCallingUserId();
+ public void setDelegatedScopes(ComponentName who, String delegatePackage,
+ List<String> scopes) throws SecurityException {
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ Preconditions.checkStringNotEmpty(delegatePackage, "Delegate package is null or empty");
+ Preconditions.checkCollectionElementsNotNull(scopes, "Scopes");
+ // Remove possible duplicates.
+ scopes = new ArrayList(new ArraySet(scopes));
+ // Ensure given scopes are valid.
+ if (scopes.retainAll(Arrays.asList(DELEGATIONS))) {
+ throw new IllegalArgumentException("Unexpected delegation scopes");
+ }
+
+ // Retrieve the user ID of the calling process.
+ final int userId = mInjector.userHandleGetCallingUserId();
synchronized (this) {
+ // Ensure calling process is device/profile owner.
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- if (getTargetSdk(who.getPackageName(), userHandle) >= Build.VERSION_CODES.N) {
- if (installerPackage != null &&
- !isPackageInstalledForUser(installerPackage, userHandle)) {
- throw new IllegalArgumentException("Package " + installerPackage
+ // Ensure the delegate is installed (skip this for DELEGATION_CERT_INSTALL in pre-N).
+ if (scopes.size() == 1 && scopes.get(0).equals(DELEGATION_CERT_INSTALL) ||
+ getTargetSdk(who.getPackageName(), userId) >= Build.VERSION_CODES.N) {
+ // Throw when the delegate package is not installed.
+ if (!isPackageInstalledForUser(delegatePackage, userId)) {
+ throw new IllegalArgumentException("Package " + delegatePackage
+ " is not installed on the current user");
}
}
- DevicePolicyData policy = getUserData(userHandle);
- policy.mDelegatedCertInstallerPackage = installerPackage;
- saveSettingsLocked(userHandle);
+
+ // Set the new delegate in user policies.
+ final DevicePolicyData policy = getUserData(userId);
+ if (!scopes.isEmpty()) {
+ policy.mDelegationMap.put(delegatePackage, new ArrayList<>(scopes));
+ } else {
+ // Remove any delegation info if the given scopes list is empty.
+ policy.mDelegationMap.remove(delegatePackage);
+ }
+
+ // Notify delegate package of updates.
+ final Intent intent = new Intent(
+ DevicePolicyManager.ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED);
+ // Only call receivers registered in the manifest (don’t wake app if not running).
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ // Limit components this intent resolves to to the delegate package.
+ intent.setPackage(delegatePackage);
+ // Include the list of delegated scopes as an extra.
+ intent.putExtra(DevicePolicyManager.EXTRA_DELEGATION_SCOPES, scopes.toArray());
+ // Send the broadcast.
+ mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
+
+ // Persist updates.
+ saveSettingsLocked(userId);
+ }
+ }
+
+ /**
+ * Get the delegation scopes given to a delegate package by a device owner or profile owner.
+ *
+ * A DO/PO can get the scopes of any package. A non DO/PO package can get its own scopes by
+ * passing in {@code null} as the {@code who} parameter and its own name as the
+ * {@code delegatepackage}.
+ *
+ * @param who the device owner or profile owner, or {@code null} if the caller is
+ * {@code delegatePackage}.
+ * @param delegatePackage the name of the delegate package whose scopes are to be retrieved.
+ * @return a list of the delegation scopes currently given to {@code delegatePackage}.
+ */
+ @Override
+ @NonNull
+ public List<String> getDelegatedScopes(ComponentName who,
+ String delegatePackage) throws SecurityException {
+ Preconditions.checkNotNull(delegatePackage, "Delegate package is null");
+
+ // Retrieve the user ID of the calling process.
+ final int callingUid = mInjector.binderGetCallingUid();
+ final int userId = UserHandle.getUserId(callingUid);
+ synchronized (this) {
+ // Ensure calling process is device/profile owner.
+ if (who != null) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ // Or ensure calling process is delegatePackage itself.
+ } else {
+ int uid = 0;
+ try {
+ uid = mInjector.getPackageManager()
+ .getPackageUidAsUser(delegatePackage, userId);
+ } catch(NameNotFoundException e) {
+ }
+ if (uid != callingUid) {
+ throw new SecurityException("Caller with uid " + callingUid + " is not "
+ + delegatePackage);
+ }
+ }
+ final DevicePolicyData policy = getUserData(userId);
+ // Retrieve the scopes assigned to delegatePackage, or null if no scope was given.
+ final List<String> scopes = policy.mDelegationMap.get(delegatePackage);
+ return scopes == null ? Collections.EMPTY_LIST : scopes;
+ }
+ }
+
+ /**
+ * Get a list of packages that were given a specific delegation scopes by a device owner or
+ * profile owner.
+ *
+ * @param who the device owner or profile owner.
+ * @param scope the scope whose delegates are to be retrieved.
+ * @return a list of the delegate packages currently given the {@code scope} delegation.
+ */
+ @NonNull
+ public List<String> getDelegatePackages(ComponentName who, String scope)
+ throws SecurityException {
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ Preconditions.checkNotNull(scope, "Scope is null");
+ if (!Arrays.asList(DELEGATIONS).contains(scope)) {
+ throw new IllegalArgumentException("Unexpected delegation scope: " + scope);
+ }
+
+ // Retrieve the user ID of the calling process.
+ final int userId = mInjector.userHandleGetCallingUserId();
+ synchronized (this) {
+ // Ensure calling process is device/profile owner.
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ final DevicePolicyData policy = getUserData(userId);
+
+ // Create a list to hold the resulting delegate packages.
+ final List<String> delegatePackagesWithScope = new ArrayList<>();
+ // Add all delegations containing scope to the result list.
+ for (int i = 0; i < policy.mDelegationMap.size(); i++) {
+ if (policy.mDelegationMap.valueAt(i).contains(scope)) {
+ delegatePackagesWithScope.add(policy.mDelegationMap.keyAt(i));
+ }
+ }
+ return delegatePackagesWithScope;
+ }
+ }
+
+ /**
+ * Check whether a caller application has been delegated a given scope via
+ * {@link #setDelegatedScopes} to access privileged APIs on the behalf of a profile owner or
+ * device owner.
+ * <p>
+ * This is done by checking that {@code callerPackage} was granted {@code scope} delegation and
+ * then comparing the calling UID with the UID of {@code callerPackage} as reported by
+ * {@link PackageManager#getPackageUidAsUser}.
+ *
+ * @param callerPackage the name of the package that is trying to invoke a function in the DPMS.
+ * @param scope the delegation scope to be checked.
+ * @return {@code true} if the calling process is a delegate of {@code scope}.
+ */
+ private boolean isCallerDelegate(String callerPackage, String scope) {
+ Preconditions.checkNotNull(callerPackage, "callerPackage is null");
+ if (!Arrays.asList(DELEGATIONS).contains(scope)) {
+ throw new IllegalArgumentException("Unexpected delegation scope: " + scope);
+ }
+
+ // Retrieve the UID and user ID of the calling process.
+ final int callingUid = mInjector.binderGetCallingUid();
+ final int userId = UserHandle.getUserId(callingUid);
+ synchronized (this) {
+ // Retrieve user policy data.
+ final DevicePolicyData policy = getUserData(userId);
+ // Retrieve the list of delegation scopes granted to callerPackage.
+ final List<String> scopes = policy.mDelegationMap.get(callerPackage);
+ // Check callingUid only if callerPackage has the required scope delegation.
+ if (scopes != null && scopes.contains(scope)) {
+ try {
+ // Retrieve the expected UID for callerPackage.
+ final int uid = mInjector.getPackageManager()
+ .getPackageUidAsUser(callerPackage, userId);
+ // Return true if the caller is actually callerPackage.
+ return uid == callingUid;
+ } catch (NameNotFoundException e) {
+ // Ignore.
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Throw a security exception if a ComponentName is given and it is not a device/profile owner
+ * or if the calling process is not a delegate of the given scope.
+ *
+ * @param who the device owner of profile owner, or null if {@code callerPackage} is a
+ * {@code scope} delegate.
+ * @param callerPackage the name of the calling package. Required if {@code who} is
+ * {@code null}.
+ * @param reqPolicy the policy used in the API whose access permission is being checked.
+ * @param scoppe the delegation scope corresponding to the API being checked.
+ * @throws SecurityException if {@code who} is given and is not an owner for {@code reqPolicy};
+ * or when {@code who} is {@code null} and {@code callerPackage} is not a delegate
+ * of {@code scope}.
+ */
+ private void enforceCanManageScope(ComponentName who, String callerPackage, int reqPolicy,
+ String scope) {
+ // If a ComponentName is given ensure it is a device or profile owner according to policy.
+ if (who != null) {
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, reqPolicy);
+ }
+ // If no ComponentName is given ensure calling process has scope delegation.
+ } else if (!isCallerDelegate(callerPackage, scope)) {
+ throw new SecurityException("Caller with uid " + mInjector.binderGetCallingUid()
+ + " is not a delegate of scope " + scope + ".");
+ }
+ }
+
+ /**
+ * Helper function to preserve delegation behavior pre-O when using the deprecated functions
+ * {@code #setCertInstallerPackage} and {@code #setApplicationRestrictionsManagingPackage}.
+ */
+ private void setDelegatedScopePreO(ComponentName who,
+ String delegatePackage, String scope) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
+
+ final int userId = mInjector.userHandleGetCallingUserId();
+ synchronized(this) {
+ // Ensure calling process is device/profile owner.
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ final DevicePolicyData policy = getUserData(userId);
+
+ if (delegatePackage != null) {
+ // Set package as a delegate for scope if it is not already one.
+ List<String> scopes = policy.mDelegationMap.get(delegatePackage);
+ if (scopes == null) {
+ scopes = new ArrayList<>();
+ }
+ if (!scopes.contains(scope)) {
+ scopes.add(scope);
+ setDelegatedScopes(who, delegatePackage, scopes);
+ }
+ }
+
+ // Clear any existing scope delegates.
+ for (int i = 0; i < policy.mDelegationMap.size(); i++) {
+ final String currentPackage = policy.mDelegationMap.keyAt(i);
+ final List<String> currentScopes = policy.mDelegationMap.valueAt(i);
+
+ if (!currentPackage.equals(delegatePackage) && currentScopes.remove(scope)) {
+ setDelegatedScopes(who, currentPackage, currentScopes);
+ }
+ }
}
}
@Override
+ public void setCertInstallerPackage(ComponentName who, String installerPackage)
+ throws SecurityException {
+ setDelegatedScopePreO(who, installerPackage, DELEGATION_CERT_INSTALL);
+ }
+
+ @Override
public String getCertInstallerPackage(ComponentName who) throws SecurityException {
- int userHandle = UserHandle.getCallingUserId();
- synchronized (this) {
- getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- DevicePolicyData policy = getUserData(userHandle);
- return policy.mDelegatedCertInstallerPackage;
- }
+ final List<String> delegatePackages = getDelegatePackages(who, DELEGATION_CERT_INSTALL);
+ return delegatePackages.size() > 0 ? delegatePackages.get(0) : null;
}
/**
@@ -4869,7 +5143,7 @@
}
}
- private void wipeDataNoLock(boolean wipeExtRequested, String reason, boolean force) {
+ private void forceWipeDeviceNoLock(boolean wipeExtRequested, String reason) {
wtfIfInLock();
if (wipeExtRequested) {
@@ -4878,94 +5152,87 @@
sm.wipeAdoptableDisks();
}
try {
- RecoverySystem.rebootWipeUserData(mContext, false /* shutdown */, reason, force);
+ mInjector.recoverySystemRebootWipeUserData(
+ /*shutdown=*/ false, reason, /*force=*/ true);
} catch (IOException | SecurityException e) {
Slog.w(LOG_TAG, "Failed requesting data wipe", e);
}
}
+ private void forceWipeUser(int userId) {
+ try {
+ IActivityManager am = mInjector.getIActivityManager();
+ if (am.getCurrentUser().id == userId) {
+ am.switchUser(UserHandle.USER_SYSTEM);
+ }
+
+ boolean userRemoved = mUserManagerInternal.removeUserEvenWhenDisallowed(userId);
+ if (!userRemoved) {
+ Slog.w(LOG_TAG, "Couldn't remove user " + userId);
+ } else if (isManagedProfile(userId)) {
+ sendWipeProfileNotification();
+ }
+ } catch (RemoteException re) {
+ // Shouldn't happen
+ }
+ }
+
@Override
public void wipeData(int flags) {
if (!mHasFeature) {
return;
}
- final int userHandle = mInjector.userHandleGetCallingUserId();
- enforceFullCrossUsersPermission(userHandle);
+ enforceFullCrossUsersPermission(mInjector.userHandleGetCallingUserId());
- final String source;
+ final ActiveAdmin admin;
synchronized (this) {
- // This API can only be called by an active device admin,
- // so try to retrieve it to check that the caller is one.
- final ActiveAdmin admin = getActiveAdminForCallerLocked(null,
- DeviceAdminInfo.USES_POLICY_WIPE_DATA);
- source = admin.info.getComponent().flattenToShortString();
-
- long ident = mInjector.binderClearCallingIdentity();
- try {
- final String restriction;
- if (userHandle == UserHandle.USER_SYSTEM) {
- restriction = UserManager.DISALLOW_FACTORY_RESET;
- } else if (isManagedProfile(userHandle)) {
- restriction = UserManager.DISALLOW_REMOVE_MANAGED_PROFILE;
- } else {
- restriction = UserManager.DISALLOW_REMOVE_USER;
- }
- if (isAdminAffectedByRestriction(
- admin.info.getComponent(), restriction, userHandle)) {
- throw new SecurityException("Cannot wipe data. " + restriction
- + " restriction is set for user " + userHandle);
- }
-
- if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) {
- if (!isDeviceOwner(admin.info.getComponent(), userHandle)) {
- throw new SecurityException(
- "Only device owner admins can set WIPE_RESET_PROTECTION_DATA");
- }
- PersistentDataBlockManager manager = (PersistentDataBlockManager)
- mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
- if (manager != null) {
- manager.wipe();
- }
- }
-
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
+ admin = getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_WIPE_DATA);
}
- final boolean wipeExtRequested = (flags & WIPE_EXTERNAL_STORAGE) != 0;
- wipeDeviceNoLock(wipeExtRequested, userHandle,
- "DevicePolicyManager.wipeData() from " + source, /*force=*/ true);
+ String reason = "DevicePolicyManager.wipeData() from "
+ + admin.info.getComponent().flattenToShortString();
+ wipeDataNoLock(
+ admin.info.getComponent(), flags, reason, admin.getUserHandle().getIdentifier());
}
- private void wipeDeviceNoLock(
- boolean wipeExtRequested, final int userHandle, String reason, boolean force) {
+ private void wipeDataNoLock(ComponentName admin, int flags, String reason, int userId) {
wtfIfInLock();
long ident = mInjector.binderClearCallingIdentity();
try {
- // TODO If split user is enabled and the device owner is set in the primary user (rather
- // than system), we should probably trigger factory reset. Current code just remove
- // that user (but still clears FRP...)
- if (userHandle == UserHandle.USER_SYSTEM) {
- wipeDataNoLock(wipeExtRequested, reason, force);
+ // First check whether the admin is allowed to wipe the device/user/profile.
+ final String restriction;
+ if (userId == UserHandle.USER_SYSTEM) {
+ restriction = UserManager.DISALLOW_FACTORY_RESET;
+ } else if (isManagedProfile(userId)) {
+ restriction = UserManager.DISALLOW_REMOVE_MANAGED_PROFILE;
} else {
- try {
- IActivityManager am = mInjector.getIActivityManager();
- if (am.getCurrentUser().id == userHandle) {
- am.switchUser(UserHandle.USER_SYSTEM);
- }
+ restriction = UserManager.DISALLOW_REMOVE_USER;
+ }
+ if (isAdminAffectedByRestriction(admin, restriction, userId)) {
+ throw new SecurityException("Cannot wipe data. " + restriction
+ + " restriction is set for user " + userId);
+ }
- boolean userRemoved = force
- ? mUserManagerInternal.removeUserEvenWhenDisallowed(userHandle)
- : mUserManager.removeUser(userHandle);
- if (!userRemoved) {
- Slog.w(LOG_TAG, "Couldn't remove user " + userHandle);
- } else if (isManagedProfile(userHandle)) {
- sendWipeProfileNotification();
- }
- } catch (RemoteException re) {
- // Shouldn't happen
+ if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) {
+ if (!isDeviceOwner(admin, userId)) {
+ throw new SecurityException(
+ "Only device owner admins can set WIPE_RESET_PROTECTION_DATA");
}
+ PersistentDataBlockManager manager = (PersistentDataBlockManager)
+ mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
+ if (manager != null) {
+ manager.wipe();
+ }
+ }
+
+ // TODO If split user is enabled and the device owner is set in the primary user
+ // (rather than system), we should probably trigger factory reset. Current code just
+ // removes that user (but still clears FRP...)
+ if (userId == UserHandle.USER_SYSTEM) {
+ forceWipeDeviceNoLock(/*wipeExtRequested=*/ (flags & WIPE_EXTERNAL_STORAGE) != 0,
+ reason);
+ } else {
+ forceWipeUser(userId);
}
} finally {
mInjector.binderRestoreCallingIdentity(ident);
@@ -5105,25 +5372,21 @@
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
+ boolean wipeData = false;
+ ActiveAdmin strictestAdmin = null;
final long ident = mInjector.binderClearCallingIdentity();
try {
- boolean wipeData = false;
- int identifier = 0;
synchronized (this) {
DevicePolicyData policy = getUserData(userHandle);
policy.mFailedPasswordAttempts++;
saveSettingsLocked(userHandle);
if (mHasFeature) {
- ActiveAdmin strictestAdmin = getAdminWithMinimumFailedPasswordsForWipeLocked(
+ strictestAdmin = getAdminWithMinimumFailedPasswordsForWipeLocked(
userHandle, /* parent */ false);
int max = strictestAdmin != null
? strictestAdmin.maximumFailedPasswordsForWipe : 0;
if (max > 0 && policy.mFailedPasswordAttempts >= max) {
- // Wipe the user/profile associated with the policy that was violated. This
- // is not necessarily calling user: if the policy that fired was from a
- // managed profile rather than the main user profile, we wipe former only.
wipeData = true;
- identifier = strictestAdmin.getUserHandle().getIdentifier();
}
sendAdminCommandForLockscreenPoliciesLocked(
@@ -5131,14 +5394,33 @@
DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, userHandle);
}
}
- if (wipeData) {
- // Call without holding lock.
- wipeDeviceNoLock(false, identifier, "reportFailedPasswordAttempt()", false);
- }
} finally {
mInjector.binderRestoreCallingIdentity(ident);
}
+ if (wipeData && strictestAdmin != null) {
+ final int userId = strictestAdmin.getUserHandle().getIdentifier();
+ Slog.i(LOG_TAG, "Max failed password attempts policy reached for admin: "
+ + strictestAdmin.info.getComponent().flattenToShortString()
+ + ". Calling wipeData for user " + userId);
+
+ // Attempt to wipe the device/user/profile associated with the admin, as if the
+ // admin had called wipeData(). That way we can check whether the admin is actually
+ // allowed to wipe the device (e.g. a regular device admin shouldn't be able to wipe the
+ // device if the device owner has set DISALLOW_FACTORY_RESET, but the DO should be
+ // able to do so).
+ // IMPORTANT: Call without holding the lock to prevent deadlock.
+ try {
+ wipeDataNoLock(strictestAdmin.info.getComponent(),
+ /*flags=*/ 0,
+ /*reason=*/ "reportFailedPasswordAttempt()",
+ userId);
+ } catch (SecurityException e) {
+ Slog.w(LOG_TAG, "Failed to wipe user " + userId
+ + " after max failed password attempts reached.", e);
+ }
+ }
+
if (mInjector.securityLogIsLoggingEnabled()) {
SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 0,
/*method strength*/ 1);
@@ -6110,7 +6392,8 @@
try {
// TODO Send to system too?
mContext.sendBroadcastAsUser(
- new Intent(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED),
+ new Intent(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND),
UserHandle.of(userId));
} finally {
mInjector.binderRestoreCallingIdentity(ident);
@@ -6146,6 +6429,13 @@
}
}
+ private boolean isProfileOwnerPackage(String packageName, int userId) {
+ synchronized (this) {
+ return mOwners.hasProfileOwner(userId)
+ && mOwners.getProfileOwnerPackage(userId).equals(packageName);
+ }
+ }
+
public boolean isProfileOwner(ComponentName who, int userId) {
final ComponentName profileOwner = getProfileOwner(userId);
return who != null && who.equals(profileOwner);
@@ -6253,6 +6543,7 @@
clearDeviceOwnerLocked(admin, deviceOwnerUserId);
removeActiveAdminLocked(deviceOwnerComponent, deviceOwnerUserId);
Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED);
+ intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mContext.sendBroadcastAsUser(intent, UserHandle.of(deviceOwnerUserId));
} finally {
mInjector.binderRestoreCallingIdentity(ident);
@@ -6387,11 +6678,11 @@
}
private void clearUserPoliciesLocked(int userId) {
- // Reset some of the user-specific policies
- DevicePolicyData policy = getUserData(userId);
+ // Reset some of the user-specific policies.
+ final DevicePolicyData policy = getUserData(userId);
policy.mPermissionPolicy = DevicePolicyManager.PERMISSION_POLICY_PROMPT;
- policy.mDelegatedCertInstallerPackage = null;
- policy.mApplicationRestrictionsManagingPackage = null;
+ // Clear delegations.
+ policy.mDelegationMap.clear();
policy.mStatusBarDisabled = false;
policy.mUserProvisioningState = DevicePolicyManager.STATE_USER_UNMANAGED;
saveSettingsLocked(userId);
@@ -6975,66 +7266,31 @@
@Override
public boolean setApplicationRestrictionsManagingPackage(ComponentName admin,
String packageName) {
- Preconditions.checkNotNull(admin, "ComponentName is null");
-
- final int userHandle = mInjector.userHandleGetCallingUserId();
- synchronized (this) {
- getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- if (packageName != null && !isPackageInstalledForUser(packageName, userHandle)) {
- return false;
- }
- DevicePolicyData policy = getUserData(userHandle);
- policy.mApplicationRestrictionsManagingPackage = packageName;
- saveSettingsLocked(userHandle);
- return true;
+ try {
+ setDelegatedScopePreO(admin, packageName, DELEGATION_APP_RESTRICTIONS);
+ } catch (IllegalArgumentException e) {
+ return false;
}
+ return true;
}
@Override
public String getApplicationRestrictionsManagingPackage(ComponentName admin) {
- Preconditions.checkNotNull(admin, "ComponentName is null");
-
- final int userHandle = mInjector.userHandleGetCallingUserId();
- synchronized (this) {
- getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
- DevicePolicyData policy = getUserData(userHandle);
- return policy.mApplicationRestrictionsManagingPackage;
- }
+ final List<String> delegatePackages = getDelegatePackages(admin,
+ DELEGATION_APP_RESTRICTIONS);
+ return delegatePackages.size() > 0 ? delegatePackages.get(0) : null;
}
@Override
- public boolean isCallerApplicationRestrictionsManagingPackage() {
- final int callingUid = mInjector.binderGetCallingUid();
- final int userHandle = UserHandle.getUserId(callingUid);
- synchronized (this) {
- final DevicePolicyData policy = getUserData(userHandle);
- if (policy.mApplicationRestrictionsManagingPackage == null) {
- return false;
- }
-
- try {
- int uid = mInjector.getPackageManager().getPackageUidAsUser(
- policy.mApplicationRestrictionsManagingPackage, userHandle);
- return uid == callingUid;
- } catch (NameNotFoundException e) {
- return false;
- }
- }
- }
-
- private void enforceCanManageApplicationRestrictions(ComponentName who) {
- if (who != null) {
- enforceProfileOrDeviceOwner(who);
- } else if (!isCallerApplicationRestrictionsManagingPackage()) {
- throw new SecurityException(
- "No admin component given, and caller cannot manage application restrictions "
- + "for other apps.");
- }
+ public boolean isCallerApplicationRestrictionsManagingPackage(String callerPackage) {
+ return isCallerDelegate(callerPackage, DELEGATION_APP_RESTRICTIONS);
}
@Override
- public void setApplicationRestrictions(ComponentName who, String packageName, Bundle settings) {
- enforceCanManageApplicationRestrictions(who);
+ public void setApplicationRestrictions(ComponentName who, String callerPackage,
+ String packageName, Bundle settings) {
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+ DELEGATION_APP_RESTRICTIONS);
final UserHandle userHandle = mInjector.binderGetCallingUserHandle();
final long id = mInjector.binderClearCallingIdentity();
@@ -7724,8 +7980,10 @@
}
@Override
- public Bundle getApplicationRestrictions(ComponentName who, String packageName) {
- enforceCanManageApplicationRestrictions(who);
+ public Bundle getApplicationRestrictions(ComponentName who, String callerPackage,
+ String packageName) {
+ enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+ DELEGATION_APP_RESTRICTIONS);
final UserHandle userHandle = mInjector.binderGetCallingUserHandle();
final long id = mInjector.binderClearCallingIdentity();
@@ -9119,21 +9377,25 @@
final long ident = mInjector.binderClearCallingIdentity();
try {
final UserHandle callingUserHandle = UserHandle.of(callingUserId);
- if (mUserManager.hasUserRestriction(
- UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserHandle)) {
- // The DO can initiate provisioning if the restriction was set by the DO.
- if (!isDeviceOwnerPackage(packageName, callingUserId)
- || isAdminAffectedByRestriction(mOwners.getDeviceOwnerComponent(),
- UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserId)) {
- // Caller is not DO or the restriction was set by the system.
+ final ComponentName ownerAdmin = getOwnerComponent(packageName, callingUserId);
+ if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE,
+ callingUserHandle)) {
+ // An admin can initiate provisioning if it has set the restriction.
+ if (ownerAdmin == null || isAdminAffectedByRestriction(ownerAdmin,
+ UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserId)) {
return CODE_ADD_MANAGED_PROFILE_DISALLOWED;
}
}
-
- // TODO: Allow it if the caller is the DO? DO could just call removeUser() before
- // provisioning, so not strictly required...
- boolean canRemoveProfile = !mUserManager.hasUserRestriction(
- UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, callingUserHandle);
+ boolean canRemoveProfile = true;
+ if (mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
+ callingUserHandle)) {
+ // We can remove a profile if the admin itself has set the restriction.
+ if (ownerAdmin == null || isAdminAffectedByRestriction(ownerAdmin,
+ UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
+ callingUserId)) {
+ canRemoveProfile = false;
+ }
+ }
if (!mUserManager.canAddMoreManagedProfiles(callingUserId, canRemoveProfile)) {
return CODE_CANNOT_ADD_MANAGED_PROFILE;
}
@@ -9143,6 +9405,16 @@
return CODE_OK;
}
+ private ComponentName getOwnerComponent(String packageName, int userId) {
+ if (isDeviceOwnerPackage(packageName, userId)) {
+ return mOwners.getDeviceOwnerComponent();
+ }
+ if (isProfileOwnerPackage(packageName, userId)) {
+ return mOwners.getProfileOwnerComponent(userId);
+ }
+ return null;
+ }
+
private int checkManagedUserProvisioningPreCondition(int callingUserId) {
if (!hasFeatureManagedUsers()) {
return CODE_MANAGED_USERS_NOT_SUPPORTED;
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/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 4927f0c..3b92a34 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -37,6 +37,7 @@
import com.android.internal.widget.LockPatternUtils;
import java.io.File;
+import java.io.IOException;
import java.util.Map;
/**
@@ -264,6 +265,12 @@
}
@Override
+ void recoverySystemRebootWipeUserData(boolean shutdown, String reason, boolean force)
+ throws IOException {
+ context.recoverySystem.rebootWipeUserData(shutdown, reason, force);
+ }
+
+ @Override
boolean systemPropertiesGetBoolean(String key, boolean def) {
return context.systemProperties.getBoolean(key, def);
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 8da47c8..5d4c3cf 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -68,6 +68,9 @@
import java.util.Set;
import java.util.concurrent.TimeUnit;
+import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
+import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
+
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
@@ -82,6 +85,7 @@
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
/**
@@ -1128,6 +1132,7 @@
public void testSetGetApplicationRestriction() {
setAsProfileOwner(admin1);
+ mContext.packageName = admin1.getPackageName();
{
Bundle rest = new Bundle();
@@ -1159,29 +1164,131 @@
assertEquals(0, dpm.getApplicationRestrictions(admin1, "pkg2").size());
}
+ /**
+ * Setup a package in the package manager mock. Useful for faking installed applications.
+ *
+ * @param packageName the name of the package to be setup
+ * @param appId the application ID to be given to the package
+ * @return the UID of the package as known by the mock package manager
+ */
+ private int setupPackageInPackageManager(final String packageName, final int appId)
+ throws Exception {
+ // Make the PackageManager return the package instead of throwing a NameNotFoundException
+ final PackageInfo pi = new PackageInfo();
+ pi.applicationInfo = new ApplicationInfo();
+ pi.applicationInfo.flags = ApplicationInfo.FLAG_HAS_CODE;
+ doReturn(pi).when(mContext.ipackageManager).getPackageInfo(
+ eq(packageName),
+ anyInt(),
+ eq(DpmMockContext.CALLER_USER_HANDLE));
+ // Setup application UID with the PackageManager
+ final int uid = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, appId);
+ doReturn(uid).when(mContext.packageManager).getPackageUidAsUser(
+ eq(packageName),
+ eq(DpmMockContext.CALLER_USER_HANDLE));
+ // Associate packageName to uid
+ doReturn(packageName).when(mContext.ipackageManager).getNameForUid(eq(uid));
+ doReturn(new String[]{packageName})
+ .when(mContext.ipackageManager).getPackagesForUid(eq(uid));
+ return uid;
+ }
+
+ /**
+ * Simple test for delegate set/get and general delegation. Tests verifying that delegated
+ * privileges can acually be exercised by a delegate are not covered here.
+ */
+ public void testDelegation() throws Exception {
+ setAsProfileOwner(admin1);
+
+ final int userHandle = DpmMockContext.CALLER_USER_HANDLE;
+
+ // Given two packages
+ final String CERT_DELEGATE = "com.delegate.certs";
+ final String RESTRICTIONS_DELEGATE = "com.delegate.apprestrictions";
+ final int CERT_DELEGATE_UID = setupPackageInPackageManager(CERT_DELEGATE, 20988);
+ final int RESTRICTIONS_DELEGATE_UID = setupPackageInPackageManager(RESTRICTIONS_DELEGATE,
+ 20989);
+
+ // On delegation
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ mContext.packageName = admin1.getPackageName();
+ dpm.setCertInstallerPackage(admin1, CERT_DELEGATE);
+ dpm.setApplicationRestrictionsManagingPackage(admin1, RESTRICTIONS_DELEGATE);
+
+ // DPMS correctly stores and retrieves the delegates
+ DevicePolicyManagerService.DevicePolicyData policy = dpms.mUserData.get(userHandle);
+ assertEquals(2, policy.mDelegationMap.size());
+ MoreAsserts.assertContentsInAnyOrder(policy.mDelegationMap.get(CERT_DELEGATE),
+ DELEGATION_CERT_INSTALL);
+ MoreAsserts.assertContentsInAnyOrder(dpm.getDelegatedScopes(admin1, CERT_DELEGATE),
+ DELEGATION_CERT_INSTALL);
+ assertEquals(CERT_DELEGATE, dpm.getCertInstallerPackage(admin1));
+ MoreAsserts.assertContentsInAnyOrder(policy.mDelegationMap.get(RESTRICTIONS_DELEGATE),
+ DELEGATION_APP_RESTRICTIONS);
+ MoreAsserts.assertContentsInAnyOrder(dpm.getDelegatedScopes(admin1, RESTRICTIONS_DELEGATE),
+ DELEGATION_APP_RESTRICTIONS);
+ assertEquals(RESTRICTIONS_DELEGATE, dpm.getApplicationRestrictionsManagingPackage(admin1));
+
+ // On calling install certificate APIs from an unauthorized process
+ mContext.binder.callingUid = RESTRICTIONS_DELEGATE_UID;
+ mContext.packageName = RESTRICTIONS_DELEGATE;
+
+ // DPMS throws a SecurityException
+ try {
+ dpm.installCaCert(null, null);
+ fail("Didn't throw SecurityException on unauthorized access");
+ } catch (SecurityException expected) {
+ }
+
+ // On calling install certificate APIs from an authorized process
+ mContext.binder.callingUid = CERT_DELEGATE_UID;
+ mContext.packageName = CERT_DELEGATE;
+
+ // DPMS executes without a SecurityException
+ try {
+ dpm.installCaCert(null, null);
+ } catch (SecurityException unexpected) {
+ fail("Threw SecurityException on authorized access");
+ } catch (NullPointerException expected) {
+ }
+
+ // On removing a delegate
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ mContext.packageName = admin1.getPackageName();
+ dpm.setCertInstallerPackage(admin1, null);
+
+ // DPMS does not allow access to ex-delegate
+ mContext.binder.callingUid = CERT_DELEGATE_UID;
+ mContext.packageName = CERT_DELEGATE;
+ try {
+ dpm.installCaCert(null, null);
+ fail("Didn't throw SecurityException on unauthorized access");
+ } catch (SecurityException expected) {
+ }
+
+ // But still allows access to other existing delegates
+ mContext.binder.callingUid = RESTRICTIONS_DELEGATE_UID;
+ mContext.packageName = RESTRICTIONS_DELEGATE;
+ try {
+ dpm.getApplicationRestrictions(null, "pkg");
+ } catch (SecurityException expected) {
+ fail("Threw SecurityException on authorized access");
+ }
+ }
+
public void testApplicationRestrictionsManagingApp() throws Exception {
setAsProfileOwner(admin1);
final String nonExistAppRestrictionsManagerPackage = "com.google.app.restrictions.manager2";
final String appRestrictionsManagerPackage = "com.google.app.restrictions.manager";
final int appRestrictionsManagerAppId = 20987;
- final int appRestrictionsManagerUid = UserHandle.getUid(
- DpmMockContext.CALLER_USER_HANDLE, appRestrictionsManagerAppId);
- doReturn(appRestrictionsManagerUid).when(mContext.packageManager).getPackageUidAsUser(
- eq(appRestrictionsManagerPackage),
- eq(DpmMockContext.CALLER_USER_HANDLE));
- mContext.binder.callingUid = appRestrictionsManagerUid;
-
- final PackageInfo pi = new PackageInfo();
- pi.applicationInfo = new ApplicationInfo();
- pi.applicationInfo.flags = ApplicationInfo.FLAG_HAS_CODE;
- doReturn(pi).when(mContext.ipackageManager).getPackageInfo(
- eq(appRestrictionsManagerPackage),
- anyInt(),
- eq(DpmMockContext.CALLER_USER_HANDLE));
+ final int appRestrictionsManagerUid = setupPackageInPackageManager(
+ appRestrictionsManagerPackage, appRestrictionsManagerAppId);
// appRestrictionsManager package shouldn't be able to manage restrictions as the PO hasn't
// delegated that permission yet.
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ mContext.packageName = admin1.getPackageName();
assertFalse(dpm.isCallerApplicationRestrictionsManagingPackage());
Bundle rest = new Bundle();
rest.putString("KEY_STRING", "Foo1");
@@ -1190,18 +1297,21 @@
fail("Didn't throw expected SecurityException");
} catch (SecurityException expected) {
MoreAsserts.assertContainsRegex(
- "caller cannot manage application restrictions", expected.getMessage());
+ "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.",
+ expected.getMessage());
}
try {
dpm.getApplicationRestrictions(null, "pkg1");
fail("Didn't throw expected SecurityException");
} catch (SecurityException expected) {
MoreAsserts.assertContainsRegex(
- "caller cannot manage application restrictions", expected.getMessage());
+ "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.",
+ expected.getMessage());
}
// Check via the profile owner that no restrictions were set.
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ mContext.packageName = admin1.getPackageName();
assertEquals(0, dpm.getApplicationRestrictions(admin1, "pkg1").size());
// Check the API does not allow setting a non-existent package
@@ -1221,6 +1331,7 @@
// Now that package should be able to set and retrieve app restrictions.
mContext.binder.callingUid = appRestrictionsManagerUid;
+ mContext.packageName = appRestrictionsManagerPackage;
assertTrue(dpm.isCallerApplicationRestrictionsManagingPackage());
dpm.setApplicationRestrictions(null, "pkg1", rest);
Bundle returned = dpm.getApplicationRestrictions(null, "pkg1");
@@ -1236,12 +1347,14 @@
fail("Didn't throw expected SecurityException");
} catch (SecurityException expected) {
MoreAsserts.assertContainsRegex(
- "caller cannot manage application restrictions", expected.getMessage());
+ "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.",
+ expected.getMessage());
}
// The DPM is still able to manage app restrictions, even if it allowed another app to do it
// too.
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ mContext.packageName = admin1.getPackageName();
assertEquals(returned, dpm.getApplicationRestrictions(admin1, "pkg1"));
dpm.setApplicationRestrictions(admin1, "pkg1", null);
assertEquals(0, dpm.getApplicationRestrictions(admin1, "pkg1").size());
@@ -1250,13 +1363,15 @@
dpm.setApplicationRestrictionsManagingPackage(admin1, null);
assertNull(dpm.getApplicationRestrictionsManagingPackage(admin1));
mContext.binder.callingUid = appRestrictionsManagerUid;
+ mContext.packageName = appRestrictionsManagerPackage;
assertFalse(dpm.isCallerApplicationRestrictionsManagingPackage());
try {
dpm.setApplicationRestrictions(null, "pkg1", null);
fail("Didn't throw expected SecurityException");
} catch (SecurityException expected) {
MoreAsserts.assertContainsRegex(
- "caller cannot manage application restrictions", expected.getMessage());
+ "Caller with uid \\d+ is not a delegate of scope delegation-app-restrictions.",
+ expected.getMessage());
}
}
@@ -2358,6 +2473,23 @@
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
}
+ private void setup_nonSplitUser_withDo_primaryUser() throws Exception {
+ setDeviceOwner();
+ setup_nonSplitUser_afterDeviceSetup_primaryUser();
+ setUpPackageManagerForFakeAdmin(adminAnotherPackage, DpmMockContext.ANOTHER_UID, admin2);
+ }
+
+ private void setup_nonSplitUser_withDo_primaryUser_ManagedProfile() throws Exception {
+ setup_nonSplitUser_withDo_primaryUser();
+ final int MANAGED_PROFILE_USER_ID = 18;
+ final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 1308);
+ addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+ when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM,
+ false /* we can't remove a managed profile */)).thenReturn(false);
+ when(mContext.userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM,
+ true)).thenReturn(true);
+ }
+
public void testIsProvisioningAllowed_nonSplitUser_afterDeviceSetup_primaryUser()
throws Exception {
setup_nonSplitUser_afterDeviceSetup_primaryUser();
@@ -2387,144 +2519,124 @@
DevicePolicyManager.CODE_NOT_SYSTEM_USER_SPLIT);
}
- public void testIsProvisioningAllowed_nonSplitUser_withDo_primaryUser() throws Exception {
- setDeviceOwner();
- setup_nonSplitUser_afterDeviceSetup_primaryUser();
- setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
+ public void testProvisioning_nonSplitUser_withDo_primaryUser() throws Exception {
+ setup_nonSplitUser_withDo_primaryUser();
mContext.packageName = admin1.getPackageName();
-
- final ComponentName adminDifferentPackage =
- new ComponentName("another.package", "whatever.random.class");
- final int ANOTHER_UID = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, 948);
- setUpPackageManagerForFakeAdmin(adminDifferentPackage, ANOTHER_UID, admin2);
-
- // COMP mode is allowed.
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
-
- when(mContext.userManager.hasUserRestriction(
- eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
- eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
- .thenReturn(true);
-
- // The DO should be allowed to initiate provisioning if it set the restriction itself.
- when(mContext.userManager.getUserRestrictionSource(
- eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
- eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
- .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
-
- // But another app should not
- mContext.binder.callingUid = ANOTHER_UID;
- mContext.packageName = adminDifferentPackage.getPackageName();
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
-
- // The DO should not be allowed to initiate provisioning if the restriction is set by
- // another entity.
- when(mContext.userManager.getUserRestrictionSource(
- eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
- eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
- .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
- mContext.binder.callingUid = DpmMockContext.CALLER_UID;
- mContext.packageName = admin1.getPackageName();
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
-
- mContext.binder.callingUid = ANOTHER_UID;
- mContext.packageName = adminDifferentPackage.getPackageName();
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
- }
-
- public void testIsProvisioningAllowed_nonSplitUser_comp() throws Exception {
- setDeviceOwner();
- setup_nonSplitUser_afterDeviceSetup_primaryUser();
- setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID);
-
- final ComponentName adminDifferentPackage =
- new ComponentName("another.package", "whatever.class");
- final int ANOTHER_UID = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, 948);
- setUpPackageManagerForFakeAdmin(adminDifferentPackage, ANOTHER_UID, admin2);
-
- final int MANAGED_PROFILE_USER_ID = 18;
- final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 1308);
- addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
-
- when(mContext.userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE,
- false /* we can't remove a managed profile */)).thenReturn(false);
- when(mContext.userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE,
- true)).thenReturn(true);
-
- // We can delete the managed profile to create a new one, so provisioning is allowed.
- mContext.packageName = admin1.getPackageName();
- mContext.binder.callingUid = DpmMockContext.CALLER_UID;
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
-
- mContext.packageName = adminDifferentPackage.getPackageName();
- mContext.binder.callingUid = ANOTHER_UID;
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
-
- when(mContext.userManager.hasUserRestriction(
- eq(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE),
- eq(UserHandle.of(DpmMockContext.CALLER_USER_HANDLE))))
- .thenReturn(true);
-
- // Now, we can't remove the profile any more to create a new one.
- mContext.packageName = admin1.getPackageName();
- mContext.binder.callingUid = DpmMockContext.CALLER_UID;
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
-
- mContext.packageName = adminDifferentPackage.getPackageName();
- mContext.binder.callingUid = ANOTHER_UID;
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
- }
-
- public void
- testCheckProvisioningPreCondition_nonSplitUser_withDo_primaryUser() throws Exception {
- setDeviceOwner();
- setup_nonSplitUser_afterDeviceSetup_primaryUser();
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_HAS_DEVICE_OWNER);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false);
// COMP mode is allowed.
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_OK);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
- // And other DPCs can also provisioning a managed profile (DO + BYOD case).
+ // And other DPCs can also provision a managed profile (DO + BYOD case).
assertCheckProvisioningPreCondition(
DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
- "some.other.dpc.package.name",
+ DpmMockContext.ANOTHER_PACKAGE_NAME,
DevicePolicyManager.CODE_OK);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true,
+ DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
+ }
+ public void testProvisioning_nonSplitUser_withDo_primaryUser_restrictedByDo() throws Exception {
+ setup_nonSplitUser_withDo_primaryUser();
+ mContext.packageName = admin1.getPackageName();
+ mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+ // The DO should be allowed to initiate provisioning if it set the restriction itself, but
+ // other packages should be forbidden.
when(mContext.userManager.hasUserRestriction(
eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
.thenReturn(true);
-
- // The DO should be allowed to initiate provisioning if it set the restriction itself, but
- // other packages should be forbidden.
when(mContext.userManager.getUserRestrictionSource(
eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
.thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_OK);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
assertCheckProvisioningPreCondition(
DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
- "some.other.dpc.package.name",
+ DpmMockContext.ANOTHER_PACKAGE_NAME,
DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false,
+ DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
+ }
+ public void testProvisioning_nonSplitUser_withDo_primaryUser_restrictedBySystem()
+ throws Exception {
+ setup_nonSplitUser_withDo_primaryUser();
+ mContext.packageName = admin1.getPackageName();
+ mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
// The DO should not be allowed to initiate provisioning if the restriction is set by
// another entity.
+ when(mContext.userManager.hasUserRestriction(
+ eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
+ eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
+ .thenReturn(true);
when(mContext.userManager.getUserRestrictionSource(
eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
.thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED);
- assertCheckProvisioningPreCondition(
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
+
+ assertCheckProvisioningPreCondition(
DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
- "some.other.dpc.package.name",
+ DpmMockContext.ANOTHER_PACKAGE_NAME,
DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false,
+ DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
+ }
+
+ public void testCheckProvisioningPreCondition_nonSplitUser_comp() throws Exception {
+ setup_nonSplitUser_withDo_primaryUser_ManagedProfile();
+ mContext.packageName = admin1.getPackageName();
+ mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+
+ // We can delete the managed profile to create a new one, so provisioning is allowed.
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ DevicePolicyManager.CODE_OK);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
+ assertCheckProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ DpmMockContext.ANOTHER_PACKAGE_NAME,
+ DevicePolicyManager.CODE_OK);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true,
+ DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
+ }
+
+ public void testCheckProvisioningPreCondition_nonSplitUser_comp_cannot_remove_profile()
+ throws Exception {
+ setup_nonSplitUser_withDo_primaryUser_ManagedProfile();
+ mContext.packageName = admin1.getPackageName();
+ mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
+ when(mContext.userManager.hasUserRestriction(
+ eq(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE),
+ eq(UserHandle.SYSTEM)))
+ .thenReturn(true);
+ when(mContext.userManager.getUserRestrictionSource(
+ eq(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE),
+ eq(UserHandle.SYSTEM)))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+
+ // We can't remove the profile to create a new one.
+ assertCheckProvisioningPreCondition(
+ DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ DpmMockContext.ANOTHER_PACKAGE_NAME,
+ DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false,
+ DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
+
+ // But the device owner can still do it because it has set the restriction itself.
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+ DevicePolicyManager.CODE_OK);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
}
private void setup_splitUser_firstBoot_systemUser() throws Exception {
@@ -3233,6 +3345,140 @@
}
}
+ public void testWipeDataDeviceOwner() throws Exception {
+ setDeviceOwner();
+ when(mContext.userManager.getUserRestrictionSource(
+ UserManager.DISALLOW_FACTORY_RESET,
+ UserHandle.SYSTEM))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+
+ dpm.wipeData(0);
+ verify(mContext.recoverySystem).rebootWipeUserData(
+ /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true));
+ }
+
+ public void testWipeDataDeviceOwnerDisallowed() throws Exception {
+ setDeviceOwner();
+ when(mContext.userManager.getUserRestrictionSource(
+ UserManager.DISALLOW_FACTORY_RESET,
+ UserHandle.SYSTEM))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
+ try {
+ // The DO is not allowed to wipe the device if the user restriction was set
+ // by the system
+ dpm.wipeData(0);
+ fail("SecurityException not thrown");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ public void testMaximumFailedPasswordAttemptsReachedManagedProfile() throws Exception {
+ final int MANAGED_PROFILE_USER_ID = 15;
+ final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436);
+ addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+
+ // Even if the caller is the managed profile, the current user is the user 0
+ when(mContext.iactivityManager.getCurrentUser())
+ .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
+
+ when(mContext.userManager.getUserRestrictionSource(
+ UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
+ UserHandle.of(MANAGED_PROFILE_USER_ID)))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_PROFILE_OWNER);
+
+ mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+ dpm.setMaximumFailedPasswordsForWipe(admin1, 3);
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN);
+ // Failed password attempts on the parent user are taken into account, as there isn't a
+ // separate work challenge.
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+
+ // The profile should be wiped even if DISALLOW_REMOVE_MANAGED_PROFILE is enabled, because
+ // both the user restriction and the policy were set by the PO.
+ verify(mContext.userManagerInternal).removeUserEvenWhenDisallowed(
+ MANAGED_PROFILE_USER_ID);
+ verifyZeroInteractions(mContext.recoverySystem);
+ }
+
+ public void testMaximumFailedPasswordAttemptsReachedManagedProfileDisallowed()
+ throws Exception {
+ final int MANAGED_PROFILE_USER_ID = 15;
+ final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436);
+ addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+
+ // Even if the caller is the managed profile, the current user is the user 0
+ when(mContext.iactivityManager.getCurrentUser())
+ .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
+
+ when(mContext.userManager.getUserRestrictionSource(
+ UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
+ UserHandle.of(MANAGED_PROFILE_USER_ID)))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
+
+ mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+ dpm.setMaximumFailedPasswordsForWipe(admin1, 3);
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN);
+ // Failed password attempts on the parent user are taken into account, as there isn't a
+ // separate work challenge.
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+
+ // DISALLOW_REMOVE_MANAGED_PROFILE was set by the system, not the PO, so the profile is
+ // not wiped.
+ verify(mContext.userManagerInternal, never())
+ .removeUserEvenWhenDisallowed(anyInt());
+ verifyZeroInteractions(mContext.recoverySystem);
+ }
+
+ public void testMaximumFailedPasswordAttemptsReachedDeviceOwner() throws Exception {
+ setDeviceOwner();
+ when(mContext.userManager.getUserRestrictionSource(
+ UserManager.DISALLOW_FACTORY_RESET,
+ UserHandle.SYSTEM))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+
+ dpm.setMaximumFailedPasswordsForWipe(admin1, 3);
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN);
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+
+ // The device should be wiped even if DISALLOW_FACTORY_RESET is enabled, because both the
+ // user restriction and the policy were set by the DO.
+ verify(mContext.recoverySystem).rebootWipeUserData(
+ /*shutdown=*/ eq(false), anyString(), /*force=*/ eq(true));
+ }
+
+ public void testMaximumFailedPasswordAttemptsReachedDeviceOwnerDisallowed() throws Exception {
+ setDeviceOwner();
+ when(mContext.userManager.getUserRestrictionSource(
+ UserManager.DISALLOW_FACTORY_RESET,
+ UserHandle.SYSTEM))
+ .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
+
+ dpm.setMaximumFailedPasswordsForWipe(admin1, 3);
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ mContext.callerPermissions.add(permission.BIND_DEVICE_ADMIN);
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+ dpm.reportFailedPasswordAttempt(UserHandle.USER_SYSTEM);
+
+ // DISALLOW_FACTORY_RESET was set by the system, not the DO, so the device is not wiped.
+ verifyZeroInteractions(mContext.recoverySystem);
+ verify(mContext.userManagerInternal, never())
+ .removeUserEvenWhenDisallowed(anyInt());
+ }
+
public void testGetPermissionGrantState() throws Exception {
final String permission = "some.permission";
final String app1 = "com.example.app1";
@@ -3287,6 +3533,21 @@
dpm.isProvisioningAllowed(action));
}
+ private void assertProvisioningAllowed(String action, boolean expected, String packageName,
+ int uid) {
+ String previousPackageName = mContext.packageName;
+ int previousUid = mMockContext.binder.callingUid;
+
+ // Call assertProvisioningAllowed with the packageName / uid passed as arguments.
+ mContext.packageName = packageName;
+ mMockContext.binder.callingUid = uid;
+ assertProvisioningAllowed(action, expected);
+
+ // Set the previous package name / calling uid to go back to the initial state.
+ mContext.packageName = previousPackageName;
+ mMockContext.binder.callingUid = previousUid;
+ }
+
private void assertCheckProvisioningPreCondition(String action, int provisioningCondition) {
assertCheckProvisioningPreCondition(action, admin1.getPackageName(), provisioningCondition);
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 44bf547..22cd135 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -55,6 +55,7 @@
import org.mockito.stubbing.Answer;
import java.io.File;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -99,6 +100,10 @@
*/
public static final int SYSTEM_PID = 11111;
+ public static final String ANOTHER_PACKAGE_NAME = "com.another.package.name";
+
+ public static final int ANOTHER_UID = UserHandle.getUid(UserHandle.USER_SYSTEM, 18434);
+
public static class MockBinder {
public int callingUid = CALLER_UID;
public int callingPid = CALLER_PID;
@@ -154,6 +159,12 @@
}
}
+ public static class RecoverySystemForMock {
+ public void rebootWipeUserData(
+ boolean shutdown, String reason, boolean force) throws IOException {
+ }
+ }
+
public static class SystemPropertiesForMock {
public boolean getBoolean(String key, boolean def) {
return false;
@@ -263,6 +274,7 @@
public final UserManagerForMock userManagerForMock;
public final PowerManagerForMock powerManager;
public final PowerManagerInternal powerManagerInternal;
+ public final RecoverySystemForMock recoverySystem;
public final NotificationManager notificationManager;
public final IIpConnectivityMetrics iipConnectivityMetrics;
public final IWindowManager iwindowManager;
@@ -308,6 +320,7 @@
packageManagerInternal = mock(PackageManagerInternal.class);
powerManager = mock(PowerManagerForMock.class);
powerManagerInternal = mock(PowerManagerInternal.class);
+ recoverySystem = mock(RecoverySystemForMock.class);
notificationManager = mock(NotificationManager.class);
iipConnectivityMetrics = mock(IIpConnectivityMetrics.class);
iwindowManager = mock(IWindowManager.class);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
index 8a11976..ed6779c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
@@ -45,6 +45,7 @@
public ComponentName admin1;
public ComponentName admin2;
public ComponentName admin3;
+ public ComponentName adminAnotherPackage;
public ComponentName adminNoPerm;
@Override
@@ -59,6 +60,8 @@
admin1 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin1.class);
admin2 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin2.class);
admin3 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin3.class);
+ adminAnotherPackage = new ComponentName(DpmMockContext.ANOTHER_PACKAGE_NAME,
+ "whatever.random.class");
adminNoPerm = new ComponentName(mRealTestContext, DummyDeviceAdmins.AdminNoPerm.class);
}
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index da27ea9..56aad23 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -424,6 +424,17 @@
mNM.notify("secret", 7012, n);
}
},
+ new Test("1 minute timeout") {
+ public void run()
+ {
+ Notification n = new Notification.Builder(NotificationTestList.this)
+ .setSmallIcon(R.drawable.icon2)
+ .setContentTitle("timeout in a minute")
+ .setTimeout(System.currentTimeMillis() + (1000 * 60))
+ .build();
+ mNM.notify("timeout_min", 7013, n);
+ }
+ },
new Test("Off") {
public void run() {
PowerManager pm = (PowerManager)NotificationTestList.this.getSystemService(Context.POWER_SERVICE);
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/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
index d0c9599..147ed99 100644
--- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
@@ -249,14 +249,17 @@
// ---- delegate methods ----
@LayoutlibDelegate
/*package*/ static boolean addFont(FontFamily thisFontFamily, String path, int ttcIndex) {
- final FontFamily_Delegate delegate = getDelegate(thisFontFamily.mNativePtr);
+ if (thisFontFamily.mBuilderPtr == 0) {
+ throw new IllegalStateException("Unable to call addFont after freezing.");
+ }
+ final FontFamily_Delegate delegate = getDelegate(thisFontFamily.mBuilderPtr);
return delegate != null && delegate.addFont(path, ttcIndex);
}
// ---- native methods ----
@LayoutlibDelegate
- /*package*/ static long nCreateFamily(String lang, int variant) {
+ /*package*/ static long nInitBuilder(String lang, int variant) {
// TODO: support lang. This is required for japanese locale.
FontFamily_Delegate delegate = new FontFamily_Delegate();
// variant can be 0, 1 or 2.
@@ -271,6 +274,11 @@
}
@LayoutlibDelegate
+ /*package*/ static long nCreateFamily(long builderPtr) {
+ return builderPtr;
+ }
+
+ @LayoutlibDelegate
/*package*/ static void nUnrefFamily(long nativePtr) {
// Removing the java reference for the object doesn't mean that it's freed for garbage
// collection. Typeface_Delegate may still hold a reference for it.
@@ -278,22 +286,22 @@
}
@LayoutlibDelegate
- /*package*/ static boolean nAddFont(long nativeFamily, ByteBuffer font, int ttcIndex) {
+ /*package*/ static boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex) {
assert false : "The only client of this method has been overriden.";
return false;
}
@LayoutlibDelegate
- /*package*/ static boolean nAddFontWeightStyle(long nativeFamily, ByteBuffer font,
+ /*package*/ static boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font,
int ttcIndex, List<FontConfig.Axis> listOfAxis,
int weight, boolean isItalic) {
assert false : "The only client of this method has been overriden.";
return false;
}
- static boolean addFont(long nativeFamily, final String path, final int weight,
+ static boolean addFont(long builderPtr, final String path, final int weight,
final boolean isItalic) {
- final FontFamily_Delegate delegate = getDelegate(nativeFamily);
+ final FontFamily_Delegate delegate = getDelegate(builderPtr);
if (delegate != null) {
if (sFontLocation == null) {
delegate.mPostInitRunnables.add(() -> delegate.addFont(path, weight, isItalic));
@@ -305,8 +313,8 @@
}
@LayoutlibDelegate
- /*package*/ static boolean nAddFontFromAsset(long nativeFamily, AssetManager mgr, String path) {
- FontFamily_Delegate ffd = sManager.getDelegate(nativeFamily);
+ /*package*/ static boolean nAddFontFromAsset(long builderPtr, AssetManager mgr, String path) {
+ FontFamily_Delegate ffd = sManager.getDelegate(builderPtr);
if (ffd == null) {
return false;
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
index 6e337d5..f6c463f 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -213,9 +213,10 @@
Map<String, ByteBuffer> bufferForPath) {
FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant());
for (FontConfig.Font font : family.getFonts()) {
- FontFamily_Delegate.addFont(fontFamily.mNativePtr, font.getFontName(),
+ FontFamily_Delegate.addFont(fontFamily.mBuilderPtr, font.getFontName(),
font.getWeight(), font.isItalic());
}
+ fontFamily.freeze();
return fontFamily;
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 7ba86fd..741eb27 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -339,7 +339,8 @@
*/
private final static String[] PROMOTED_FIELDS = new String[] {
"android.graphics.drawable.VectorDrawable#mVectorState",
- "android.view.Choreographer#mLastFrameTimeNanos"
+ "android.view.Choreographer#mLastFrameTimeNanos",
+ "android.graphics.FontFamily#mBuilderPtr"
};
/**
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));
}