Merge "AudioService: no forced usage for media when hearing aid is connected"
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 9b9311f..50d23ad2 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -232,6 +232,9 @@
java_defaults {
name: "framework-stubs-default",
+ libs: [ "stub-annotations" ],
+ static_libs: [ "private-stub-annotations-jar" ],
+ sdk_version: "core_current",
errorprone: {
javacflags: [
"-XepDisableAllChecks",
@@ -247,62 +250,26 @@
java_library_static {
name: "android_stubs_current",
- srcs: [
- ":api-stubs-docs",
- ],
- libs: [
- "stub-annotations",
- ],
- static_libs: [
- "private-stub-annotations-jar",
- ],
+ srcs: [ ":api-stubs-docs" ],
defaults: ["framework-stubs-default"],
- sdk_version: "core_current",
}
java_library_static {
name: "android_system_stubs_current",
- srcs: [
- ":system-api-stubs-docs",
- ],
- libs: [
- "stub-annotations",
- ],
- static_libs: [
- "private-stub-annotations-jar",
- ],
+ srcs: [ ":system-api-stubs-docs" ],
defaults: ["framework-stubs-default"],
- sdk_version: "core_current",
}
java_library_static {
name: "android_test_stubs_current",
- srcs: [
- ":test-api-stubs-docs",
- ],
- libs: [
- "stub-annotations",
- ],
- static_libs: [
- "private-stub-annotations-jar",
- ],
+ srcs: [ ":test-api-stubs-docs" ],
defaults: ["framework-stubs-default"],
- sdk_version: "core_current",
}
java_library_static {
name: "android_module_lib_stubs_current",
- srcs: [
- ":module-lib-api-stubs-docs",
- ],
- libs: [
- "stub-annotations",
- ],
- static_libs: [
- "private-stub-annotations-jar",
- ],
+ srcs: [ ":module-lib-api-stubs-docs" ],
defaults: ["framework-stubs-default"],
- sdk_version: "core_current",
}
/////////////////////////////////////////////////////////////////////
diff --git a/api/current.txt b/api/current.txt
index 826d409..fa232ac 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -12575,6 +12575,7 @@
method public int getLayoutDirection();
method @NonNull public android.os.LocaleList getLocales();
method public boolean isLayoutSizeAtLeast(int);
+ method public boolean isNightModeActive();
method public boolean isScreenHdr();
method public boolean isScreenRound();
method public boolean isScreenWideColorGamut();
@@ -17032,6 +17033,7 @@
field public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1; // 0x1
field public static final int BIOMETRIC_ERROR_NONE_ENROLLED = 11; // 0xb
field public static final int BIOMETRIC_ERROR_NO_HARDWARE = 12; // 0xc
+ field public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15; // 0xf
field public static final int BIOMETRIC_SUCCESS = 0; // 0x0
}
@@ -17066,6 +17068,7 @@
field public static final int BIOMETRIC_ERROR_NO_BIOMETRICS = 11; // 0xb
field public static final int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14; // 0xe
field public static final int BIOMETRIC_ERROR_NO_SPACE = 4; // 0x4
+ field public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15; // 0xf
field public static final int BIOMETRIC_ERROR_TIMEOUT = 3; // 0x3
field public static final int BIOMETRIC_ERROR_UNABLE_TO_PROCESS = 2; // 0x2
field public static final int BIOMETRIC_ERROR_USER_CANCELED = 10; // 0xa
@@ -31280,6 +31283,7 @@
method public int getWifiState();
method public boolean is5GHzBandSupported();
method public boolean is6GHzBandSupported();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isAutoWakeupEnabled();
method @Deprecated public boolean isDeviceToApRttSupported();
method public boolean isEasyConnectSupported();
method public boolean isEnhancedOpenSupported();
@@ -42599,6 +42603,7 @@
method @NonNull public String getKeystoreAlias();
method public int getPurposes();
method @NonNull public String[] getSignaturePaddings();
+ method public int getUserAuthenticationType();
method public int getUserAuthenticationValidityDurationSeconds();
method @NonNull public boolean isDigestsSpecified();
method public boolean isInvalidatedByBiometricEnrollment();
@@ -42633,9 +42638,10 @@
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUnlockedDeviceRequired(boolean);
+ method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationParameters(@IntRange(from=0xffffffff) int, int);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationRequired(boolean);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidWhileOnBody(boolean);
- method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidityDurationSeconds(@IntRange(from=0xffffffff) int);
+ method @Deprecated @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidityDurationSeconds(@IntRange(from=0xffffffff) int);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserConfirmationRequired(boolean);
method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserPresenceRequired(boolean);
}
@@ -42652,6 +42658,7 @@
method public int getOrigin();
method public int getPurposes();
method @NonNull public String[] getSignaturePaddings();
+ method public int getUserAuthenticationType();
method public int getUserAuthenticationValidityDurationSeconds();
method public boolean isInsideSecureHardware();
method public boolean isInvalidatedByBiometricEnrollment();
@@ -42675,6 +42682,8 @@
}
public abstract class KeyProperties {
+ field public static final int AUTH_BIOMETRIC_STRONG = 2; // 0x2
+ field public static final int AUTH_DEVICE_CREDENTIAL = 1; // 0x1
field public static final String BLOCK_MODE_CBC = "CBC";
field public static final String BLOCK_MODE_CTR = "CTR";
field public static final String BLOCK_MODE_ECB = "ECB";
@@ -42721,6 +42730,7 @@
method @Nullable public java.util.Date getKeyValidityStart();
method public int getPurposes();
method @NonNull public String[] getSignaturePaddings();
+ method public int getUserAuthenticationType();
method public int getUserAuthenticationValidityDurationSeconds();
method public boolean isDigestsSpecified();
method public boolean isInvalidatedByBiometricEnrollment();
@@ -42746,9 +42756,10 @@
method @NonNull public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean);
method @NonNull public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...);
method @NonNull public android.security.keystore.KeyProtection.Builder setUnlockedDeviceRequired(boolean);
+ method @NonNull public android.security.keystore.KeyProtection.Builder setUserAuthenticationParameters(@IntRange(from=0xffffffff) int, int);
method @NonNull public android.security.keystore.KeyProtection.Builder setUserAuthenticationRequired(boolean);
method @NonNull public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidWhileOnBody(boolean);
- method @NonNull public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidityDurationSeconds(@IntRange(from=0xffffffff) int);
+ method @Deprecated @NonNull public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidityDurationSeconds(@IntRange(from=0xffffffff) int);
method @NonNull public android.security.keystore.KeyProtection.Builder setUserConfirmationRequired(boolean);
method @NonNull public android.security.keystore.KeyProtection.Builder setUserPresenceRequired(boolean);
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 24936d5..6980069 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1471,10 +1471,10 @@
public final class BluetoothA2dpSink implements android.bluetooth.BluetoothProfile {
method public void finalize();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isAudioPlaying(@Nullable android.bluetooth.BluetoothDevice);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int);
- field @RequiresPermission(android.Manifest.permission.BLUETOOTH) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED";
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isAudioPlaying(@Nullable android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int);
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED";
}
public final class BluetoothAdapter {
@@ -1647,9 +1647,9 @@
}
public class BluetoothPbap implements android.bluetooth.BluetoothProfile {
- method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
- field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
}
public interface BluetoothProfile {
@@ -2007,18 +2007,15 @@
}
public final class InstallationFile implements android.os.Parcelable {
- ctor public InstallationFile(@NonNull String, long, @Nullable byte[]);
+ ctor public InstallationFile(int, @NonNull String, long, @Nullable byte[], @Nullable byte[]);
method public int describeContents();
- method public int getFileType();
+ method public long getLengthBytes();
+ method public int getLocation();
method @Nullable public byte[] getMetadata();
method @NonNull public String getName();
- method public long getSize();
+ method @Nullable public byte[] getSignature();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.InstallationFile> CREATOR;
- field public static final int FILE_TYPE_APK = 0; // 0x0
- field public static final int FILE_TYPE_LIB = 1; // 0x1
- field public static final int FILE_TYPE_OBB = 2; // 0x2
- field public static final int FILE_TYPE_UNKNOWN = -1; // 0xffffffff
}
public final class InstantAppInfo implements android.os.Parcelable {
@@ -4854,6 +4851,8 @@
public class Tuner implements java.lang.AutoCloseable {
ctor @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public Tuner(@NonNull android.content.Context, @NonNull String, int, @Nullable android.media.tv.tuner.Tuner.OnResourceLostListener);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int cancelScanning();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int cancelTuning();
method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void clearOnTuneEventListener();
method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void close();
method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int connectCiCam(int);
@@ -4874,8 +4873,6 @@
method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setLna(boolean);
method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void setOnTuneEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.OnTuneEventListener);
method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner);
- method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int stopScan();
- method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int stopTune();
method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int tune(@NonNull android.media.tv.tuner.frontend.FrontendSettings);
method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void updateResourcePriority(int, int);
}
@@ -7717,6 +7714,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public byte[] retrieveBackupData();
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public byte[] retrieveSoftApBackupData();
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void save(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener);
+ method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setAutoWakeupEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE) public void setDeviceMobilityState(int);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setMacRandomizationSettingPasspointEnabled(@NonNull String, boolean);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setMeteredOverridePasspoint(@NonNull String, int);
@@ -8242,8 +8240,8 @@
public final class NativeScanResult implements android.os.Parcelable {
ctor public NativeScanResult();
method public int describeContents();
- method @NonNull public byte[] getBssid();
- method @NonNull public int getCapabilities();
+ method @Nullable public android.net.MacAddress getBssid();
+ method public int getCapabilities();
method public int getFrequencyMhz();
method @NonNull public byte[] getInformationElements();
method @NonNull public java.util.List<android.net.wifi.wificond.RadioChainInfo> getRadioChainInfos();
@@ -8252,15 +8250,31 @@
method public long getTsf();
method public boolean isAssociated();
method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int BSS_CAPABILITY_APSD = 2048; // 0x800
+ field public static final int BSS_CAPABILITY_CF_POLLABLE = 4; // 0x4
+ field public static final int BSS_CAPABILITY_CF_POLL_REQUEST = 8; // 0x8
+ field public static final int BSS_CAPABILITY_CHANNEL_AGILITY = 128; // 0x80
+ field public static final int BSS_CAPABILITY_DELAYED_BLOCK_ACK = 16384; // 0x4000
+ field public static final int BSS_CAPABILITY_DSSS_OFDM = 8192; // 0x2000
+ field public static final int BSS_CAPABILITY_ESS = 1; // 0x1
+ field public static final int BSS_CAPABILITY_IBSS = 2; // 0x2
+ field public static final int BSS_CAPABILITY_IMMEDIATE_BLOCK_ACK = 32768; // 0x8000
+ field public static final int BSS_CAPABILITY_PBCC = 64; // 0x40
+ field public static final int BSS_CAPABILITY_PRIVACY = 16; // 0x10
+ field public static final int BSS_CAPABILITY_QOS = 512; // 0x200
+ field public static final int BSS_CAPABILITY_RADIO_MANAGEMENT = 4096; // 0x1000
+ field public static final int BSS_CAPABILITY_SHORT_PREAMBLE = 32; // 0x20
+ field public static final int BSS_CAPABILITY_SHORT_SLOT_TIME = 1024; // 0x400
+ field public static final int BSS_CAPABILITY_SPECTRUM_MANAGEMENT = 256; // 0x100
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.wificond.NativeScanResult> CREATOR;
}
public final class NativeWifiClient implements android.os.Parcelable {
- ctor public NativeWifiClient(@NonNull byte[]);
+ ctor public NativeWifiClient(@Nullable android.net.MacAddress);
method public int describeContents();
+ method @Nullable public android.net.MacAddress getMacAddress();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.wificond.NativeWifiClient> CREATOR;
- field @NonNull public final byte[] macAddress;
}
public final class PnoNetwork implements android.os.Parcelable {
@@ -8305,7 +8319,7 @@
public class WifiCondManager {
method public void abortScan(@NonNull String);
method public void enableVerboseLogging(boolean);
- method @NonNull public java.util.List<java.lang.Integer> getChannelsMhzForBand(int);
+ method @NonNull public int[] getChannelsMhzForBand(int);
method @Nullable public android.net.wifi.wificond.DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String);
method @NonNull public java.util.List<android.net.wifi.wificond.NativeScanResult> getScanResults(@NonNull String, int);
method @Nullable public android.net.wifi.wificond.WifiCondManager.TxPacketCounters getTxPacketCounters(@NonNull String);
@@ -9680,7 +9694,6 @@
field public static final String EUICC_UNSUPPORTED_COUNTRIES = "euicc_unsupported_countries";
field public static final String INSTALL_CARRIER_APP_NOTIFICATION_PERSISTENT = "install_carrier_app_notification_persistent";
field public static final String INSTALL_CARRIER_APP_NOTIFICATION_SLEEP_MILLIS = "install_carrier_app_notification_sleep_millis";
- field public static final String NETWORK_RECOMMENDATIONS_ENABLED = "network_recommendations_enabled";
field public static final String OTA_DISABLE_AUTOMATIC_UPDATE = "ota_disable_automatic_update";
field public static final String REQUIRE_PASSWORD_TO_DECRYPT = "require_password_to_decrypt";
field public static final String SOFT_AP_TIMEOUT_ENABLED = "soft_ap_timeout_enabled";
@@ -9695,7 +9708,7 @@
field public static final String WIFI_SCAN_THROTTLE_ENABLED = "wifi_scan_throttle_enabled";
field public static final String WIFI_SCORE_PARAMS = "wifi_score_params";
field public static final String WIFI_VERBOSE_LOGGING_ENABLED = "wifi_verbose_logging_enabled";
- field public static final String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled";
+ field @Deprecated public static final String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled";
}
public static final class Settings.Secure extends android.provider.Settings.NameValueTable {
@@ -10317,7 +10330,7 @@
public abstract class DataLoaderService extends android.app.Service {
ctor public DataLoaderService();
- method @Nullable public android.service.dataloader.DataLoaderService.DataLoader onCreateDataLoader();
+ method @Nullable public android.service.dataloader.DataLoaderService.DataLoader onCreateDataLoader(@NonNull android.content.pm.DataLoaderParams);
}
public static interface DataLoaderService.DataLoader {
diff --git a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
index d79740b..9912d2b 100644
--- a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
@@ -76,6 +76,16 @@
private final int mDescriptionResId;
/**
+ * Resource id of the animated image of the accessibility shortcut target.
+ */
+ private final int mAnimatedImageRes;
+
+ /**
+ * Resource id of the html description of the accessibility shortcut target.
+ */
+ private final int mHtmlDescriptionRes;
+
+ /**
* Creates a new instance.
*
* @param context Context for accessing resources.
@@ -119,6 +129,14 @@
// Gets summary
mSummaryResId = asAttributes.getResourceId(
com.android.internal.R.styleable.AccessibilityShortcutTarget_summary, 0);
+ // Gets animated image
+ mAnimatedImageRes = asAttributes.getResourceId(
+ com.android.internal.R.styleable
+ .AccessibilityShortcutTarget_animatedImageDrawable, 0);
+ // Gets html description
+ mHtmlDescriptionRes = asAttributes.getResourceId(
+ com.android.internal.R.styleable.AccessibilityShortcutTarget_htmlDescription,
+ 0);
asAttributes.recycle();
if (mDescriptionResId == 0 || mSummaryResId == 0) {
@@ -172,6 +190,25 @@
}
/**
+ * The animated image resource id of the accessibility shortcut target.
+ *
+ * @return The animated image resource id.
+ */
+ public int getAnimatedImageRes() {
+ return mAnimatedImageRes;
+ }
+
+ /**
+ * The localized html description of the accessibility shortcut target.
+ *
+ * @return The localized html description.
+ */
+ @Nullable
+ public String loadHtmlDescription(@NonNull PackageManager packageManager) {
+ return loadResourceString(packageManager, mActivityInfo, mHtmlDescriptionRes);
+ }
+
+ /**
* Gets string resource by the given activity and resource id.
*/
@Nullable
diff --git a/core/java/android/app/ITaskOrganizerController.aidl b/core/java/android/app/ITaskOrganizerController.aidl
index 5d5956e..9d6c3d6 100644
--- a/core/java/android/app/ITaskOrganizerController.aidl
+++ b/core/java/android/app/ITaskOrganizerController.aidl
@@ -52,7 +52,11 @@
boolean deleteRootTask(IWindowContainer task);
/** Gets direct child tasks (ordered from top-to-bottom) */
- List<ActivityManager.RunningTaskInfo> getChildTasks(in IWindowContainer parent);
+ List<ActivityManager.RunningTaskInfo> getChildTasks(in IWindowContainer parent,
+ in int[] activityTypes);
+
+ /** Gets all root tasks on a display (ordered from top-to-bottom) */
+ List<ActivityManager.RunningTaskInfo> getRootTasks(int displayId, in int[] activityTypes);
/** Get the root task which contains the current ime target */
IWindowContainer getImeTarget(int display);
diff --git a/core/java/android/app/PictureInPictureParams.java b/core/java/android/app/PictureInPictureParams.java
index bfd966c5..67d94de 100644
--- a/core/java/android/app/PictureInPictureParams.java
+++ b/core/java/android/app/PictureInPictureParams.java
@@ -247,6 +247,14 @@
return mSourceRectHint != null && !mSourceRectHint.isEmpty();
}
+ /**
+ * @return True if no parameters are set
+ * @hide
+ */
+ public boolean empty() {
+ return !hasSourceBoundsHint() && !hasSetActions() && !hasSetAspectRatio();
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 662ca6e..f7d712d 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -149,6 +149,13 @@
public IWindowContainer token;
/**
+ * The PictureInPictureParams for the Task, if set.
+ * @hide
+ */
+ @Nullable
+ public PictureInPictureParams pictureInPictureParams;
+
+ /**
* The activity type of the top activity in this task.
* @hide
*/
@@ -209,6 +216,9 @@
configuration.readFromParcel(source);
token = IWindowContainer.Stub.asInterface(source.readStrongBinder());
topActivityType = source.readInt();
+ pictureInPictureParams = source.readInt() != 0
+ ? PictureInPictureParams.CREATOR.createFromParcel(source)
+ : null;
}
/**
@@ -246,6 +256,12 @@
configuration.writeToParcel(dest, flags);
dest.writeStrongInterface(token);
dest.writeInt(topActivityType);
+ if (pictureInPictureParams == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ pictureInPictureParams.writeToParcel(dest, flags);
+ }
}
@Override
@@ -261,6 +277,7 @@
+ " supportsSplitScreenMultiWindow=" + supportsSplitScreenMultiWindow
+ " resizeMode=" + resizeMode
+ " token=" + token
- + " topActivityType=" + topActivityType;
+ + " topActivityType=" + topActivityType
+ + " pictureInPictureParams=" + pictureInPictureParams;
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 9cec514..dc15b51 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -8951,7 +8951,8 @@
*
* <strong>Note: Starting from Android R, apps should no longer call this method with the
* setting {@link android.provider.Settings.Secure#LOCATION_MODE}, which is deprecated. Instead,
- * device owners should call {@link #setLocationEnabled(ComponentName, boolean)}.
+ * device owners should call {@link #setLocationEnabled(ComponentName, boolean)}. This will be
+ * enforced for all apps targeting Android R or above.
* </strong>
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
@@ -8961,6 +8962,7 @@
*/
public void setSecureSetting(@NonNull ComponentName admin, String setting, String value) {
throwIfParentInstance("setSecureSetting");
+
if (mService != null) {
try {
mService.setSecureSetting(admin, setting, value);
diff --git a/core/java/android/app/compat/TEST_MAPPING b/core/java/android/app/compat/TEST_MAPPING
new file mode 100644
index 0000000..c047df5
--- /dev/null
+++ b/core/java/android/app/compat/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "imports": [
+ {
+ "path": "frameworks/base/services/core/java/com/android/services/compat"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java
index ee2cc6d..ab49230 100755
--- a/core/java/android/bluetooth/BluetoothA2dpSink.java
+++ b/core/java/android/bluetooth/BluetoothA2dpSink.java
@@ -66,7 +66,7 @@
*/
@SystemApi
@SuppressLint("ActionValue")
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public static final String ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED";
@@ -296,7 +296,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean setConnectionPolicy(@Nullable BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -345,7 +345,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) {
if (VDBG) log("getConnectionPolicy(" + device + ")");
final IBluetoothA2dpSink service = getService();
@@ -370,7 +370,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean isAudioPlaying(@Nullable BluetoothDevice device) {
final IBluetoothA2dpSink service = getService();
if (service != null && isEnabled() && isValidDevice(device)) {
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 587c92e..66bfcbd 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1970,6 +1970,38 @@
}
}
+ private static final String BLUETOOTH_FILTERING_CACHE_PROPERTY =
+ "cache_key.bluetooth.is_offloaded_filtering_supported";
+ private final PropertyInvalidatedCache<Void, Boolean> mBluetoothFilteringCache =
+ new PropertyInvalidatedCache<Void, Boolean>(
+ 8, BLUETOOTH_FILTERING_CACHE_PROPERTY) {
+ @Override
+ protected Boolean recompute(Void query) {
+ try {
+ mServiceLock.readLock().lock();
+ if (mService != null) {
+ return mService.isOffloadedFilteringSupported();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to get isOffloadedFilteringSupported, error: ", e);
+ } finally {
+ mServiceLock.readLock().unlock();
+ }
+ return false;
+
+ }
+ };
+
+ /** @hide */
+ public void disableIsOffloadedFilteringSupportedCache() {
+ mBluetoothFilteringCache.disableLocal();
+ }
+
+ /** @hide */
+ public static void invalidateIsOffloadedFilteringSupportedCache() {
+ PropertyInvalidatedCache.invalidateCache(BLUETOOTH_FILTERING_CACHE_PROPERTY);
+ }
+
/**
* Return true if offloaded filters are supported
*
@@ -1979,17 +2011,7 @@
if (!getLeAccess()) {
return false;
}
- try {
- mServiceLock.readLock().lock();
- if (mService != null) {
- return mService.isOffloadedFilteringSupported();
- }
- } catch (RemoteException e) {
- Log.e(TAG, "failed to get isOffloadedFilteringSupported, error: ", e);
- } finally {
- mServiceLock.readLock().unlock();
- }
- return false;
+ return mBluetoothFilteringCache.query(null);
}
/**
@@ -2361,6 +2383,43 @@
return BluetoothAdapter.STATE_DISCONNECTED;
}
+ private static final String BLUETOOTH_PROFILE_CACHE_PROPERTY =
+ "cache_key.bluetooth.get_profile_connection_state";
+ private final PropertyInvalidatedCache<Integer, Integer>
+ mGetProfileConnectionStateCache =
+ new PropertyInvalidatedCache<Integer, Integer>(
+ 8, BLUETOOTH_PROFILE_CACHE_PROPERTY) {
+ @Override
+ protected Integer recompute(Integer query) {
+ try {
+ mServiceLock.readLock().lock();
+ if (mService != null) {
+ return mService.getProfileConnectionState(query);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "getProfileConnectionState:", e);
+ } finally {
+ mServiceLock.readLock().unlock();
+ }
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ @Override
+ public String queryToString(Integer query) {
+ return String.format("getProfileConnectionState(profile=\"%d\")",
+ query);
+ }
+ };
+
+ /** @hide */
+ public void disableGetProfileConnectionStateCache() {
+ mGetProfileConnectionStateCache.disableLocal();
+ }
+
+ /** @hide */
+ public static void invalidateGetProfileConnectionStateCache() {
+ PropertyInvalidatedCache.invalidateCache(BLUETOOTH_PROFILE_CACHE_PROPERTY);
+ }
+
/**
* Get the current connection state of a profile.
* This function can be used to check whether the local Bluetooth adapter
@@ -2378,17 +2437,7 @@
if (getState() != STATE_ON) {
return BluetoothProfile.STATE_DISCONNECTED;
}
- try {
- mServiceLock.readLock().lock();
- if (mService != null) {
- return mService.getProfileConnectionState(profile);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "getProfileConnectionState:", e);
- } finally {
- mServiceLock.readLock().unlock();
- }
- return BluetoothProfile.STATE_DISCONNECTED;
+ return mGetProfileConnectionStateCache.query(new Integer(profile));
}
/**
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index e07ca52..1f89ddf 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -38,9 +38,6 @@
import java.util.List;
/**
- * The Android Bluetooth API is not finalized, and *will* change. Use at your
- * own risk.
- *
* Public API for controlling the Bluetooth Pbap Service. This includes
* Bluetooth Phone book Access profile.
* BluetoothPbap is a proxy object for controlling the Bluetooth Pbap
@@ -56,6 +53,11 @@
* notification when it is bound, this is especially important if you wish to
* immediately call methods on BluetoothPbap after construction.
*
+ * To get an instance of the BluetoothPbap class, you can call
+ * {@link BluetoothAdapter#getProfileProxy(Context, ServiceListener, int)} with the final param
+ * being {@link BluetoothProfile#PBAP}. The ServiceListener should be able to get the instance of
+ * BluetoothPbap in {@link android.bluetooth.BluetoothProfile.ServiceListener#onServiceConnected}.
+ *
* Android only supports one connected Bluetooth Pce at a time.
*
* @hide
@@ -87,6 +89,7 @@
*/
@SuppressLint("ActionValue")
@SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
@@ -235,7 +238,8 @@
*/
@SystemApi
@Override
- public int getConnectionState(@Nullable BluetoothDevice device) {
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public @BtProfileState int getConnectionState(@Nullable BluetoothDevice device) {
log("getConnectionState: device=" + device);
try {
final IBluetoothPbap service = mService;
@@ -287,7 +291,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 0e0161f..f32a4ab 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -700,27 +700,6 @@
/** @hide */
public static final String REMOTE_CALLBACK_RESULT = "result";
- /**
- * How long we wait for an attached process to publish its content providers
- * before we decide it must be hung.
- * @hide
- */
- public static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS = 10 * 1000;
-
- /**
- * How long we wait for an provider to be published. Should be longer than
- * {@link #CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS}.
- * @hide
- */
- public static final int CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS =
- CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS + 10 * 1000;
-
- // Should be >= {@link #CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS}, because that's how
- // long ActivityManagerService is giving a content provider to get published if a new process
- // needs to be started for that.
- private static final int GET_TYPE_TIMEOUT_MILLIS =
- CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS + 5 * 1000;
-
public ContentResolver(@Nullable Context context) {
this(context, null);
}
@@ -870,6 +849,8 @@
}
}
+ private static final int GET_TYPE_TIMEOUT_MILLIS = 3000;
+
private static class GetTypeResultListener implements RemoteCallback.OnResultListener {
@GuardedBy("this")
public boolean done;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 6f8a99f..0f88c90 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4289,7 +4289,9 @@
* intent filter in their manifests, so that they can be looked up and bound to by
* {@code DataLoaderManagerService}.
*
- * Data loader service providers must be privileged apps.
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ *
+ * Data loader service providers must be privileged apps.
* See {@link com.android.server.pm.PackageManagerShellCommandDataLoader} as an example of such
* data loader service provider.
*
@@ -4970,11 +4972,14 @@
* <pre>
* <accessibility-shortcut-target
* android:description="@string/shortcut_target_description"
- * android:summary="@string/shortcut_target_summary" />
+ * android:summary="@string/shortcut_target_summary"
+ * android:animatedImageDrawable="@drawable/shortcut_target_animated_image"
+ * android:htmlDescription="@string/shortcut_target_html_description" />
* </pre>
* <p>
* Both description and summary are necessary. The system will ignore the accessibility
- * shortcut target if they are missing.
+ * shortcut target if they are missing. The animated image and html description are supported
+ * to help users understand how to use the shortcut target.
* </p>
*/
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
diff --git a/core/java/android/content/pm/InstallationFile.java b/core/java/android/content/pm/InstallationFile.java
index 111ad32..b449945 100644
--- a/core/java/android/content/pm/InstallationFile.java
+++ b/core/java/android/content/pm/InstallationFile.java
@@ -16,82 +16,59 @@
package android.content.pm;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
/**
* Defines the properties of a file in an installation session.
- * TODO(b/136132412): update with new APIs.
- *
* @hide
*/
@SystemApi
public final class InstallationFile implements Parcelable {
- public static final int FILE_TYPE_UNKNOWN = -1;
- public static final int FILE_TYPE_APK = 0;
- public static final int FILE_TYPE_LIB = 1;
- public static final int FILE_TYPE_OBB = 2;
+ private final @PackageInstaller.FileLocation int mLocation;
+ private final @NonNull String mName;
+ private final long mLengthBytes;
+ private final @Nullable byte[] mMetadata;
+ private final @Nullable byte[] mSignature;
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = {"FILE_TYPE_"}, value = {
- FILE_TYPE_APK,
- FILE_TYPE_LIB,
- FILE_TYPE_OBB,
- })
- public @interface FileType {
- }
-
- private String mFileName;
- private @FileType int mFileType;
- private long mFileSize;
- private byte[] mMetadata;
-
- public InstallationFile(@NonNull String fileName, long fileSize,
- @Nullable byte[] metadata) {
- mFileName = fileName;
- mFileSize = fileSize;
+ public InstallationFile(@PackageInstaller.FileLocation int location, @NonNull String name,
+ long lengthBytes, @Nullable byte[] metadata, @Nullable byte[] signature) {
+ mLocation = location;
+ mName = name;
+ mLengthBytes = lengthBytes;
mMetadata = metadata;
- if (fileName.toLowerCase().endsWith(".apk")) {
- mFileType = FILE_TYPE_APK;
- } else if (fileName.toLowerCase().endsWith(".obb")) {
- mFileType = FILE_TYPE_OBB;
- } else if (fileName.toLowerCase().endsWith(".so") && fileName.toLowerCase().startsWith(
- "lib/")) {
- mFileType = FILE_TYPE_LIB;
- } else {
- mFileType = FILE_TYPE_UNKNOWN;
- }
+ mSignature = signature;
}
- public @FileType int getFileType() {
- return mFileType;
+ public @PackageInstaller.FileLocation int getLocation() {
+ return mLocation;
}
public @NonNull String getName() {
- return mFileName;
+ return mName;
}
- public long getSize() {
- return mFileSize;
+ public long getLengthBytes() {
+ return mLengthBytes;
}
public @Nullable byte[] getMetadata() {
return mMetadata;
}
+ public @Nullable byte[] getSignature() {
+ return mSignature;
+ }
+
private InstallationFile(Parcel source) {
- mFileName = source.readString();
- mFileType = source.readInt();
- mFileSize = source.readLong();
+ mLocation = source.readInt();
+ mName = source.readString();
+ mLengthBytes = source.readLong();
mMetadata = source.createByteArray();
+ mSignature = source.createByteArray();
}
@Override
@@ -101,10 +78,11 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeString(mFileName);
- dest.writeInt(mFileType);
- dest.writeLong(mFileSize);
+ dest.writeInt(mLocation);
+ dest.writeString(mName);
+ dest.writeLong(mLengthBytes);
dest.writeByteArray(mMetadata);
+ dest.writeByteArray(mSignature);
}
public static final @NonNull Creator<InstallationFile> CREATOR =
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index d253278..70603b4 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -16,6 +16,7 @@
package android.content.pm;
+import static android.Manifest.permission;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -601,11 +602,15 @@
}
/**
- * Show an error log on logcat, when the calling user is a managed profile, and the target
- * user is different from the calling user, in order to help developers to detect it.
+ * Show an error log on logcat, when the calling user is a managed profile, the target
+ * user is different from the calling user, and it is not called from a package that has the
+ * {@link permission.INTERACT_ACROSS_USERS_FULL} permission, in order to help
+ * developers to detect it.
*/
private void logErrorForInvalidProfileAccess(@NonNull UserHandle target) {
- if (UserHandle.myUserId() != target.getIdentifier() && mUserManager.isManagedProfile()) {
+ if (UserHandle.myUserId() != target.getIdentifier() && mUserManager.isManagedProfile()
+ && mContext.checkSelfPermission(permission.INTERACT_ACROSS_USERS_FULL)
+ != PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "Accessing other profiles/users from managed profile is no longer allowed.");
}
}
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 8c358cc..6a9e0aa 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -45,6 +45,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
+import android.app.UiModeManager;
import android.app.WindowConfiguration;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.LocaleProto;
@@ -1975,6 +1976,15 @@
readFromParcel(source);
}
+
+ /**
+ * Retuns whether the configuration is in night mode
+ * @return true if night mode is active and false otherwise
+ */
+ public boolean isNightModeActive() {
+ return (uiMode & UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES;
+ }
+
public int compareTo(Configuration that) {
int n;
float a = this.fontScale;
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java
index 5a13651..add67aa 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -132,6 +132,14 @@
int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14;
/**
+ * A security vulnerability has been discovered and the sensor is unavailable until a
+ * security update has addressed this issue. This error can be received if for example,
+ * authentication was requested with {@link Authenticators#BIOMETRIC_STRONG}, but the
+ * sensor's strength can currently only meet {@link Authenticators#BIOMETRIC_WEAK}.
+ */
+ int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15;
+
+ /**
* This constant is only used by SystemUI. It notifies SystemUI that authentication was paused
* because the authentication attempt was unsuccessful.
* @hide
diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
index bae0fd3..6ba7c2a 100644
--- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
@@ -17,6 +17,7 @@
package android.hardware.biometrics;
import android.app.KeyguardManager;
+import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.face.FaceManager;
/**
@@ -142,6 +143,15 @@
public static final int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14;
/**
+ * A security vulnerability has been discovered and the sensor is unavailable until a
+ * security update has addressed this issue. This error can be received if for example,
+ * authentication was requested with {@link Authenticators#BIOMETRIC_STRONG}, but the
+ * sensor's strength can currently only meet {@link Authenticators#BIOMETRIC_WEAK}.
+ * @hide
+ */
+ int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15;
+
+ /**
* @hide
*/
public static final int FACE_ERROR_VENDOR_BASE = 1000;
diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
index 5c74456..0732d76 100644
--- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
@@ -18,6 +18,7 @@
import android.app.KeyguardManager;
import android.compat.annotation.UnsupportedAppUsage;
+import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.fingerprint.FingerprintManager;
/**
@@ -128,6 +129,15 @@
public static final int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14;
/**
+ * A security vulnerability has been discovered and the sensor is unavailable until a
+ * security update has addressed this issue. This error can be received if for example,
+ * authentication was requested with {@link Authenticators#BIOMETRIC_STRONG}, but the
+ * sensor's strength can currently only meet {@link Authenticators#BIOMETRIC_WEAK}.
+ * @hide
+ */
+ public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15;
+
+ /**
* @hide
*/
@UnsupportedAppUsage
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 125b676..7d66cae 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -61,10 +61,20 @@
public static final int BIOMETRIC_ERROR_NO_HARDWARE =
BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT;
+ /**
+ * A security vulnerability has been discovered and the sensor is unavailable until a
+ * security update has addressed this issue. This error can be received if for example,
+ * authentication was requested with {@link Authenticators#BIOMETRIC_STRONG}, but the
+ * sensor's strength can currently only meet {@link Authenticators#BIOMETRIC_WEAK}.
+ */
+ public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED =
+ BiometricConstants.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED;
+
@IntDef({BIOMETRIC_SUCCESS,
BIOMETRIC_ERROR_HW_UNAVAILABLE,
BIOMETRIC_ERROR_NONE_ENROLLED,
- BIOMETRIC_ERROR_NO_HARDWARE})
+ BIOMETRIC_ERROR_NO_HARDWARE,
+ BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED})
@interface BiometricError {}
/**
diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java
index 987a53e..25fb3e0 100644
--- a/core/java/android/os/incremental/IncrementalFileStorages.java
+++ b/core/java/android/os/incremental/IncrementalFileStorages.java
@@ -29,6 +29,8 @@
* @throws IllegalStateException the session is not an Incremental installation session.
*/
+import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -85,7 +87,7 @@
try {
result = new IncrementalFileStorages(stageDir, incrementalManager, dataLoaderParams);
for (InstallationFile file : addedFiles) {
- if (file.getFileType() == InstallationFile.FILE_TYPE_APK) {
+ if (file.getLocation() == LOCATION_DATA_APP) {
try {
result.addApkFile(file);
} catch (IOException e) {
@@ -95,7 +97,7 @@
e);
}
} else {
- throw new IOException("Unknown file type: " + file.getFileType());
+ throw new IOException("Unknown file location: " + file.getLocation());
}
}
@@ -147,8 +149,8 @@
String apkName = apk.getName();
File targetFile = Paths.get(stageDirPath, apkName).toFile();
if (!targetFile.exists()) {
- mDefaultStorage.makeFile(apkName, apk.getSize(), null,
- apk.getMetadata(), 0, null, null, null);
+ mDefaultStorage.makeFile(apkName, apk.getLengthBytes(), null, apk.getMetadata(),
+ apk.getSignature());
}
}
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index ba38268..d2d8f85 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -299,7 +299,16 @@
return nativeIsIncrementalPath(path);
}
+ /**
+ * Returns raw signature for file if it's on Incremental File System.
+ * Unsafe, use only if you are sure what you are doing.
+ */
+ public static @Nullable byte[] unsafeGetFileSignature(@NonNull String path) {
+ return nativeUnsafeGetFileSignature(path);
+ }
+
/* Native methods */
private static native boolean nativeIsEnabled();
private static native boolean nativeIsIncrementalPath(@NonNull String path);
+ private static native byte[] nativeUnsafeGetFileSignature(@NonNull String path);
}
diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java
index 5df44ff..f4e1f96 100644
--- a/core/java/android/os/incremental/IncrementalStorage.java
+++ b/core/java/android/os/incremental/IncrementalStorage.java
@@ -20,6 +20,8 @@
import android.annotation.Nullable;
import android.os.RemoteException;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -169,10 +171,11 @@
* @param path Relative path of the new file.
* @param size Size of the new file in bytes.
* @param metadata Metadata bytes.
+ * @param v4signatureBytes Serialized V4SignatureProto.
*/
public void makeFile(@NonNull String path, long size, @Nullable UUID id,
- @Nullable byte[] metadata, int hashAlgorithm, @Nullable byte[] rootHash,
- @Nullable byte[] additionalData, @Nullable byte[] signature) throws IOException {
+ @Nullable byte[] metadata, @Nullable byte[] v4signatureBytes)
+ throws IOException {
try {
if (id == null && metadata == null) {
throw new IOException("File ID and metadata cannot both be null");
@@ -181,13 +184,7 @@
params.size = size;
params.metadata = (metadata == null ? new byte[0] : metadata);
params.fileId = idToBytes(id);
- if (hashAlgorithm != 0 || signature != null) {
- params.signature = new IncrementalSignature();
- params.signature.hashAlgorithm = hashAlgorithm;
- params.signature.rootHash = rootHash;
- params.signature.additionalData = additionalData;
- params.signature.signature = signature;
- }
+ params.signature = parseV4Signature(v4signatureBytes);
int res = mService.makeFile(mId, path, params);
if (res != 0) {
throw new IOException("makeFile() failed with errno " + -res);
@@ -197,6 +194,7 @@
}
}
+
/**
* Creates a file in Incremental storage. The content of the file is mapped from a range inside
* a source file in the same storage.
@@ -349,6 +347,37 @@
}
}
+ /**
+ * Returns the metadata object of an IncFs File.
+ *
+ * @param id The file id.
+ * @return Byte array that contains metadata bytes.
+ */
+ @Nullable
+ public byte[] getFileMetadata(@NonNull UUID id) {
+ try {
+ final byte[] rawId = idToBytes(id);
+ return mService.getMetadataById(mId, rawId);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return null;
+ }
+ }
+
+ /**
+ * Informs the data loader service associated with the current storage to start data loader
+ *
+ * @return True if data loader is successfully started.
+ */
+ public boolean startLoading() {
+ try {
+ return mService.startLoading(mId);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return false;
+ }
+ }
+
private static final int UUID_BYTE_SIZE = 16;
/**
@@ -386,35 +415,44 @@
return new UUID(msb, lsb);
}
- /**
- * Returns the metadata object of an IncFs File.
- *
- * @param id The file id.
- * @return Byte array that contains metadata bytes.
- */
- @Nullable
- public byte[] getFileMetadata(@NonNull UUID id) {
- try {
- final byte[] rawId = idToBytes(id);
- return mService.getMetadataById(mId, rawId);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- return null;
- }
- }
+ private static final int INCFS_HASH_SHA256 = 1;
+ private static final int INCFS_MAX_HASH_SIZE = 32; // SHA256
+ private static final int INCFS_MAX_ADD_DATA_SIZE = 128;
/**
- * Informs the data loader service associated with the current storage to start data loader
- *
- * @return True if data loader is successfully started.
+ * Deserialize and validate v4 signature bytes.
*/
- public boolean startLoading() {
- try {
- return mService.startLoading(mId);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- return false;
+ private static IncrementalSignature parseV4Signature(@Nullable byte[] v4signatureBytes)
+ throws IOException {
+ if (v4signatureBytes == null) {
+ return null;
}
+
+ final V4Signature signature;
+ try (DataInputStream input = new DataInputStream(
+ new ByteArrayInputStream(v4signatureBytes))) {
+ signature = V4Signature.readFrom(input);
+ }
+
+ final byte[] rootHash = signature.verityRootHash;
+ final byte[] additionalData = signature.v3Digest;
+ final byte[] pkcs7Signature = signature.pkcs7SignatureBlock;
+
+ if (rootHash.length != INCFS_MAX_HASH_SIZE) {
+ throw new IOException("rootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes");
+ }
+ if (additionalData.length > INCFS_MAX_ADD_DATA_SIZE) {
+ throw new IOException(
+ "additionalData has to be at most " + INCFS_MAX_ADD_DATA_SIZE + " bytes");
+ }
+
+ IncrementalSignature result = new IncrementalSignature();
+ result.hashAlgorithm = INCFS_HASH_SHA256;
+ result.rootHash = rootHash;
+ result.additionalData = additionalData;
+ result.signature = pkcs7Signature;
+
+ return result;
}
/**
diff --git a/core/java/android/os/incremental/V4Signature.java b/core/java/android/os/incremental/V4Signature.java
new file mode 100644
index 0000000..5fadee4
--- /dev/null
+++ b/core/java/android/os/incremental/V4Signature.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 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.os.incremental;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * V4 signature fields.
+ * Keep in sync with APKSig master copy.
+ * @hide
+ */
+public class V4Signature {
+ public final byte[] verityRootHash;
+ public final byte[] v3Digest;
+ public final byte[] pkcs7SignatureBlock;
+
+ V4Signature(byte[] verityRootHash, byte[] v3Digest, byte[] pkcs7SignatureBlock) {
+ this.verityRootHash = verityRootHash;
+ this.v3Digest = v3Digest;
+ this.pkcs7SignatureBlock = pkcs7SignatureBlock;
+ }
+
+ static byte[] readBytes(DataInputStream stream) throws IOException {
+ byte[] result = new byte[stream.readInt()];
+ stream.read(result);
+ return result;
+ }
+
+ static V4Signature readFrom(DataInputStream stream) throws IOException {
+ byte[] verityRootHash = readBytes(stream);
+ byte[] v3Digest = readBytes(stream);
+ byte[] pkcs7SignatureBlock = readBytes(stream);
+ return new V4Signature(verityRootHash, v3Digest, pkcs7SignatureBlock);
+ }
+
+ static void writeBytes(DataOutputStream stream, byte[] bytes) throws IOException {
+ stream.writeInt(bytes.length);
+ stream.write(bytes);
+ }
+
+ void writeTo(DataOutputStream stream) throws IOException {
+ writeBytes(stream, this.verityRootHash);
+ writeBytes(stream, this.v3Digest);
+ writeBytes(stream, this.pkcs7SignatureBlock);
+ }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b9207e5..ea0afa9 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10207,7 +10207,9 @@
*
* Type: int (0 for false, 1 for true)
* @hide
+ * @deprecated Use {@link WifiManager#isAutoWakeupEnabled()} instead.
*/
+ @Deprecated
@SystemApi
public static final String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled";
@@ -10243,7 +10245,6 @@
* enabled state.
* @hide
*/
- @SystemApi
public static final String NETWORK_RECOMMENDATIONS_ENABLED =
"network_recommendations_enabled";
diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java
index c215778..b9700b2 100644
--- a/core/java/android/service/dataloader/DataLoaderService.java
+++ b/core/java/android/service/dataloader/DataLoaderService.java
@@ -90,7 +90,7 @@
* @hide
*/
@SystemApi
- public @Nullable DataLoader onCreateDataLoader() {
+ public @Nullable DataLoader onCreateDataLoader(@NonNull DataLoaderParams dataLoaderParams) {
return null;
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index f9a023f..1730347 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -97,6 +97,8 @@
IWindowSession openSession(in IWindowSessionCallback callback);
+ boolean useBLAST();
+
@UnsupportedAppUsage
void getInitialDisplaySize(int displayId, out Point size);
@UnsupportedAppUsage
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index d4961ea..6caa4fe 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -198,7 +198,7 @@
return "InsetsSource: {"
+ "mType=" + InsetsState.typeToString(mType)
+ ", mFrame=" + mFrame.toShortString()
- + ", mVisible" + mVisible
+ + ", mVisible=" + mVisible
+ "}";
}
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 78a080d..4ac6a66 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -258,14 +258,14 @@
*/
public void release() {
synchronized (mLock) {
- if (mNativeObject != 0) {
- nativeRelease(mNativeObject);
- setNativeObjectLocked(0);
- }
if (mHwuiContext != null) {
mHwuiContext.destroy();
mHwuiContext = null;
}
+ if (mNativeObject != 0) {
+ nativeRelease(mNativeObject);
+ setNativeObjectLocked(0);
+ }
}
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index b1c354f..637a088 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -22,7 +22,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.CompatibilityInfo.Translator;
@@ -43,8 +42,8 @@
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceControl.Transaction;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.view.SurfaceControlViewHost;
+import android.view.accessibility.AccessibilityNodeInfo;
import com.android.internal.view.SurfaceCallbackHelper;
@@ -386,7 +385,7 @@
* This gets called on a RenderThread worker thread, so members accessed here must
* be protected by a lock.
*/
- final boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER;
+ final boolean useBLAST = WindowManagerGlobal.getInstance().useBLAST();
viewRoot.registerRtFrameCallback(frame -> {
try {
final SurfaceControl.Transaction t = useBLAST ?
@@ -1120,7 +1119,7 @@
private void applySurfaceTransforms(SurfaceControl surface, SurfaceControl.Transaction t,
Rect position, long frameNumber) {
- if (frameNumber > 0 && !WindowManagerGlobal.USE_BLAST_ADAPTER) {
+ if (frameNumber > 0 && !WindowManagerGlobal.getInstance().useBLAST()) {
final ViewRootImpl viewRoot = getViewRootImpl();
t.deferTransactionUntil(surface, viewRoot.getRenderSurfaceControl(),
@@ -1138,7 +1137,7 @@
}
private void setParentSpaceRectangle(Rect position, long frameNumber) {
- final boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER;
+ final boolean useBLAST = WindowManagerGlobal.getInstance().useBLAST();
final ViewRootImpl viewRoot = getViewRootImpl();
final SurfaceControl.Transaction t = useBLAST ? viewRoot.getBLASTSyncTransaction() :
mRtTransaction;
@@ -1199,7 +1198,7 @@
@Override
public void positionLost(long frameNumber) {
- boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER;
+ boolean useBLAST = WindowManagerGlobal.getInstance().useBLAST();
if (DEBUG) {
Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d",
System.identityHashCode(this), frameNumber));
@@ -1538,7 +1537,7 @@
@Override
public void invalidate(boolean invalidateCache) {
super.invalidate(invalidateCache);
- if (!WindowManagerGlobal.USE_BLAST_ADAPTER) {
+ if (!WindowManagerGlobal.getInstance().useBLAST()) {
return;
}
final ViewRootImpl viewRoot = getViewRootImpl();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 159b93e..ebfe66f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -315,6 +315,8 @@
*/
private boolean mForceNextConfigUpdate;
+ private final boolean mUseBLASTAdapter;
+
/**
* Signals that compatibility booleans have been initialized according to
* target SDK versions.
@@ -734,6 +736,7 @@
loadSystemProperties();
mImeFocusController = new ImeFocusController(this);
+ mUseBLASTAdapter = WindowManagerGlobal.getInstance().useBLAST();
}
public static void addFirstDrawHandler(Runnable callback) {
@@ -861,7 +864,7 @@
if (mWindowAttributes.packageName == null) {
mWindowAttributes.packageName = mBasePackageName;
}
- if (WindowManagerGlobal.USE_BLAST_ADAPTER) {
+ if (mUseBLASTAdapter) {
mWindowAttributes.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
}
@@ -1341,7 +1344,7 @@
}
mWindowAttributes.privateFlags |= compatibleWindowFlag;
- if (WindowManagerGlobal.USE_BLAST_ADAPTER) {
+ if (mUseBLASTAdapter) {
mWindowAttributes.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
}
@@ -7342,7 +7345,7 @@
mPendingMergedConfiguration, mSurfaceControl, mTempInsets, mSurfaceSize,
mBlastSurfaceControl);
if (mSurfaceControl.isValid()) {
- if (!WindowManagerGlobal.USE_BLAST_ADAPTER) {
+ if (!mUseBLASTAdapter) {
mSurface.copyFrom(mSurfaceControl);
} else {
mSurface.transferFrom(getOrCreateBLASTSurface(mSurfaceSize.x,
@@ -9537,7 +9540,7 @@
}
SurfaceControl getRenderSurfaceControl() {
- if (WindowManagerGlobal.USE_BLAST_ADAPTER) {
+ if (mUseBLASTAdapter) {
return mBlastSurfaceControl;
} else {
return mSurfaceControl;
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index f501de9..ea8e7388 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -16,8 +16,6 @@
package android.view;
-import static android.view.WindowInsets.Type.ime;
-
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -148,7 +146,7 @@
* @param types The {@link InsetsType}s the application has requested to control.
* @param durationMillis Duration of animation in
* {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the
- * animation doesn't have a predetermined duration.T his value will be
+ * animation doesn't have a predetermined duration. This value will be
* passed to {@link InsetsAnimation#getDurationMillis()}
* @param interpolator The interpolator used for this animation, or {@code null} if this
* animation doesn't follow an interpolation curve. This value will be
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index f03c4e7..c22b892 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -56,13 +56,7 @@
public final class WindowManagerGlobal {
private static final String TAG = "WindowManager";
- private static final String WM_USE_BLAST_ADAPTER_FLAG = "wm_use_blast_adapter";
-
- /**
- * This flag controls whether ViewRootImpl will utilize the Blast Adapter
- * to send buffer updates to SurfaceFlinger
- */
- public static final boolean USE_BLAST_ADAPTER = false;
+ private final boolean mUseBLASTAdapter;
/**
* The user is navigating with keys (not the touch screen), so
@@ -165,6 +159,11 @@
private Runnable mSystemPropertyUpdater;
private WindowManagerGlobal() {
+ try {
+ mUseBLASTAdapter = getWindowManagerService().useBLAST();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
@UnsupportedAppUsage
@@ -233,6 +232,13 @@
}
}
+ /**
+ * Whether or not to use BLAST for ViewRootImpl
+ */
+ public boolean useBLAST() {
+ return mUseBLASTAdapter;
+ }
+
@UnsupportedAppUsage
public String[] getViewRootNames() {
synchronized (mLock) {
diff --git a/core/java/android/view/textclassifier/TextClassificationSession.java b/core/java/android/view/textclassifier/TextClassificationSession.java
index 4329a20..fed3dbf 100644
--- a/core/java/android/view/textclassifier/TextClassificationSession.java
+++ b/core/java/android/view/textclassifier/TextClassificationSession.java
@@ -16,6 +16,7 @@
package android.view.textclassifier;
+import android.annotation.NonNull;
import android.annotation.WorkerThread;
import android.view.textclassifier.SelectionEvent.InvocationMethod;
@@ -23,6 +24,8 @@
import java.util.Objects;
+import sun.misc.Cleaner;
+
/**
* Session-aware TextClassifier.
*/
@@ -35,6 +38,7 @@
private final SelectionEventHelper mEventHelper;
private final TextClassificationSessionId mSessionId;
private final TextClassificationContext mClassificationContext;
+ private final Cleaner mCleaner;
private boolean mDestroyed;
@@ -44,6 +48,8 @@
mSessionId = new TextClassificationSessionId();
mEventHelper = new SelectionEventHelper(mSessionId, mClassificationContext);
initializeRemoteSession();
+ // This ensures destroy() is called if the client forgot to do so.
+ mCleaner = Cleaner.create(this, new CleanerRunnable(mEventHelper, mDelegate));
}
@Override
@@ -114,8 +120,7 @@
@Override
public void destroy() {
- mEventHelper.endSession();
- mDelegate.destroy();
+ mCleaner.clean();
mDestroyed = true;
}
@@ -258,4 +263,25 @@
}
}
}
+
+ // We use a static nested class here to avoid retaining the object reference of the outer
+ // class. Otherwise. the Cleaner would never be triggered.
+ private static class CleanerRunnable implements Runnable {
+ @NonNull
+ private final SelectionEventHelper mEventHelper;
+ @NonNull
+ private final TextClassifier mDelegate;
+
+ CleanerRunnable(
+ @NonNull SelectionEventHelper eventHelper, @NonNull TextClassifier delegate) {
+ mEventHelper = Objects.requireNonNull(eventHelper);
+ mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ @Override
+ public void run() {
+ mEventHelper.endSession();
+ mDelegate.destroy();
+ }
+ }
}
diff --git a/core/java/android/view/textclassifier/TextClassificationSessionId.java b/core/java/android/view/textclassifier/TextClassificationSessionId.java
index f90e6b2..0b6fba2 100644
--- a/core/java/android/view/textclassifier/TextClassificationSessionId.java
+++ b/core/java/android/view/textclassifier/TextClassificationSessionId.java
@@ -17,6 +17,8 @@
package android.view.textclassifier;
import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -28,7 +30,10 @@
* This class represents the id of a text classification session.
*/
public final class TextClassificationSessionId implements Parcelable {
- private final @NonNull String mValue;
+ @NonNull
+ private final String mValue;
+ @NonNull
+ private final IBinder mToken;
/**
* Creates a new instance.
@@ -36,7 +41,7 @@
* @hide
*/
public TextClassificationSessionId() {
- this(UUID.randomUUID().toString());
+ this(UUID.randomUUID().toString(), new Binder());
}
/**
@@ -46,34 +51,28 @@
*
* @hide
*/
- public TextClassificationSessionId(@NonNull String value) {
- mValue = value;
+ public TextClassificationSessionId(@NonNull String value, @NonNull IBinder token) {
+ mValue = Objects.requireNonNull(value);
+ mToken = Objects.requireNonNull(token);
+ }
+
+ /** @hide */
+ @NonNull
+ public IBinder getToken() {
+ return mToken;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ TextClassificationSessionId that = (TextClassificationSessionId) o;
+ return Objects.equals(mValue, that.mValue) && Objects.equals(mToken, that.mToken);
}
@Override
public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + mValue.hashCode();
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- TextClassificationSessionId other = (TextClassificationSessionId) obj;
- if (!mValue.equals(other.mValue)) {
- return false;
- }
- return true;
+ return Objects.hash(mValue, mToken);
}
@Override
@@ -84,6 +83,7 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeString(mValue);
+ parcel.writeStrongBinder(mToken);
}
@Override
@@ -96,28 +96,18 @@
*
* @return The flattened id.
*/
- public @NonNull String flattenToString() {
+ @NonNull
+ public String flattenToString() {
return mValue;
}
- /**
- * Unflattens a print job id from a string.
- *
- * @param string The string.
- * @return The unflattened id, or null if the string is malformed.
- *
- * @hide
- */
- public static @NonNull TextClassificationSessionId unflattenFromString(@NonNull String string) {
- return new TextClassificationSessionId(string);
- }
-
- public static final @android.annotation.NonNull Parcelable.Creator<TextClassificationSessionId> CREATOR =
+ @NonNull
+ public static final Parcelable.Creator<TextClassificationSessionId> CREATOR =
new Parcelable.Creator<TextClassificationSessionId>() {
@Override
public TextClassificationSessionId createFromParcel(Parcel parcel) {
return new TextClassificationSessionId(
- Objects.requireNonNull(parcel.readString()));
+ parcel.readString(), parcel.readStrongBinder());
}
@Override
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp
index 571a3387..c49c3a0 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/LayoutlibLoader.cpp
@@ -40,6 +40,7 @@
extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env);
extern int register_android_graphics_Graphics(JNIEnv* env);
extern int register_android_graphics_ImageDecoder(JNIEnv*);
+extern int register_android_graphics_Interpolator(JNIEnv* env);
extern int register_android_graphics_MaskFilter(JNIEnv* env);
extern int register_android_graphics_NinePatch(JNIEnv*);
extern int register_android_graphics_PathEffect(JNIEnv* env);
@@ -115,6 +116,7 @@
{"android.graphics.FontFamily", REG_JNI(register_android_graphics_FontFamily)},
{"android.graphics.Graphics", REG_JNI(register_android_graphics_Graphics)},
{"android.graphics.ImageDecoder", REG_JNI(register_android_graphics_ImageDecoder)},
+ {"android.graphics.Interpolator", REG_JNI(register_android_graphics_Interpolator)},
{"android.graphics.MaskFilter", REG_JNI(register_android_graphics_MaskFilter)},
{"android.graphics.Matrix", REG_JNI(register_android_graphics_Matrix)},
{"android.graphics.NinePatch", REG_JNI(register_android_graphics_NinePatch)},
diff --git a/core/jni/android_os_incremental_IncrementalManager.cpp b/core/jni/android_os_incremental_IncrementalManager.cpp
index d41e982..44bff01 100644
--- a/core/jni/android_os_incremental_IncrementalManager.cpp
+++ b/core/jni/android_os_incremental_IncrementalManager.cpp
@@ -37,10 +37,28 @@
return (jboolean)IncFs_IsIncFsPath(path.c_str());
}
-static const JNINativeMethod method_table[] = {
- {"nativeIsEnabled", "()Z", (void*)nativeIsEnabled},
- {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z", (void*)nativeIsIncrementalPath},
-};
+static jbyteArray nativeUnsafeGetFileSignature(JNIEnv* env, jobject clazz, jstring javaPath) {
+ ScopedUtfChars path(env, javaPath);
+
+ char signature[INCFS_MAX_SIGNATURE_SIZE];
+ size_t size = sizeof(signature);
+ if (IncFs_UnsafeGetSignatureByPath(path.c_str(), signature, &size) < 0) {
+ return nullptr;
+ }
+
+ jbyteArray result = env->NewByteArray(size);
+ if (result != nullptr) {
+ env->SetByteArrayRegion(result, 0, size, (const jbyte*)signature);
+ }
+ return result;
+}
+
+static const JNINativeMethod method_table[] = {{"nativeIsEnabled", "()Z", (void*)nativeIsEnabled},
+ {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z",
+ (void*)nativeIsIncrementalPath},
+ {"nativeUnsafeGetFileSignature",
+ "(Ljava/lang/String;)[B",
+ (void*)nativeUnsafeGetFileSignature}};
int register_android_os_incremental_IncrementalManager(JNIEnv* env) {
return jniRegisterNativeMethods(env, "android/os/incremental/IncrementalManager",
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 92941b8..7290e30 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -611,6 +611,12 @@
// Set the jemalloc decay time to 1.
mallopt(M_DECAY_TIME, 1);
+
+ // Maybe initialize GWP-ASan here. Must be called after
+ // mallopt(M_SET_ZYGOTE_CHILD).
+ bool ForceEnableGwpAsan = false;
+ android_mallopt(M_INITIALIZE_GWP_ASAN, &ForceEnableGwpAsan,
+ sizeof(ForceEnableGwpAsan));
}
static void SetUpSeccompFilter(uid_t uid, bool is_child_zygote) {
diff --git a/core/proto/android/content/locusid.proto b/core/proto/android/content/locusid.proto
new file mode 100644
index 0000000..4f0ce6b
--- /dev/null
+++ b/core/proto/android/content/locusid.proto
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+syntax = "proto2";
+
+package android.content;
+
+option java_multiple_files = true;
+
+// On disk representation of android.content.LocusId. Currently used by
+// com.android.server.people.ConversationInfoProto.
+message LocusIdProto {
+ optional string locus_id = 1;
+}
diff --git a/core/proto/android/server/peopleservice.proto b/core/proto/android/server/peopleservice.proto
new file mode 100644
index 0000000..294b6ef
--- /dev/null
+++ b/core/proto/android/server/peopleservice.proto
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+syntax = "proto2";
+
+package com.android.server.people;
+
+option java_multiple_files = true;
+
+import "frameworks/base/core/proto/android/content/locusid.proto";
+
+// On disk data of conversation infos for a user and app package.
+message ConversationInfosProto {
+
+ // The series of conversation infos for a user and app package.
+ repeated ConversationInfoProto conversation_infos = 1;
+}
+
+// Individual conversation info (com.android.server.people.data.ConversationInfo) for a user
+// and app package.
+message ConversationInfoProto {
+
+ // The conversation's shortcut id.
+ optional string shortcut_id = 1;
+
+ // The conversation's locus id.
+ optional .android.content.LocusIdProto locus_id_proto = 2;
+
+ // The URI of the contact in the conversation.
+ optional string contact_uri = 3;
+
+ // The notification channel id of the conversation.
+ optional string notification_channel_id = 4;
+
+ // Integer representation of shortcut bit flags.
+ optional int32 shortcut_flags = 5;
+
+ // Integer representation of conversation bit flags.
+ optional int32 conversation_flags = 6;
+}
+
+// Individual event (com.android.server.people.data.Event).
+message PeopleEventProto {
+
+ // For valid values, refer to java class documentation.
+ optional int32 event_type = 1;
+
+ optional int64 time = 2;
+
+ // The duration of the event. Should only be set for some event_types. Refer to java class
+ // documentation for details.
+ optional int32 duration = 3;
+}
+
+// Index of events' time distributions (com.android.server.people.data.EventIndex).
+message PeopleEventIndexProto {
+ // Each long value in event_bitmaps represents a time slot, there should be 4 values. Further
+ // details can be found in class documentation.
+ repeated int64 event_bitmaps = 1;
+
+ optional int64 last_updated_time = 2;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ff69671..71a42e4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -96,6 +96,7 @@
<protected-broadcast android:name="android.intent.action.OVERLAY_PRIORITY_CHANGED" />
<protected-broadcast android:name="android.intent.action.MY_PACKAGE_SUSPENDED" />
<protected-broadcast android:name="android.intent.action.MY_PACKAGE_UNSUSPENDED" />
+ <protected-broadcast android:name="android.intent.action.LOAD_DATA" />
<protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED" />
<protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGING" />
@@ -2376,6 +2377,7 @@
@hide -->
<permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
android:protectionLevel="signature|installer|telephony" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<!-- Allows interaction across profiles in the same profile group. -->
<permission android:name="android.permission.INTERACT_ACROSS_PROFILES"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 20901e0..7d8b8db 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3790,6 +3790,12 @@
<attr name="description" />
<!-- Brief summary of the target of accessibility shortcut purpose or behavior. -->
<attr name="summary" />
+ <!-- Animated image of the target of accessibility shortcut purpose or behavior, to help
+ users understand how the target of accessibility shortcut can help them.-->
+ <attr name="animatedImageDrawable" format="reference"/>
+ <!-- Html description of the target of accessibility shortcut purpose or behavior, to help
+ users understand how the target of accessibility shortcut can help them. -->
+ <attr name="htmlDescription" format="string"/>
</declare-styleable>
<!-- Use <code>print-service</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f17cd45..df63306 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2351,7 +2351,7 @@
type. These are flags and can be freely combined.
0 - disable whitelist (install all system packages; no logging)
1 - enforce (only install system packages if they are whitelisted)
- 2 - log (log when a non-whitelisted package is run)
+ 2 - log (log non-whitelisted packages)
4 - any package not mentioned in the whitelist file is implicitly whitelisted on all users
8 - same as 4, but just for the SYSTEM user
16 - ignore OTAs (don't install system packages during OTAs)
@@ -3792,6 +3792,10 @@
or empty if the default should be used. -->
<string translatable="false" name="config_deviceSpecificAudioService"></string>
+ <!-- Class name of the device specific implementation of DisplayAreaPolicy.Provider
+ or empty if the default should be used. -->
+ <string translatable="false" name="config_deviceSpecificDisplayAreaPolicyProvider"></string>
+
<!-- Component name of media projection permission dialog -->
<string name="config_mediaProjectionPermissionDialogComponent" translatable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a54566c..08c8403 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -414,6 +414,14 @@
logging. [CHAR LIMIT=NONE]-->
<string name="network_logging_notification_text">Your organization manages this device and may monitor network traffic. Tap for details.</string>
+ <!-- Content title for a notification. This notification indicates that the device owner has
+ changed the location settings. [CHAR LIMIT=NONE] -->
+ <string name="location_changed_notification_title">Location settings changed by your admin</string>
+ <!-- Content text for a notification. Tapping opens device location settings.
+ [CHAR LIMIT=NONE] -->
+ <string name="location_changed_notification_text">Tap to see your location settings.</string>
+
+
<!-- Factory reset warning dialog strings--> <skip />
<!-- Shows up in the dialog's title to warn about an impeding factory reset. [CHAR LIMIT=NONE] -->
<string name="factory_reset_warning">Your device will be erased</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 2dd62c1..0babe48 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1213,6 +1213,8 @@
<java-symbol type="string" name="device_ownership_relinquished" />
<java-symbol type="string" name="network_logging_notification_title" />
<java-symbol type="string" name="network_logging_notification_text" />
+ <java-symbol type="string" name="location_changed_notification_title" />
+ <java-symbol type="string" name="location_changed_notification_text" />
<java-symbol type="string" name="personal_apps_suspended_notification_title" />
<java-symbol type="string" name="personal_apps_suspended_notification_text" />
<java-symbol type="string" name="factory_reset_warning" />
@@ -3864,6 +3866,8 @@
<!-- Toast message for background started foreground service while-in-use permission restriction feature -->
<java-symbol type="string" name="allow_while_in_use_permission_in_fgs" />
+ <java-symbol type="string" name="config_deviceSpecificDisplayAreaPolicyProvider" />
+
<!-- Whether to expand the lock screen user switcher by default -->
<java-symbol type="bool" name="config_expandLockScreenUserSwitcher" />
</resources>
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 718ca46..59335a5 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1330,12 +1330,6 @@
android:process=":FakeProvider">
</provider>
- <provider
- android:name="android.content.SlowProvider"
- android:authorities="android.content.SlowProvider"
- android:process=":SlowProvider">
- </provider>
-
<!-- Application components used for os tests -->
<service android:name="android.os.MessengerService"
diff --git a/core/tests/coretests/res/values/strings.xml b/core/tests/coretests/res/values/strings.xml
index f630188..21613a8 100644
--- a/core/tests/coretests/res/values/strings.xml
+++ b/core/tests/coretests/res/values/strings.xml
@@ -148,4 +148,7 @@
<!-- Summary of the accessibility shortcut [CHAR LIMIT=NONE] -->
<string name="accessibility_shortcut_summary">Accessibility shortcut summary</string>
+
+ <!-- Html description of the accessibility shortcut [CHAR LIMIT=NONE] -->
+ <string name="accessibility_shortcut_html_description">Accessibility shortcut html description</string>
</resources>
diff --git a/core/tests/coretests/res/xml/accessibility_shortcut_test_activity.xml b/core/tests/coretests/res/xml/accessibility_shortcut_test_activity.xml
index 60e2998..a597b71 100644
--- a/core/tests/coretests/res/xml/accessibility_shortcut_test_activity.xml
+++ b/core/tests/coretests/res/xml/accessibility_shortcut_test_activity.xml
@@ -19,4 +19,6 @@
<accessibility-shortcut-target xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/accessibility_shortcut_description"
android:summary="@string/accessibility_shortcut_summary"
+ android:animatedImageDrawable="@drawable/bitmap_drawable"
+ android:htmlDescription="@string/accessibility_shortcut_html_description"
/>
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java b/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
index ae6d8df..82a7b2c 100644
--- a/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
+++ b/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
@@ -84,6 +84,22 @@
}
@Test
+ public void testAnimatedImageRes() {
+ assertThat("Animated image resource id is not correct",
+ mShortcutInfo.getAnimatedImageRes(), is(R.drawable.bitmap_drawable));
+ }
+
+ @Test
+ public void testHtmlDescription() {
+ final String htmlDescription = mTargetContext.getResources()
+ .getString(R.string.accessibility_shortcut_html_description);
+
+ assertNotNull("Can't find html description string", htmlDescription);
+ assertThat("Html description is not correct",
+ mShortcutInfo.loadHtmlDescription(mPackageManager), is(htmlDescription));
+ }
+
+ @Test
public void testEquals() {
assertTrue(mShortcutInfo.equals(mShortcutInfo));
assertFalse(mShortcutInfo.equals(null));
diff --git a/core/tests/coretests/src/android/content/ContentResolverTest.java b/core/tests/coretests/src/android/content/ContentResolverTest.java
index 6dc7392..9dcce1e 100644
--- a/core/tests/coretests/src/android/content/ContentResolverTest.java
+++ b/core/tests/coretests/src/android/content/ContentResolverTest.java
@@ -209,13 +209,4 @@
String type = mResolver.getType(Uri.parse("content://android.content.FakeProviderRemote"));
assertEquals("fake/remote", type);
}
-
-
- @Test
- public void testGetType_slowProvider() {
- // This provider is running in a different process and is intentionally slow to start.
- // We are trying to confirm that it does not cause an ANR
- String type = mResolver.getType(Uri.parse("content://android.content.SlowProvider"));
- assertEquals("slow", type);
- }
}
diff --git a/core/tests/coretests/src/android/content/SlowProvider.java b/core/tests/coretests/src/android/content/SlowProvider.java
deleted file mode 100644
index aba32e8..0000000
--- a/core/tests/coretests/src/android/content/SlowProvider.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2020 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.content;
-
-import android.database.Cursor;
-import android.net.Uri;
-
-/**
- * A dummy content provider for tests. This provider runs in a different process from the test and
- * is intentionally slow.
- */
-public class SlowProvider extends ContentProvider {
-
- private static final int ON_CREATE_LATENCY_MILLIS = 3000;
-
- @Override
- public boolean onCreate() {
- try {
- Thread.sleep(ON_CREATE_LATENCY_MILLIS);
- } catch (InterruptedException e) {
- // Ignore
- }
- return true;
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
- return null;
- }
-
- @Override
- public String getType(Uri uri) {
- return "slow";
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- return null;
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- return 0;
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- return 0;
- }
-}
diff --git a/core/tests/coretests/src/android/content/res/ConfigurationTest.java b/core/tests/coretests/src/android/content/res/ConfigurationTest.java
index 2c956c9..669138c 100644
--- a/core/tests/coretests/src/android/content/res/ConfigurationTest.java
+++ b/core/tests/coretests/src/android/content/res/ConfigurationTest.java
@@ -148,6 +148,15 @@
assertEquals(SMALLEST_SCREEN_WIDTH_DP_UNDEFINED, config.smallestScreenWidthDp);
}
+ @Test
+ public void testNightModeHelper() {
+ Configuration config = new Configuration();
+ config.uiMode = Configuration.UI_MODE_NIGHT_YES;
+ assertTrue(config.isNightModeActive());
+ config.uiMode = Configuration.UI_MODE_NIGHT_NO;
+ assertFalse(config.isNightModeActive());
+ }
+
private void dumpDebug(File f, Configuration config) throws Exception {
final AtomicFile af = new AtomicFile(f);
FileOutputStream fos = af.startWrite();
diff --git a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
index 49fb75b..b8dbfd3 100644
--- a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
+++ b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java
@@ -466,179 +466,7 @@
// expected
}
}
-
- @MediumTest
- public void testTokenize() throws Exception {
- Cursor c;
- mDatabase.execSQL("CREATE TABLE tokens (" +
- "token TEXT COLLATE unicode," +
- "source INTEGER," +
- "token_index INTEGER," +
- "tag TEXT" +
- ");");
- mDatabase.execSQL("CREATE TABLE tokens_no_index (" +
- "token TEXT COLLATE unicode," +
- "source INTEGER" +
- ");");
-
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT _TOKENIZE(NULL, NULL, NULL, NULL)", null));
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT _TOKENIZE('tokens', NULL, NULL, NULL)", null));
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT _TOKENIZE('tokens', 10, NULL, NULL)", null));
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT _TOKENIZE('tokens', 10, 'some string', NULL)", null));
-
- Assert.assertEquals(3, DatabaseUtils.longForQuery(mDatabase,
- "SELECT _TOKENIZE('tokens', 11, 'some string ok', ' ', 1, 'foo')", null));
- Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
- "SELECT _TOKENIZE('tokens', 11, 'second field', ' ', 1, 'bar')", null));
- Assert.assertEquals(3, DatabaseUtils.longForQuery(mDatabase,
- "SELECT _TOKENIZE('tokens_no_index', 20, 'some string ok', ' ')", null));
- Assert.assertEquals(3, DatabaseUtils.longForQuery(mDatabase,
- "SELECT _TOKENIZE('tokens_no_index', 21, 'foo bar baz', ' ', 0)", null));
-
- // test Chinese
- String chinese = new String("\u4eac\u4ec5 \u5c3d\u5f84\u60ca");
- Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
- "SELECT _TOKENIZE('tokens', 12,'" + chinese + "', ' ', 1)", null));
-
- String icustr = new String("Fr\u00e9d\u00e9ric Hj\u00f8nnev\u00e5g");
-
- Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
- "SELECT _TOKENIZE('tokens', 13, '" + icustr + "', ' ', 1)", null));
-
- Assert.assertEquals(9, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens;", null));
-
- String key = DatabaseUtils.getHexCollationKey("Frederic Hjonneva");
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(13, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
- key = DatabaseUtils.getHexCollationKey("Hjonneva");
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(13, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
-
- key = DatabaseUtils.getHexCollationKey("some string ok");
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals("foo", DatabaseUtils.stringForQuery(mDatabase,
- "SELECT tag from tokens where token GLOB '" + key + "*'", null));
- key = DatabaseUtils.getHexCollationKey("string");
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals("foo", DatabaseUtils.stringForQuery(mDatabase,
- "SELECT tag from tokens where token GLOB '" + key + "*'", null));
- key = DatabaseUtils.getHexCollationKey("ok");
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals("foo", DatabaseUtils.stringForQuery(mDatabase,
- "SELECT tag from tokens where token GLOB '" + key + "*'", null));
-
- key = DatabaseUtils.getHexCollationKey("second field");
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals("bar", DatabaseUtils.stringForQuery(mDatabase,
- "SELECT tag from tokens where token GLOB '" + key + "*'", null));
- key = DatabaseUtils.getHexCollationKey("field");
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals("bar", DatabaseUtils.stringForQuery(mDatabase,
- "SELECT tag from tokens where token GLOB '" + key + "*'", null));
-
- key = DatabaseUtils.getHexCollationKey(chinese);
- String[] a = new String[1];
- a[0] = key;
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token= ?", a));
- Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token= ?", a));
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token= ?", a));
- a[0] += "*";
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB ?", a));
- Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token GLOB ?", a));
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token GLOB ?", a));
-
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token= '" + key + "'", null));
- Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token= '" + key + "'", null));
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token= '" + key + "'", null));
-
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
-
- key = DatabaseUtils.getHexCollationKey("\u4eac\u4ec5");
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
-
- key = DatabaseUtils.getHexCollationKey("\u5c3d\u5f84\u60ca");
- Log.d("DatabaseGeneralTest", "key = " + key);
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens where token GLOB '" + key + "*'", null));
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT token_index from tokens where token GLOB '" + key + "*'", null));
-
- Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens where token GLOB 'ab*'", null));
-
- key = DatabaseUtils.getHexCollationKey("some string ok");
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens_no_index where token GLOB '" + key + "*'", null));
- Assert.assertEquals(20, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens_no_index where token GLOB '" + key + "*'", null));
-
- key = DatabaseUtils.getHexCollationKey("bar");
- Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
- "SELECT count(*) from tokens_no_index where token GLOB '" + key + "*'", null));
- Assert.assertEquals(21, DatabaseUtils.longForQuery(mDatabase,
- "SELECT source from tokens_no_index where token GLOB '" + key + "*'", null));
- }
-
@MediumTest
public void testTransactions() throws Exception {
mDatabase.execSQL("CREATE TABLE test (num INTEGER);");
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
index a2d2355..8dbb5f5 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -206,6 +206,7 @@
blockModes,
userAuthenticationRequired,
(int) userAuthenticationValidityDurationSeconds,
+ keymasterHwEnforcedUserAuthenticators,
userAuthenticationRequirementEnforcedBySecureHardware,
userAuthenticationValidWhileOnBody,
trustedUserPresenceRequred,
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 450dd33..d683041 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -263,6 +263,7 @@
private final boolean mRandomizedEncryptionRequired;
private final boolean mUserAuthenticationRequired;
private final int mUserAuthenticationValidityDurationSeconds;
+ private final @KeyProperties.AuthEnum int mUserAuthenticationType;
private final boolean mUserPresenceRequired;
private final byte[] mAttestationChallenge;
private final boolean mUniqueIdIncluded;
@@ -301,6 +302,7 @@
boolean randomizedEncryptionRequired,
boolean userAuthenticationRequired,
int userAuthenticationValidityDurationSeconds,
+ @KeyProperties.AuthEnum int userAuthenticationType,
boolean userPresenceRequired,
byte[] attestationChallenge,
boolean uniqueIdIncluded,
@@ -352,6 +354,7 @@
mUserAuthenticationRequired = userAuthenticationRequired;
mUserPresenceRequired = userPresenceRequired;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
+ mUserAuthenticationType = userAuthenticationType;
mAttestationChallenge = Utils.cloneIfNotNull(attestationChallenge);
mUniqueIdIncluded = uniqueIdIncluded;
mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
@@ -605,6 +608,22 @@
}
/**
+ * Gets the modes of authentication that can authorize use of this key. This has effect only if
+ * user authentication is required (see {@link #isUserAuthenticationRequired()}).
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
+ *
+ * @return integer representing the bitwse OR of all acceptable authentication types for the
+ * key.
+ *
+ * @see #isUserAuthenticationRequired()
+ * @see Builder#setUserAuthenticationParameters(int, int)
+ */
+ public @KeyProperties.AuthEnum int getUserAuthenticationType() {
+ return mUserAuthenticationType;
+ }
+ /**
* Returns {@code true} if the key is authorized to be used only if a test of user presence has
* been performed between the {@code Signature.initSign()} and {@code Signature.sign()} calls.
* It requires that the KeyStore implementation have a direct way to validate the user presence
@@ -746,6 +765,7 @@
private boolean mRandomizedEncryptionRequired = true;
private boolean mUserAuthenticationRequired;
private int mUserAuthenticationValidityDurationSeconds = -1;
+ private @KeyProperties.AuthEnum int mUserAuthenticationType;
private boolean mUserPresenceRequired = false;
private byte[] mAttestationChallenge = null;
private boolean mUniqueIdIncluded = false;
@@ -810,6 +830,7 @@
mUserAuthenticationRequired = sourceSpec.isUserAuthenticationRequired();
mUserAuthenticationValidityDurationSeconds =
sourceSpec.getUserAuthenticationValidityDurationSeconds();
+ mUserAuthenticationType = sourceSpec.getUserAuthenticationType();
mUserPresenceRequired = sourceSpec.isUserPresenceRequired();
mAttestationChallenge = sourceSpec.getAttestationChallenge();
mUniqueIdIncluded = sourceSpec.isUniqueIdIncluded();
@@ -1207,14 +1228,62 @@
* @see BiometricPrompt
* @see BiometricPrompt.CryptoObject
* @see KeyguardManager
+ * @deprecated See {@link #setUserAuthenticationParameters(int, int)}
*/
+ @Deprecated
@NonNull
public Builder setUserAuthenticationValidityDurationSeconds(
@IntRange(from = -1) int seconds) {
if (seconds < -1) {
throw new IllegalArgumentException("seconds must be -1 or larger");
}
- mUserAuthenticationValidityDurationSeconds = seconds;
+ if (seconds == -1) {
+ return setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG);
+ }
+ return setUserAuthenticationParameters(seconds, KeyProperties.AUTH_BIOMETRIC_STRONG);
+ }
+
+ /**
+ * Sets the duration of time (seconds) and authorization type for which this key is
+ * authorized to be used after the user is successfully authenticated. This has effect if
+ * the key requires user authentication for its use (see
+ * {@link #setUserAuthenticationRequired(boolean)}).
+ *
+ * <p>By default, if user authentication is required, it must take place for every use of
+ * the key.
+ *
+ * <p>These cryptographic operations will throw {@link UserNotAuthenticatedException} during
+ * initialization if the user needs to be authenticated to proceed. This situation can be
+ * resolved by the user authenticating with the appropriate biometric or credential as
+ * required by the key. See {@link BiometricPrompt.Builder#setAllowedAuthenticators(int)}
+ * and {@link BiometricManager.Authenticators}.
+ *
+ * <p>Once resolved, initializing a new cryptographic operation using this key (or any other
+ * key which is authorized to be used for a fixed duration of time after user
+ * authentication) should succeed provided the user authentication flow completed
+ * successfully.
+ *
+ * @param timeout duration in seconds or {@code 0} if user authentication must take place
+ * for every use of the key. {@code -1} is also accepted for legacy purposes. It is
+ * functionally the same as {@code 0}.
+ * @param type set of authentication types which can authorize use of the key. See
+ * {@link KeyProperties}.{@code AUTH} flags.
+ *
+ * @see #setUserAuthenticationRequired(boolean)
+ * @see BiometricPrompt
+ * @see BiometricPrompt.CryptoObject
+ * @see KeyguardManager
+ */
+ @NonNull
+ public Builder setUserAuthenticationParameters(@IntRange(from = -1) int timeout,
+ @KeyProperties.AuthEnum int type) {
+ if (timeout < -1) {
+ throw new IllegalArgumentException("timeout must be -1 or larger");
+ } else if (timeout == -1) {
+ timeout = 0;
+ }
+ mUserAuthenticationValidityDurationSeconds = timeout;
+ mUserAuthenticationType = type;
return this;
}
@@ -1392,6 +1461,7 @@
mRandomizedEncryptionRequired,
mUserAuthenticationRequired,
mUserAuthenticationValidityDurationSeconds,
+ mUserAuthenticationType,
mUserPresenceRequired,
mAttestationChallenge,
mUniqueIdIncluded,
diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java
index 0a75cd5..d891a25 100644
--- a/keystore/java/android/security/keystore/KeyInfo.java
+++ b/keystore/java/android/security/keystore/KeyInfo.java
@@ -78,6 +78,7 @@
private final @KeyProperties.BlockModeEnum String[] mBlockModes;
private final boolean mUserAuthenticationRequired;
private final int mUserAuthenticationValidityDurationSeconds;
+ private final @KeyProperties.AuthEnum int mUserAuthenticationType;
private final boolean mUserAuthenticationRequirementEnforcedBySecureHardware;
private final boolean mUserAuthenticationValidWhileOnBody;
private final boolean mTrustedUserPresenceRequired;
@@ -101,6 +102,7 @@
@KeyProperties.BlockModeEnum String[] blockModes,
boolean userAuthenticationRequired,
int userAuthenticationValidityDurationSeconds,
+ @KeyProperties.AuthEnum int userAuthenticationType,
boolean userAuthenticationRequirementEnforcedBySecureHardware,
boolean userAuthenticationValidWhileOnBody,
boolean trustedUserPresenceRequired,
@@ -122,6 +124,7 @@
mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes));
mUserAuthenticationRequired = userAuthenticationRequired;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
+ mUserAuthenticationType = userAuthenticationType;
mUserAuthenticationRequirementEnforcedBySecureHardware =
userAuthenticationRequirementEnforcedBySecureHardware;
mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
@@ -301,6 +304,22 @@
}
/**
+ * Gets the acceptable user authentication types for which this key can be authorized to be
+ * used. This has effect only if user authentication is required (see
+ * {@link #isUserAuthenticationRequired()}).
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
+ *
+ * @return integer representing the accepted forms of user authentication for this key
+ *
+ * @see #isUserAuthenticationRequired()
+ */
+ public @KeyProperties.AuthEnum int getUserAuthenticationType() {
+ return mUserAuthenticationType;
+ }
+
+ /**
* Returns {@code true} if the requirement that this key can only be used if the user has been
* authenticated is enforced by secure hardware (e.g., Trusted Execution Environment (TEE) or
* Secure Element (SE)).
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index f12a659..c58a123 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -39,6 +39,27 @@
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "AUTH_" }, value = {
+ AUTH_BIOMETRIC_STRONG,
+ AUTH_DEVICE_CREDENTIAL,
+ })
+ public @interface AuthEnum {}
+
+ /**
+ * The non-biometric credential used to secure the device (i.e., PIN, pattern, or password)
+ */
+ public static final int AUTH_DEVICE_CREDENTIAL = 1 << 0;
+
+ /**
+ * Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the
+ * requirements for <strong>Strong</strong>, as defined by the Android CDD.
+ */
+ public static final int AUTH_BIOMETRIC_STRONG = 1 << 1;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "PURPOSE_" }, value = {
PURPOSE_ENCRYPT,
PURPOSE_DECRYPT,
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index 26181a6..e230b7c 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -225,6 +225,7 @@
private final @KeyProperties.BlockModeEnum String[] mBlockModes;
private final boolean mRandomizedEncryptionRequired;
private final boolean mUserAuthenticationRequired;
+ private final @KeyProperties.AuthEnum int mUserAuthenticationType;
private final int mUserAuthenticationValidityDurationSeconds;
private final boolean mUserPresenceRequred;
private final boolean mUserAuthenticationValidWhileOnBody;
@@ -246,6 +247,7 @@
@KeyProperties.BlockModeEnum String[] blockModes,
boolean randomizedEncryptionRequired,
boolean userAuthenticationRequired,
+ @KeyProperties.AuthEnum int userAuthenticationType,
int userAuthenticationValidityDurationSeconds,
boolean userPresenceRequred,
boolean userAuthenticationValidWhileOnBody,
@@ -267,6 +269,7 @@
mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes));
mRandomizedEncryptionRequired = randomizedEncryptionRequired;
mUserAuthenticationRequired = userAuthenticationRequired;
+ mUserAuthenticationType = userAuthenticationType;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
mUserPresenceRequred = userPresenceRequred;
mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
@@ -429,6 +432,10 @@
return mUserConfirmationRequired;
}
+ public @KeyProperties.AuthEnum int getUserAuthenticationType() {
+ return mUserAuthenticationType;
+ }
+
/**
* Gets the duration of time (seconds) for which this key is authorized to be used after the
* user is successfully authenticated. This has effect only if user authentication is required
@@ -555,6 +562,7 @@
private @KeyProperties.BlockModeEnum String[] mBlockModes;
private boolean mRandomizedEncryptionRequired = true;
private boolean mUserAuthenticationRequired;
+ private @KeyProperties.AuthEnum int mUserAuthenticationType;
private int mUserAuthenticationValidityDurationSeconds = -1;
private boolean mUserPresenceRequired = false;
private boolean mUserAuthenticationValidWhileOnBody;
@@ -850,14 +858,62 @@
* @see BiometricPrompt
* @see BiometricPrompt.CryptoObject
* @see KeyguardManager
+ * @deprecated See {@link #setUserAuthenticationParameters(int, int)}
*/
+ @Deprecated
@NonNull
public Builder setUserAuthenticationValidityDurationSeconds(
@IntRange(from = -1) int seconds) {
if (seconds < -1) {
throw new IllegalArgumentException("seconds must be -1 or larger");
}
- mUserAuthenticationValidityDurationSeconds = seconds;
+ if (seconds == -1) {
+ return setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG);
+ }
+ return setUserAuthenticationParameters(seconds, KeyProperties.AUTH_BIOMETRIC_STRONG);
+ }
+
+ /**
+ * Sets the duration of time (seconds) and authorization type for which this key is
+ * authorized to be used after the user is successfully authenticated. This has effect if
+ * the key requires user authentication for its use (see
+ * {@link #setUserAuthenticationRequired(boolean)}).
+ *
+ * <p>By default, if user authentication is required, it must take place for every use of
+ * the key.
+ *
+ * <p>These cryptographic operations will throw {@link UserNotAuthenticatedException} during
+ * initialization if the user needs to be authenticated to proceed. This situation can be
+ * resolved by the user authenticating with the appropriate biometric or credential as
+ * required by the key. See {@link BiometricPrompt.Builder#setAllowedAuthenticators(int)}
+ * and {@link BiometricManager.Authenticators}.
+ *
+ * <p>Once resolved, initializing a new cryptographic operation using this key (or any other
+ * key which is authorized to be used for a fixed duration of time after user
+ * authentication) should succeed provided the user authentication flow completed
+ * successfully.
+ *
+ * @param timeout duration in seconds or {@code 0} if user authentication must take place
+ * for every use of the key. {@code -1} is also accepted for legacy purposes. It is
+ * functionally the same as {@code 0}.
+ * @param type set of authentication types which can authorize use of the key. See
+ * {@link KeyProperties}.{@code AUTH} flags.
+ *
+ * @see #setUserAuthenticationRequired(boolean)
+ * @see BiometricPrompt
+ * @see BiometricPrompt.CryptoObject
+ * @see KeyguardManager
+ */
+ @NonNull
+ public Builder setUserAuthenticationParameters(@IntRange(from = -1) int timeout,
+ @KeyProperties.AuthEnum int type) {
+ if (timeout < -1) {
+ throw new IllegalArgumentException("timeout must be -1 or larger");
+ } else if (timeout == -1) {
+ timeout = 0;
+ }
+ mUserAuthenticationValidityDurationSeconds = timeout;
+ mUserAuthenticationType = type;
return this;
}
@@ -1002,6 +1058,7 @@
mBlockModes,
mRandomizedEncryptionRequired,
mUserAuthenticationRequired,
+ mUserAuthenticationType,
mUserAuthenticationValidityDurationSeconds,
mUserPresenceRequired,
mUserAuthenticationValidWhileOnBody,
diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java
index 79e48cd..37b1f23 100644
--- a/keystore/java/android/security/keystore/KeymasterUtils.java
+++ b/keystore/java/android/security/keystore/KeymasterUtils.java
@@ -88,17 +88,9 @@
* Adds keymaster arguments to express the key's authorization policy supported by user
* authentication.
*
- * @param userAuthenticationRequired whether user authentication is required to authorize the
- * use of the key.
- * @param userAuthenticationValidityDurationSeconds duration of time (seconds) for which user
- * authentication is valid as authorization for using the key or {@code -1} if every
- * use of the key needs authorization.
- * @param boundToSpecificSecureUserId if non-zero, specify which SID the key will be bound to,
- * overriding the default logic in this method where the key is bound to either the root
- * SID of the current user, or the fingerprint SID if explicit fingerprint authorization
- * is requested.
- * @param userConfirmationRequired whether user confirmation is required to authorize the use
- * of the key.
+ * @param args The arguments sent to keymaster that need to be populated from the spec
+ * @param spec The user authentication relevant portions of the spec passed in from the caller.
+ * This spec will be translated into the relevant keymaster tags to be loaded into args.
* @throws IllegalStateException if user authentication is required but the system is in a wrong
* state (e.g., secure lock screen not set up) for generating or importing keys that
* require user authentication.
@@ -122,7 +114,7 @@
return;
}
- if (spec.getUserAuthenticationValidityDurationSeconds() == -1) {
+ if (spec.getUserAuthenticationValidityDurationSeconds() == 0) {
PackageManager pm = KeyStore.getApplicationContext().getPackageManager();
// Every use of this key needs to be authorized by the user. This currently means
// fingerprint or face auth.
@@ -168,7 +160,8 @@
args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
KeymasterArguments.toUint64(sids.get(i)));
}
- args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, KeymasterDefs.HW_AUTH_BIOMETRIC);
+
+ args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, spec.getUserAuthenticationType());
if (spec.isUserAuthenticationValidWhileOnBody()) {
throw new ProviderException("Key validity extension while device is on-body is not "
diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
index 98e4589..9c9773e 100644
--- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -97,6 +97,7 @@
out.writeBoolean(mSpec.isRandomizedEncryptionRequired());
out.writeBoolean(mSpec.isUserAuthenticationRequired());
out.writeInt(mSpec.getUserAuthenticationValidityDurationSeconds());
+ out.writeInt(mSpec.getUserAuthenticationType());
out.writeBoolean(mSpec.isUserPresenceRequired());
out.writeByteArray(mSpec.getAttestationChallenge());
out.writeBoolean(mSpec.isUniqueIdIncluded());
@@ -153,6 +154,7 @@
final boolean randomizedEncryptionRequired = in.readBoolean();
final boolean userAuthenticationRequired = in.readBoolean();
final int userAuthenticationValidityDurationSeconds = in.readInt();
+ final int userAuthenticationTypes = in.readInt();
final boolean userPresenceRequired = in.readBoolean();
final byte[] attestationChallenge = in.createByteArray();
final boolean uniqueIdIncluded = in.readBoolean();
@@ -185,6 +187,7 @@
randomizedEncryptionRequired,
userAuthenticationRequired,
userAuthenticationValidityDurationSeconds,
+ userAuthenticationTypes,
userPresenceRequired,
attestationChallenge,
uniqueIdIncluded,
diff --git a/keystore/java/android/security/keystore/UserAuthArgs.java b/keystore/java/android/security/keystore/UserAuthArgs.java
index 6952060..c9e9bf0 100644
--- a/keystore/java/android/security/keystore/UserAuthArgs.java
+++ b/keystore/java/android/security/keystore/UserAuthArgs.java
@@ -28,6 +28,7 @@
boolean isUserAuthenticationRequired();
int getUserAuthenticationValidityDurationSeconds();
+ @KeyProperties.AuthEnum int getUserAuthenticationType();
boolean isUserAuthenticationValidWhileOnBody();
boolean isInvalidatedByBiometricEnrollment();
boolean isUserConfirmationRequired();
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 8cfd2d8..3208662 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -992,6 +992,11 @@
return bag;
}
+static bool compare_bag_entries(const ResolvedBag::Entry& entry1,
+ const ResolvedBag::Entry& entry2) {
+ return entry1.key < entry2.key;
+}
+
const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& child_resids) {
auto cached_iter = cached_bags_.find(resid);
if (cached_iter != cached_bags_.end()) {
@@ -1027,13 +1032,15 @@
child_resids.push_back(resid);
uint32_t parent_resid = dtohl(map->parent.ident);
- if (parent_resid == 0 || std::find(child_resids.begin(), child_resids.end(), parent_resid)
+ if (parent_resid == 0U || std::find(child_resids.begin(), child_resids.end(), parent_resid)
!= child_resids.end()) {
- // There is no parent or that a circular dependency exist, meaning there is nothing to
- // inherit and we can do a simple copy of the entries in the map.
+ // There is no parent or a circular dependency exist, meaning there is nothing to inherit and
+ // we can do a simple copy of the entries in the map.
const size_t entry_count = map_entry_end - map_entry;
util::unique_cptr<ResolvedBag> new_bag{reinterpret_cast<ResolvedBag*>(
malloc(sizeof(ResolvedBag) + (entry_count * sizeof(ResolvedBag::Entry))))};
+
+ bool sort_entries = false;
ResolvedBag::Entry* new_entry = new_bag->entries;
for (; map_entry != map_entry_end; ++map_entry) {
uint32_t new_key = dtohl(map_entry->name.ident);
@@ -1059,8 +1066,15 @@
new_entry->value.data, new_key);
return nullptr;
}
+ sort_entries = sort_entries ||
+ (new_entry != new_bag->entries && (new_entry->key < (new_entry - 1U)->key));
++new_entry;
}
+
+ if (sort_entries) {
+ std::sort(new_bag->entries, new_bag->entries + entry_count, compare_bag_entries);
+ }
+
new_bag->type_spec_flags = entry.type_flags;
new_bag->entry_count = static_cast<uint32_t>(entry_count);
ResolvedBag* result = new_bag.get();
@@ -1091,6 +1105,7 @@
const ResolvedBag::Entry* const parent_entry_end = parent_entry + parent_bag->entry_count;
// The keys are expected to be in sorted order. Merge the two bags.
+ bool sort_entries = false;
while (map_entry != map_entry_end && parent_entry != parent_entry_end) {
uint32_t child_key = dtohl(map_entry->name.ident);
if (!is_internal_resid(child_key)) {
@@ -1123,6 +1138,8 @@
memcpy(new_entry, parent_entry, sizeof(*new_entry));
}
+ sort_entries = sort_entries ||
+ (new_entry != new_bag->entries && (new_entry->key < (new_entry - 1U)->key));
if (child_key >= parent_entry->key) {
// Move to the next parent entry if we used it or it was overridden.
++parent_entry;
@@ -1153,6 +1170,8 @@
new_entry->value.dataType, new_entry->value.data, new_key);
return nullptr;
}
+ sort_entries = sort_entries ||
+ (new_entry != new_bag->entries && (new_entry->key < (new_entry - 1U)->key));
++map_entry;
++new_entry;
}
@@ -1172,6 +1191,10 @@
new_bag.release(), sizeof(ResolvedBag) + (actual_count * sizeof(ResolvedBag::Entry)))));
}
+ if (sort_entries) {
+ std::sort(new_bag->entries, new_bag->entries + actual_count, compare_bag_entries);
+ }
+
// Combine flags from the parent and our own bag.
new_bag->type_spec_flags = entry.type_flags | parent_bag->type_spec_flags;
new_bag->entry_count = static_cast<uint32_t>(actual_count);
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index 2f6f3df..35fea7a 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -285,6 +285,27 @@
EXPECT_EQ(0x03, get_package_id(bag->entries[1].key));
}
+TEST_F(AssetManager2Test, FindsBagResourceFromMultipleSharedLibraries) {
+ AssetManager2 assetmanager;
+
+ // libclient is built with lib_one and then lib_two in order.
+ // Reverse the order to test that proper package ID re-assignment is happening.
+ assetmanager.SetApkAssets(
+ {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()});
+
+ const ResolvedBag* bag = assetmanager.GetBag(libclient::R::style::ThemeMultiLib);
+ ASSERT_NE(nullptr, bag);
+ ASSERT_EQ(bag->entry_count, 2u);
+
+ // First attribute comes from lib_two.
+ EXPECT_EQ(2, bag->entries[0].cookie);
+ EXPECT_EQ(0x02, get_package_id(bag->entries[0].key));
+
+ // The next two attributes come from lib_one.
+ EXPECT_EQ(2, bag->entries[1].cookie);
+ EXPECT_EQ(0x03, get_package_id(bag->entries[1].key));
+}
+
TEST_F(AssetManager2Test, FindsStyleResourceWithParentFromSharedLibrary) {
AssetManager2 assetmanager;
diff --git a/libs/androidfw/tests/data/lib_two/R.h b/libs/androidfw/tests/data/lib_two/R.h
index 92b9cc1..fd5a910 100644
--- a/libs/androidfw/tests/data/lib_two/R.h
+++ b/libs/androidfw/tests/data/lib_two/R.h
@@ -30,16 +30,22 @@
};
};
+ struct integer {
+ enum : uint32_t {
+ bar = 0x02020000, // default
+ };
+ };
+
struct string {
enum : uint32_t {
- LibraryString = 0x02020000, // default
- foo = 0x02020001, // default
+ LibraryString = 0x02030000, // default
+ foo = 0x02030001, // default
};
};
struct style {
enum : uint32_t {
- Theme = 0x02030000, // default
+ Theme = 0x02040000, // default
};
};
};
diff --git a/libs/androidfw/tests/data/lib_two/lib_two.apk b/libs/androidfw/tests/data/lib_two/lib_two.apk
index 486c230..8193db6 100644
--- a/libs/androidfw/tests/data/lib_two/lib_two.apk
+++ b/libs/androidfw/tests/data/lib_two/lib_two.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/lib_two/res/values/values.xml b/libs/androidfw/tests/data/lib_two/res/values/values.xml
index 340d14c..4e1d69a 100644
--- a/libs/androidfw/tests/data/lib_two/res/values/values.xml
+++ b/libs/androidfw/tests/data/lib_two/res/values/values.xml
@@ -18,14 +18,17 @@
<public type="attr" name="attr3" id="0x00010000" />
<attr name="attr3" format="integer" />
- <public type="string" name="LibraryString" id="0x00020000" />
+ <public type="integer" name="bar" id="0x00020000" />
+ <integer name="bar">1337</integer>
+
+ <public type="string" name="LibraryString" id="0x00030000" />
<string name="LibraryString">Hi from library two</string>
- <public type="string" name="foo" id="0x00020001" />
+ <public type="string" name="foo" id="0x00030001" />
<string name="foo">Foo from lib_two</string>
- <public type="style" name="Theme" id="0x02030000" />
+ <public type="style" name="Theme" id="0x00040000" />
<style name="Theme">
- <item name="com.android.lib_two:attr3">800</item>
+ <item name="com.android.lib_two:attr3">@integer/bar</item>
</style>
</resources>
diff --git a/libs/androidfw/tests/data/libclient/R.h b/libs/androidfw/tests/data/libclient/R.h
index 43d1f9b..e21b3eb 100644
--- a/libs/androidfw/tests/data/libclient/R.h
+++ b/libs/androidfw/tests/data/libclient/R.h
@@ -34,6 +34,7 @@
struct style {
enum : uint32_t {
Theme = 0x7f020000, // default
+ ThemeMultiLib = 0x7f020001, // default
};
};
diff --git a/libs/androidfw/tests/data/libclient/libclient.apk b/libs/androidfw/tests/data/libclient/libclient.apk
index 1799024..4b9a883 100644
--- a/libs/androidfw/tests/data/libclient/libclient.apk
+++ b/libs/androidfw/tests/data/libclient/libclient.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/libclient/res/values/values.xml b/libs/androidfw/tests/data/libclient/res/values/values.xml
index fead7c3..a29f473 100644
--- a/libs/androidfw/tests/data/libclient/res/values/values.xml
+++ b/libs/androidfw/tests/data/libclient/res/values/values.xml
@@ -27,6 +27,12 @@
<item name="bar">@com.android.lib_one:string/foo</item>
</style>
+ <public type="style" name="ThemeMultiLib" id="0x7f020001" />
+ <style name="ThemeMultiLib" >
+ <item name="com.android.lib_one:attr1">@com.android.lib_one:string/foo</item>
+ <item name="com.android.lib_two:attr3">@com.android.lib_two:integer/bar</item>
+ </style>
+
<public type="string" name="foo_one" id="0x7f030000" />
<string name="foo_one">@com.android.lib_one:string/foo</string>
diff --git a/location/java/android/location/AbstractListenerManager.java b/location/java/android/location/AbstractListenerManager.java
index 944ebf9..f075a53 100644
--- a/location/java/android/location/AbstractListenerManager.java
+++ b/location/java/android/location/AbstractListenerManager.java
@@ -27,6 +27,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
+import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -35,26 +36,34 @@
*
* @hide
*/
-abstract class AbstractListenerManager<T> {
+abstract class AbstractListenerManager<TRequest, TListener> {
- private static class Registration<T> {
+ private static class Registration<TRequest, TListener> {
private final Executor mExecutor;
- @Nullable private volatile T mListener;
+ @Nullable private TRequest mRequest;
+ @Nullable private volatile TListener mListener;
- private Registration(Executor executor, T listener) {
+ private Registration(@Nullable TRequest request, Executor executor, TListener listener) {
Preconditions.checkArgument(listener != null, "invalid null listener/callback");
Preconditions.checkArgument(executor != null, "invalid null executor");
mExecutor = executor;
mListener = listener;
+ mRequest = request;
+ }
+
+ @Nullable
+ public TRequest getRequest() {
+ return mRequest;
}
private void unregister() {
+ mRequest = null;
mListener = null;
}
- private void execute(Consumer<T> operation) {
+ private void execute(Consumer<TListener> operation) {
mExecutor.execute(() -> {
- T listener = mListener;
+ TListener listener = mListener;
if (listener == null) {
return;
}
@@ -71,71 +80,135 @@
}
@GuardedBy("mListeners")
- private final ArrayMap<Object, Registration<T>> mListeners = new ArrayMap<>();
+ private final ArrayMap<Object, Registration<TRequest, TListener>> mListeners =
+ new ArrayMap<>();
- public boolean addListener(@NonNull T listener, @NonNull Handler handler)
+ @GuardedBy("mListeners")
+ @Nullable
+ private TRequest mMergedRequest;
+
+ public boolean addListener(@NonNull TListener listener, @NonNull Handler handler)
throws RemoteException {
- return addInternal(listener, handler);
+ return addInternal(/* request= */ null, listener, handler);
}
- public boolean addListener(@NonNull T listener, @NonNull Executor executor)
+ public boolean addListener(@NonNull TListener listener, @NonNull Executor executor)
throws RemoteException {
- return addInternal(listener, executor);
+ return addInternal(/* request= */ null, listener, executor);
}
- protected final boolean addInternal(@NonNull Object listener, @NonNull Handler handler)
- throws RemoteException {
- return addInternal(listener, new HandlerExecutor(handler));
+ public boolean addListener(@Nullable TRequest request, @NonNull TListener listener,
+ @NonNull Handler handler) throws RemoteException {
+ return addInternal(request, listener, handler);
}
- protected final boolean addInternal(@NonNull Object listener, @NonNull Executor executor)
+ public boolean addListener(@Nullable TRequest request, @NonNull TListener listener,
+ @NonNull Executor executor) throws RemoteException {
+ return addInternal(request, listener, executor);
+ }
+
+ protected final boolean addInternal(@Nullable TRequest request, @NonNull Object listener,
+ @NonNull Handler handler) throws RemoteException {
+ return addInternal(request, listener, new HandlerExecutor(handler));
+ }
+
+ protected final boolean addInternal(@Nullable TRequest request, @NonNull Object listener,
+ @NonNull Executor executor)
throws RemoteException {
Preconditions.checkArgument(listener != null, "invalid null listener/callback");
- return addInternal(listener, new Registration<>(executor, convertKey(listener)));
+ return addInternal(listener, new Registration<>(request, executor, convertKey(listener)));
}
- private boolean addInternal(Object key, Registration<T> registration) throws RemoteException {
+ private boolean addInternal(Object key, Registration<TRequest, TListener> registration)
+ throws RemoteException {
Preconditions.checkNotNull(registration);
synchronized (mListeners) {
- if (mListeners.isEmpty() && !registerService()) {
- return false;
- }
- Registration<T> oldRegistration = mListeners.put(key, registration);
+ boolean initialRequest = mListeners.isEmpty();
+
+ Registration<TRequest, TListener> oldRegistration = mListeners.put(key, registration);
if (oldRegistration != null) {
oldRegistration.unregister();
}
+ TRequest merged = mergeRequests();
+
+ if (initialRequest || !Objects.equals(merged, mMergedRequest)) {
+ mMergedRequest = merged;
+ if (!initialRequest) {
+ unregisterService();
+ }
+ registerService(mMergedRequest);
+ }
+
return true;
}
}
public void removeListener(Object listener) throws RemoteException {
synchronized (mListeners) {
- Registration<T> oldRegistration = mListeners.remove(listener);
+ Registration<TRequest, TListener> oldRegistration = mListeners.remove(listener);
if (oldRegistration == null) {
return;
}
oldRegistration.unregister();
- if (mListeners.isEmpty()) {
+ boolean lastRequest = mListeners.isEmpty();
+ TRequest merged = lastRequest ? null : mergeRequests();
+ boolean newRequest = !lastRequest && !Objects.equals(merged, mMergedRequest);
+
+ if (lastRequest || newRequest) {
unregisterService();
+ mMergedRequest = merged;
+ if (newRequest) {
+ registerService(mMergedRequest);
+ }
}
}
}
@SuppressWarnings("unchecked")
- protected T convertKey(@NonNull Object listener) {
- return (T) listener;
+ protected TListener convertKey(@NonNull Object listener) {
+ return (TListener) listener;
}
- protected abstract boolean registerService() throws RemoteException;
+ protected abstract boolean registerService(TRequest request) throws RemoteException;
protected abstract void unregisterService() throws RemoteException;
- protected void execute(Consumer<T> operation) {
+ @Nullable
+ protected TRequest merge(@NonNull TRequest[] requests) {
+ for (TRequest request : requests) {
+ Preconditions.checkArgument(request == null,
+ "merge() has to be overridden for non-null requests.");
+ }
+ return null;
+ }
+
+ protected void execute(Consumer<TListener> operation) {
synchronized (mListeners) {
- for (Registration<T> registration : mListeners.values()) {
+ for (Registration<TRequest, TListener> registration : mListeners.values()) {
registration.execute(operation);
}
}
}
+
+ @GuardedBy("mListeners")
+ @SuppressWarnings("unchecked")
+ @Nullable
+ private TRequest mergeRequests() {
+ Preconditions.checkState(Thread.holdsLock(mListeners));
+
+ if (mListeners.isEmpty()) {
+ return null;
+ }
+
+ if (mListeners.size() == 1) {
+ return mListeners.valueAt(0).getRequest();
+ }
+
+ TRequest[] requests = (TRequest[]) new Object[mListeners.size()];
+ for (int index = 0; index < mListeners.size(); index++) {
+ requests[index] = mListeners.valueAt(index).getRequest();
+ }
+ return merge(requests);
+ }
}
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 6a5c0ec..a99e68f 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -22,6 +22,7 @@
import android.location.GeocoderParams;
import android.location.Geofence;
import android.location.GnssMeasurementCorrections;
+import android.location.GnssRequest;
import android.location.IBatchedLocationCallback;
import android.location.IGnssMeasurementsListener;
import android.location.IGnssStatusListener;
@@ -69,8 +70,10 @@
double upperRightLatitude, double upperRightLongitude, int maxResults,
in GeocoderParams params, out List<Address> addrs);
- boolean addGnssMeasurementsListener(in IGnssMeasurementsListener listener,
- String packageName, String featureId, String listenerIdentifier);
+ boolean addGnssMeasurementsListener(in GnssRequest request,
+ in IGnssMeasurementsListener listener,
+ String packageName, String featureId,
+ String listenerIdentifier);
void injectGnssMeasurementCorrections(in GnssMeasurementCorrections corrections,
in String packageName);
long getGnssCapabilities(in String packageName);
@@ -107,6 +110,7 @@
boolean isProviderEnabledForUser(String provider, int userId);
boolean isLocationEnabledForUser(int userId);
+ void setLocationEnabledForUser(boolean enabled, int userId);
void addTestProvider(String name, in ProviderProperties properties, String opPackageName);
void removeTestProvider(String provider, String opPackageName);
void setTestProviderLocation(String provider, in Location loc, String opPackageName);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 7e6486c..8ae967f 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -478,13 +478,11 @@
@TestApi
@RequiresPermission(WRITE_SECURE_SETTINGS)
public void setLocationEnabledForUser(boolean enabled, @NonNull UserHandle userHandle) {
- Settings.Secure.putIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.LOCATION_MODE,
- enabled
- ? Settings.Secure.LOCATION_MODE_ON
- : Settings.Secure.LOCATION_MODE_OFF,
- userHandle.getIdentifier());
+ try {
+ mService.setLocationEnabledForUser(enabled, userHandle.getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -2199,7 +2197,7 @@
* Registers a GNSS Measurement callback.
*
* @param request extra parameters to pass to GNSS measurement provider. For example, if {@link
- * GnssRequest#isFullTrackingEnabled()} is true, GNSS chipset switches off duty
+ * GnssRequest#isFullTracking()} is true, GNSS chipset switches off duty
* cycling.
* @param executor the executor that the callback runs on.
* @param callback a {@link GnssMeasurementsEvent.Callback} object to register.
@@ -2216,7 +2214,12 @@
@NonNull GnssRequest request,
@NonNull @CallbackExecutor Executor executor,
@NonNull GnssMeasurementsEvent.Callback callback) {
- throw new RuntimeException();
+ Preconditions.checkArgument(request != null, "invalid null request");
+ try {
+ return mGnssMeasurementsListenerManager.addListener(request, callback, executor);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -2763,8 +2766,7 @@
}
private class GnssStatusListenerManager extends
- AbstractListenerManager<GnssStatus.Callback> {
-
+ AbstractListenerManager<Void, GnssStatus.Callback> {
@Nullable
private IGnssStatusListener mListenerTransport;
@@ -2782,19 +2784,19 @@
public boolean addListener(@NonNull GpsStatus.Listener listener, @NonNull Executor executor)
throws RemoteException {
- return addInternal(listener, executor);
+ return addInternal(null, listener, executor);
}
public boolean addListener(@NonNull OnNmeaMessageListener listener,
@NonNull Handler handler)
throws RemoteException {
- return addInternal(listener, handler);
+ return addInternal(null, listener, handler);
}
public boolean addListener(@NonNull OnNmeaMessageListener listener,
@NonNull Executor executor)
throws RemoteException {
- return addInternal(listener, executor);
+ return addInternal(null, listener, executor);
}
@Override
@@ -2833,7 +2835,7 @@
}
@Override
- protected boolean registerService() throws RemoteException {
+ protected boolean registerService(Void ignored) throws RemoteException {
Preconditions.checkState(mListenerTransport == null);
GnssStatusListener transport = new GnssStatusListener();
@@ -2893,17 +2895,17 @@
}
private class GnssMeasurementsListenerManager extends
- AbstractListenerManager<GnssMeasurementsEvent.Callback> {
+ AbstractListenerManager<GnssRequest, GnssMeasurementsEvent.Callback> {
@Nullable
private IGnssMeasurementsListener mListenerTransport;
@Override
- protected boolean registerService() throws RemoteException {
+ protected boolean registerService(GnssRequest request) throws RemoteException {
Preconditions.checkState(mListenerTransport == null);
GnssMeasurementsListener transport = new GnssMeasurementsListener();
- if (mService.addGnssMeasurementsListener(transport, mContext.getPackageName(),
+ if (mService.addGnssMeasurementsListener(request, transport, mContext.getPackageName(),
mContext.getFeatureId(), "gnss measurement callback")) {
mListenerTransport = transport;
return true;
@@ -2920,6 +2922,18 @@
mListenerTransport = null;
}
+ @Override
+ @Nullable
+ protected GnssRequest merge(@NonNull GnssRequest[] requests) {
+ Preconditions.checkArgument(requests.length > 0);
+ for (GnssRequest request : requests) {
+ if (request.isFullTracking()) {
+ return request;
+ }
+ }
+ return requests[0];
+ }
+
private class GnssMeasurementsListener extends IGnssMeasurementsListener.Stub {
@Override
public void onGnssMeasurementsReceived(final GnssMeasurementsEvent event) {
@@ -2934,13 +2948,13 @@
}
private class GnssNavigationMessageListenerManager extends
- AbstractListenerManager<GnssNavigationMessage.Callback> {
+ AbstractListenerManager<Void, GnssNavigationMessage.Callback> {
@Nullable
private IGnssNavigationMessageListener mListenerTransport;
@Override
- protected boolean registerService() throws RemoteException {
+ protected boolean registerService(Void ignored) throws RemoteException {
Preconditions.checkState(mListenerTransport == null);
GnssNavigationMessageListener transport = new GnssNavigationMessageListener();
@@ -2975,13 +2989,13 @@
}
private class BatchedLocationCallbackManager extends
- AbstractListenerManager<BatchedLocationCallback> {
+ AbstractListenerManager<Void, BatchedLocationCallback> {
@Nullable
private IBatchedLocationCallback mListenerTransport;
@Override
- protected boolean registerService() throws RemoteException {
+ protected boolean registerService(Void ignored) throws RemoteException {
Preconditions.checkState(mListenerTransport == null);
BatchedLocationCallback transport = new BatchedLocationCallback();
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 3561f83..9b183a3 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -244,7 +244,7 @@
*
* <p>
* Tuner events are started when {@link #tune(FrontendSettings)} is called and end when {@link
- * #stopTune()} is called.
+ * #cancelTuning()} is called.
*
* @param eventListener receives tune events.
* @throws SecurityException if the caller does not have appropriate permissions.
@@ -309,7 +309,7 @@
*/
@RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
@Result
- public int stopTune() {
+ public int cancelTuning() {
TunerUtils.checkTunerPermission(mContext);
return nativeStopTune();
}
@@ -322,8 +322,8 @@
* @param settings A {@link FrontendSettings} to configure the frontend.
* @param scanType The scan type.
* @throws SecurityException if the caller does not have appropriate permissions.
- * @throws IllegalStateException if {@code scan} is called again before {@link #stopScan()} is
- * called.
+ * @throws IllegalStateException if {@code scan} is called again before
+ * {@link #cancelScanning()} is called.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
@Result
@@ -354,7 +354,7 @@
*/
@RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
@Result
- public int stopScan() {
+ public int cancelScanning() {
TunerUtils.checkTunerPermission(mContext);
int retVal = nativeStopScan();
mScanCallback = null;
diff --git a/media/tests/TunerTest/OWNERS b/media/tests/TunerTest/OWNERS
new file mode 100644
index 0000000..73ea663
--- /dev/null
+++ b/media/tests/TunerTest/OWNERS
@@ -0,0 +1,4 @@
+amyjojo@google.com
+nchalko@google.com
+quxiangfang@google.com
+shubang@google.com
diff --git a/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java b/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java
index bd5b795..c4e41c8 100644
--- a/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java
+++ b/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java
@@ -16,6 +16,8 @@
package com.android.incremental.nativeadb;
+import android.annotation.NonNull;
+import android.content.pm.DataLoaderParams;
import android.service.dataloader.DataLoaderService;
/** This code is used for testing only. */
@@ -26,7 +28,7 @@
}
@Override
- public DataLoader onCreateDataLoader() {
+ public DataLoader onCreateDataLoader(@NonNull DataLoaderParams dataLoaderParams) {
return null;
}
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 1fe967b..5458676e 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -646,16 +646,17 @@
android:label="Controls Providers"
android:theme="@style/Theme.ControlsManagement"
android:showForAllUsers="true"
+ android:clearTaskOnLaunch="true"
android:excludeFromRecents="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
android:visibleToInstantApps="true">
</activity>
<activity android:name=".controls.management.ControlsFavoritingActivity"
- android:parentActivityName=".controls.management.ControlsProviderSelectorActivity"
android:theme="@style/Theme.ControlsManagement"
android:excludeFromRecents="true"
android:showForAllUsers="true"
+ android:finishOnTaskLaunch="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
android:visibleToInstantApps="true">
</activity>
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index 01811e9..f607cc8 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -118,6 +118,7 @@
public CharSequence label;
public CharSequence secondaryLabel;
public CharSequence contentDescription;
+ public CharSequence stateDescription;
public CharSequence dualLabelContentDescription;
public boolean disabledByPolicy;
public boolean dualTarget = false;
@@ -135,6 +136,7 @@
|| !Objects.equals(other.label, label)
|| !Objects.equals(other.secondaryLabel, secondaryLabel)
|| !Objects.equals(other.contentDescription, contentDescription)
+ || !Objects.equals(other.stateDescription, stateDescription)
|| !Objects.equals(other.dualLabelContentDescription,
dualLabelContentDescription)
|| !Objects.equals(other.expandedAccessibilityClassName,
@@ -151,6 +153,7 @@
other.label = label;
other.secondaryLabel = secondaryLabel;
other.contentDescription = contentDescription;
+ other.stateDescription = stateDescription;
other.dualLabelContentDescription = dualLabelContentDescription;
other.expandedAccessibilityClassName = expandedAccessibilityClassName;
other.disabledByPolicy = disabledByPolicy;
@@ -177,6 +180,7 @@
sb.append(",label=").append(label);
sb.append(",secondaryLabel=").append(secondaryLabel);
sb.append(",contentDescription=").append(contentDescription);
+ sb.append(",stateDescription=").append(stateDescription);
sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription);
sb.append(",expandedAccessibilityClassName=").append(expandedAccessibilityClassName);
sb.append(",disabledByPolicy=").append(disabledByPolicy);
diff --git a/packages/SystemUI/res-keyguard/layout/controls_management.xml b/packages/SystemUI/res-keyguard/layout/controls_management.xml
deleted file mode 100644
index 8330258..0000000
--- a/packages/SystemUI/res-keyguard/layout/controls_management.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center_horizontal"
- android:paddingTop="@dimen/controls_management_top_padding"
- android:paddingStart="@dimen/controls_management_side_padding"
- android:paddingEnd="@dimen/controls_management_side_padding" >
-
- <TextView
- android:id="@+id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:textSize="@dimen/controls_title_size"
- android:textAlignment="center" />
-
- <TextView
- android:id="@+id/subtitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/controls_management_titles_margin"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textAlignment="center" />
-
- <androidx.recyclerview.widget.RecyclerView
- android:id="@+id/list"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/controls_management_list_margin" />
-
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/controls_zone_header.xml b/packages/SystemUI/res-keyguard/layout/controls_zone_header.xml
new file mode 100644
index 0000000..7b43a03
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/controls_zone_header.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.Control.Title"
+ android:textColor="?android:attr/colorPrimary"
+ android:layout_marginStart="12dp"
+ android:layout_marginEnd="2dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="4dp">
+
+</TextView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_cancel_24.xml b/packages/SystemUI/res/drawable/ic_cancel_24.xml
new file mode 100644
index 0000000..8ab28dd
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_cancel_24.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_important.xml b/packages/SystemUI/res/drawable/ic_important.xml
new file mode 100644
index 0000000..d7439e1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_important.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M4,18.99h11c0.67,0 1.27,-0.32 1.63,-0.83L21,12l-4.37,-6.16C16.27,5.33 15.67,5 15,5H4l5,7 -5,6.99z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_important_outline.xml b/packages/SystemUI/res/drawable/ic_important_outline.xml
new file mode 100644
index 0000000..7a628bb
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_important_outline.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M15,19L3,19l4.5,-7L3,5h12c0.65,0 1.26,0.31 1.63,0.84L21,12l-4.37,6.16c-0.37,0.52 -0.98,0.84 -1.63,0.84zM6.5,17L15,17l3.5,-5L15,7L6.5,7l3.5,5 -3.5,5z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_star.xml b/packages/SystemUI/res/drawable/ic_star.xml
deleted file mode 100644
index 4a731b3..0000000
--- a/packages/SystemUI/res/drawable/ic_star.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
- Copyright (C) 2020 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
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="@android:color/white"
- android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27z"/>
-</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_star_border.xml b/packages/SystemUI/res/drawable/ic_star_border.xml
deleted file mode 100644
index 9ede40b..0000000
--- a/packages/SystemUI/res/drawable/ic_star_border.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
- Copyright (C) 2020 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
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="@android:color/white"
- android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
-</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/screenshot_cancel.xml b/packages/SystemUI/res/drawable/screenshot_cancel.xml
new file mode 100644
index 0000000..be3c598
--- /dev/null
+++ b/packages/SystemUI/res/drawable/screenshot_cancel.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0">
+ <path
+ android:pathData="M24,24m-16,0a16,16 0,1 1,32 0a16,16 0,1 1,-32 0"
+ android:fillColor="@android:color/white"/>
+ <path
+ android:fillColor="@color/GM2_grey_500"
+ android:pathData="M31,18.41L29.59,17 24,22.59 18.41,17 17,18.41 22.59,24 17,29.59 18.41,31 24,25.41 29.59,31 31,29.59 25.41,24z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml
index 68c8246..823bbcd 100644
--- a/packages/SystemUI/res/layout/controls_base_item.xml
+++ b/packages/SystemUI/res/layout/controls_base_item.xml
@@ -72,8 +72,8 @@
<CheckBox
android:id="@+id/favorite"
android:visibility="gone"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/controls_management.xml b/packages/SystemUI/res/layout/controls_management.xml
new file mode 100644
index 0000000..a7379be
--- /dev/null
+++ b/packages/SystemUI/res/layout/controls_management.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal"
+ android:paddingTop="@dimen/controls_management_top_padding"
+ android:paddingStart="@dimen/controls_management_side_padding"
+ android:paddingEnd="@dimen/controls_management_side_padding" >
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textSize="@dimen/controls_title_size"
+ android:textAlignment="center" />
+
+ <TextView
+ android:id="@+id/subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/controls_management_titles_margin"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textAlignment="center" />
+
+ <androidx.core.widget.NestedScrollView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:layout_marginTop="@dimen/controls_management_list_margin">
+
+ <ViewStub
+ android:id="@+id/stub"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+ </androidx.core.widget.NestedScrollView>
+
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="64dp">
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/controls_app_divider_height"
+ android:layout_gravity="center_horizontal|top"
+ android:background="?android:attr/listDivider" />
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="4dp">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:text="See other apps"
+ android:textAppearance="@style/TextAppearance.Control.Title"
+ android:textColor="?android:attr/colorPrimary"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"/>
+
+ <Button
+ android:id="@+id/done"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:text="Done"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"/>
+ </androidx.constraintlayout.widget.ConstraintLayout>
+ </FrameLayout>
+
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_management_apps.xml b/packages/SystemUI/res/layout/controls_management_apps.xml
new file mode 100644
index 0000000..2bab433
--- /dev/null
+++ b/packages/SystemUI/res/layout/controls_management_apps.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<androidx.recyclerview.widget.RecyclerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ >
+
+</androidx.recyclerview.widget.RecyclerView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_management_favorites.xml b/packages/SystemUI/res/layout/controls_management_favorites.xml
new file mode 100644
index 0000000..a36dd12
--- /dev/null
+++ b/packages/SystemUI/res/layout/controls_management_favorites.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/text_favorites"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="FAVORITES"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/divider1"
+ app:layout_constraintTop_toTopOf="parent"
+ />
+
+ <View
+ android:id="@+id/divider1"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/controls_app_divider_height"
+ android:layout_gravity="center_horizontal|top"
+ android:background="?android:attr/listDivider"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/listFavorites"
+ app:layout_constraintTop_toBottomOf="@id/text_favorites"
+ />
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/listFavorites"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginTop="@dimen/controls_management_list_margin"
+ android:nestedScrollingEnabled="false"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/text_all"
+ app:layout_constraintTop_toBottomOf="@id/divider1"/>
+
+ <TextView
+ android:id="@+id/text_all"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/controls_management_list_margin"
+ android:text="ALL"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/divider2"
+ app:layout_constraintTop_toBottomOf="@id/listFavorites"
+ />
+
+ <View
+ android:id="@+id/divider2"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/controls_app_divider_height"
+ android:layout_gravity="center_horizontal|top"
+ android:background="?android:attr/listDivider"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/listAll"
+ app:layout_constraintTop_toBottomOf="@id/text_all"
+ />
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/listAll"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginTop="@dimen/controls_management_list_margin"
+ android:nestedScrollingEnabled="false"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/divider2"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml
index 40b2476..2cd9505 100644
--- a/packages/SystemUI/res/layout/controls_with_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_with_favorites.xml
@@ -1,3 +1,18 @@
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml
index 1f7def2..d0151ff 100644
--- a/packages/SystemUI/res/layout/global_screenshot.xml
+++ b/packages/SystemUI/res/layout/global_screenshot.xml
@@ -55,10 +55,22 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
- android:elevation="8dp"
+ android:elevation="@dimen/screenshot_preview_elevation"
android:visibility="gone"
android:background="@drawable/screenshot_rounded_corners"
android:adjustViewBounds="true"/>
+ <FrameLayout
+ android:id="@+id/global_screenshot_dismiss_button"
+ android:layout_width="@dimen/screenshot_dismiss_button_tappable_size"
+ android:layout_height="@dimen/screenshot_dismiss_button_tappable_size"
+ android:elevation="9dp"
+ android:visibility="gone">
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="@dimen/screenshot_dismiss_button_margin"
+ android:src="@drawable/screenshot_cancel"/>
+ </FrameLayout>
<ImageView
android:id="@+id/global_screenshot_flash"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
index a9d6e35..8460612 100644
--- a/packages/SystemUI/res/layout/notification_conversation_info.xml
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -28,7 +28,7 @@
android:paddingStart="@*android:dimen/notification_content_margin_start">
<!-- Package Info -->
- <RelativeLayout
+ <LinearLayout
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_guts_conversation_header_height"
@@ -41,16 +41,20 @@
android:layout_height="@dimen/notification_guts_conversation_icon_size"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
- android:layout_marginEnd="6dp" />
+ android:layout_marginEnd="15dp" />
<LinearLayout
android:id="@+id/names"
+ android:layout_weight="1"
+ android:layout_width="0dp"
android:orientation="vertical"
- android:layout_width="wrap_content"
+
android:layout_height="wrap_content"
android:minHeight="@dimen/notification_guts_conversation_icon_size"
android:layout_centerVertical="true"
android:gravity="center_vertical"
- android:layout_toEndOf="@id/conversation_icon">
+ android:layout_alignEnd="@id/conversation_icon"
+ android:layout_toEndOf="@id/conversation_icon"
+ android:layout_alignStart="@id/mute">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -107,67 +111,40 @@
android:layout_weight="1"
style="@style/TextAppearance.NotificationImportanceChannel"/>
</LinearLayout>
+ <TextView
+ android:id="@+id/delegate_name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ style="@style/TextAppearance.NotificationImportanceHeader"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:ellipsize="end"
+ android:text="@string/notification_delegate_header"
+ android:layout_toEndOf="@id/pkg_divider"
+ android:maxLines="1" />
</LinearLayout>
- <TextView
- android:id="@+id/pkg_divider"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- style="@style/TextAppearance.NotificationImportanceHeader"
- android:layout_marginStart="2dp"
- android:layout_marginEnd="2dp"
- android:layout_toEndOf="@id/name"
- android:text="@*android:string/notification_header_divider_symbol" />
- <TextView
- android:id="@+id/delegate_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- style="@style/TextAppearance.NotificationImportanceHeader"
- android:layout_marginStart="2dp"
- android:layout_marginEnd="2dp"
- android:ellipsize="end"
- android:text="@string/notification_delegate_header"
- android:layout_toEndOf="@id/pkg_divider"
- android:maxLines="1" />
-
<!-- end aligned fields -->
<ImageButton
- android:id="@+id/demote"
+ android:id="@+id/mute"
android:layout_width="@dimen/notification_importance_toggle_size"
android:layout_height="@dimen/notification_importance_toggle_size"
android:layout_centerVertical="true"
android:background="@drawable/ripple_drawable"
- android:contentDescription="@string/demote"
- android:src="@drawable/ic_demote_conversation"
- android:layout_toStartOf="@id/app_settings"
- android:tint="@color/notification_guts_link_icon_tint"/>
- <!-- Optional link to app. Only appears if the channel is not disabled and the app
-asked for it -->
- <ImageButton
- android:id="@+id/app_settings"
- android:layout_width="@dimen/notification_importance_toggle_size"
- android:layout_height="@dimen/notification_importance_toggle_size"
- android:layout_centerVertical="true"
- android:visibility="gone"
- android:background="@drawable/ripple_drawable"
- android:contentDescription="@string/notification_app_settings"
- android:src="@drawable/ic_info"
- android:layout_toStartOf="@id/info"
+ android:layout_toStartOf="@id/fave"
android:tint="@color/notification_guts_link_icon_tint"/>
<ImageButton
- android:id="@+id/info"
+ android:id="@+id/fave"
android:layout_width="@dimen/notification_importance_toggle_size"
android:layout_height="@dimen/notification_importance_toggle_size"
android:layout_centerVertical="true"
android:background="@drawable/ripple_drawable"
- android:contentDescription="@string/notification_more_settings"
- android:src="@drawable/ic_settings"
android:layout_alignParentEnd="true"
android:tint="@color/notification_guts_link_icon_tint"/>
- </RelativeLayout>
+
+ </LinearLayout>
<LinearLayout
android:id="@+id/actions"
@@ -182,6 +159,23 @@
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/GM2_grey_300" />
+
+ <Button
+ android:id="@+id/snooze"
+ android:layout_height="@dimen/notification_guts_conversation_action_height"
+ android:layout_width="match_parent"
+ style="?android:attr/borderlessButtonStyle"
+ android:text="@string/notification_menu_snooze_action"
+ android:gravity="left|center_vertical"
+ android:drawableStart="@drawable/ic_snooze"
+ android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+ android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/GM2_grey_300" />
+
<Button
android:id="@+id/bubble"
android:layout_height="@dimen/notification_guts_conversation_action_height"
@@ -212,42 +206,15 @@
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/GM2_grey_300" />
- <Button
- android:id="@+id/fave"
- android:layout_height="@dimen/notification_guts_conversation_action_height"
- android:layout_width="match_parent"
- style="?android:attr/borderlessButtonStyle"
- android:gravity="left|center_vertical"
- android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
- android:drawableTint="@color/notification_guts_link_icon_tint"/>
- <View
- android:layout_width="match_parent"
- android:layout_height="0.5dp"
- android:background="@color/GM2_grey_300" />
<Button
- android:id="@+id/snooze"
+ android:id="@+id/info"
android:layout_height="@dimen/notification_guts_conversation_action_height"
android:layout_width="match_parent"
style="?android:attr/borderlessButtonStyle"
- android:text="@string/notification_menu_snooze_action"
+ android:drawableStart="@drawable/ic_settings"
+ android:text="@string/notification_menu_settings_action"
android:gravity="left|center_vertical"
- android:drawableStart="@drawable/ic_snooze"
- android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
- android:drawableTint="@color/notification_guts_link_icon_tint"/>
-
- <View
- android:layout_width="match_parent"
- android:layout_height="0.5dp"
- android:background="@color/GM2_grey_300" />
- <Button
- android:id="@+id/mute"
- android:layout_height="@dimen/notification_guts_conversation_action_height"
- android:layout_width="match_parent"
- style="?android:attr/borderlessButtonStyle"
- android:text="@string/notification_conversation_mute"
- android:gravity="left|center_vertical"
- android:drawableStart="@drawable/ic_notifications_silence"
android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
android:drawableTint="@color/notification_guts_link_icon_tint"/>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7a3395c..0db7d67 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -103,7 +103,8 @@
<item name="status_bar_icon_scale_factor" format="float" type="dimen">1.0</item>
<dimen name="group_overflow_number_size">@*android:dimen/notification_text_size</dimen>
- <dimen name="group_overflow_number_padding">@*android:dimen/notification_content_margin_end</dimen>
+ <dimen name="group_overflow_number_padding">@*android:dimen/notification_content_margin_end
+ </dimen>
<!-- max height of a notification such that the content can still fade out when closing -->
<dimen name="max_notification_fadeout_height">100dp</dimen>
@@ -267,7 +268,9 @@
<dimen name="status_bar_icon_drawing_size">15dp</dimen>
<!-- size at which Notification icons will be drawn on Ambient Display -->
- <dimen name="status_bar_icon_drawing_size_dark">@*android:dimen/notification_header_icon_size_ambient</dimen>
+ <dimen name="status_bar_icon_drawing_size_dark">
+ @*android:dimen/notification_header_icon_size_ambient
+ </dimen>
<!-- size of notification icons when the notifications are hidden -->
<dimen name="hidden_shelf_icon_size">16dp</dimen>
@@ -299,8 +302,11 @@
<dimen name="global_screenshot_legacy_bg_padding">20dp</dimen>
<dimen name="global_screenshot_bg_padding">20dp</dimen>
<dimen name="global_screenshot_x_scale">80dp</dimen>
+ <dimen name="screenshot_preview_elevation">8dp</dimen>
<dimen name="screenshot_offset_y">48dp</dimen>
<dimen name="screenshot_offset_x">16dp</dimen>
+ <dimen name="screenshot_dismiss_button_tappable_size">48dp</dimen>
+ <dimen name="screenshot_dismiss_button_margin">8dp</dimen>
<dimen name="screenshot_action_container_offset_y">32dp</dimen>
<dimen name="screenshot_action_container_corner_radius">10dp</dimen>
<dimen name="screenshot_action_container_padding_vertical">10dp</dimen>
@@ -597,10 +603,14 @@
<!-- The height of the divider between the individual notifications in a notification
group. -->
- <dimen name="notification_children_container_divider_height">@dimen/notification_divider_height</dimen>
+ <dimen name="notification_children_container_divider_height">
+ @dimen/notification_divider_height
+ </dimen>
<!-- The top margin for the notification children container in its non-expanded form. -->
- <dimen name="notification_children_container_margin_top">@*android:dimen/notification_content_margin_top</dimen>
+ <dimen name="notification_children_container_margin_top">
+ @*android:dimen/notification_content_margin_top
+ </dimen>
<!-- The height of a notification header -->
<dimen name="notification_header_height">53dp</dimen>
@@ -1061,9 +1071,12 @@
<integer name="wireless_charging_scale_dots_duration">83</integer>
<integer name="wireless_charging_num_dots">16</integer>
<!-- Starting text size in sp of batteryLevel for wireless charging animation -->
- <item name="wireless_charging_anim_battery_level_text_size_start" format="float" type="dimen">0</item>
+ <item name="wireless_charging_anim_battery_level_text_size_start" format="float" type="dimen">
+ 0
+ </item>
<!-- Ending text size in sp of batteryLevel for wireless charging animation -->
- <item name="wireless_charging_anim_battery_level_text_size_end" format="float" type="dimen">24</item>
+ <item name="wireless_charging_anim_battery_level_text_size_end" format="float" type="dimen">24
+ </item>
<!-- time until battery info is at full opacity-->
<integer name="wireless_charging_anim_opacity_offset">80</integer>
<!-- duration batteryLevel opacity goes from 0 to 1 duration -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b85b51e..8f9e934 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1829,23 +1829,23 @@
<!-- Notification: Conversation: control panel, label for button that demotes notification from conversation to normal notification -->
<string name="demote">Mark this notification as not a conversation</string>
- <!-- [CHAR LIMIT=100] Mark this conversation as a favorite -->
- <string name="notification_conversation_favorite">Mark as important</string>
+ <!-- [CHAR LIMIT=100] This conversation is marked as important -->
+ <string name="notification_conversation_favorite">Important conversation</string>
- <!-- [CHAR LIMIT=100] Unmark this conversation as a favorite -->
- <string name="notification_conversation_unfavorite">Mark as unimportant</string>
+ <!-- [CHAR LIMIT=100] This conversation is not marked as important -->
+ <string name="notification_conversation_unfavorite">Not an important conversation</string>
- <!-- [CHAR LIMIT=100] Mute this conversation -->
- <string name="notification_conversation_mute">Silence</string>
+ <!-- [CHAR LIMIT=100] This conversation is silenced (will not make sound or vibrate)-->
+ <string name="notification_conversation_mute">Silenced</string>
- <!-- [CHAR LIMIT=100] Umute this conversation -->
+ <!-- [CHAR LIMIT=100] This conversation is alerting (may make sound and/or vibrate)-->
<string name="notification_conversation_unmute">Alerting</string>
<!-- [CHAR LIMIT=100] Show notification as bubble -->
- <string name="notification_conversation_bubble">Show as bubble</string>
+ <string name="notification_conversation_bubble">Show bubble</string>
<!-- [CHAR LIMIT=100] Turn off bubbles for notification -->
- <string name="notification_conversation_unbubble">Turn off bubbles</string>
+ <string name="notification_conversation_unbubble">Remove bubbles</string>
<!-- [CHAR LIMIT=100] Add this conversation to home screen -->
<string name="notification_conversation_home_screen">Add to home screen</string>
@@ -1860,7 +1860,10 @@
<string name="notification_menu_snooze_description">notification snooze options</string>
<!-- Notification: Menu row: Label for the snooze action shown in local context menu. [CHAR LIMIT=NONE] -->
- <string name="notification_menu_snooze_action">Snooze</string>
+ <string name="notification_menu_snooze_action">Remind me</string>
+
+ <!-- Notification: Menu row: Label for the snooze action shown in local context menu. [CHAR LIMIT=NONE] -->
+ <string name="notification_menu_settings_action">Settings</string>
<!-- Notification: Snooze panel: Snooze undo button label. [CHAR LIMIT=50]-->
<string name="snooze_undo">UNDO</string>
@@ -2029,6 +2032,9 @@
<!-- Label for feature switch [CHAR LIMIT=30] -->
<string name="switch_bar_off">Off</string>
+ <!-- The tile in quick settings is unavailable. [CHAR LIMIT=32] -->
+ <string name="tile_unavailable">Unavailable</string>
+
<!-- SysUI Tuner: Button that leads to the navigation bar customization screen [CHAR LIMIT=60] -->
<string name="nav_bar">Navigation bar</string>
@@ -2583,6 +2589,4 @@
<string name="controls_favorite_default_title">Controls</string>
<!-- Controls management controls screen subtitle [CHAR LIMIT=NONE] -->
<string name="controls_favorite_subtitle">Choose controls for quick access</string>
-
-
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index bea55c8..2d55a1d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -146,7 +146,6 @@
int viewType) {
BadgedImageView view = (BadgedImageView) LayoutInflater.from(parent.getContext())
.inflate(R.layout.bubble_view, parent, false);
- view.setPadding(15, 15, 15, 15);
return new ViewHolder(view);
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
index 53841e2..49a16d8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
@@ -20,6 +20,6 @@
data class ControlStatus(
val control: Control,
- val favorite: Boolean,
+ var favorite: Boolean,
val removed: Boolean = false
)
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
index b3ba2b2..fce5041 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -26,10 +26,18 @@
val available: Boolean
fun getFavoriteControls(): List<ControlInfo>
- fun loadForComponent(componentName: ComponentName, callback: (List<ControlStatus>) -> Unit)
+ fun loadForComponent(
+ componentName: ComponentName,
+ callback: (List<ControlStatus>, List<String>) -> Unit
+ )
+
fun subscribeToFavorites()
fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean)
- fun countFavoritesForComponent(componentName: ComponentName): Int = 0
+ fun replaceFavoritesForComponent(componentName: ComponentName, favorites: List<ControlInfo>)
+
+ fun getFavoritesForComponent(componentName: ComponentName): List<ControlInfo>
+ fun countFavoritesForComponent(componentName: ComponentName): Int
+
fun unsubscribe()
fun action(controlInfo: ControlInfo, action: ControlAction)
fun refreshStatus(componentName: ComponentName, control: Control)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 7de1557..e611197 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -70,9 +70,10 @@
}
// Map of map: ComponentName -> (String -> ControlInfo).
- // Only for current user
+ //
@GuardedBy("currentFavorites")
- private val currentFavorites = ArrayMap<ComponentName, MutableMap<String, ControlInfo>>()
+ private val currentFavorites = ArrayMap<ComponentName, MutableList<ControlInfo>>()
+ .withDefault { mutableListOf() }
private var userChanging: Boolean = true
@@ -180,15 +181,14 @@
val infos = persistenceWrapper.readFavorites()
synchronized(currentFavorites) {
infos.forEach {
- currentFavorites.getOrPut(it.component, { ArrayMap<String, ControlInfo>() })
- .put(it.controlId, it)
+ currentFavorites.getOrPut(it.component, { mutableListOf() }).add(it)
}
}
}
override fun loadForComponent(
componentName: ComponentName,
- callback: (List<ControlStatus>) -> Unit
+ callback: (List<ControlStatus>, List<String>) -> Unit
) {
if (!confirmAvailability()) {
if (userChanging) {
@@ -200,29 +200,34 @@
TimeUnit.MILLISECONDS
)
} else {
- callback(emptyList())
+ callback(emptyList(), emptyList())
}
return
}
bindingController.bindAndLoad(componentName) {
synchronized(currentFavorites) {
- val favoritesForComponentKeys: Set<String> =
- currentFavorites.get(componentName)?.keys ?: emptySet()
- val changed = updateFavoritesLocked(componentName, it)
+ val favoritesForComponentKeys: List<String> =
+ currentFavorites.getValue(componentName).map { it.controlId }
+ val changed = updateFavoritesLocked(componentName, it, favoritesForComponentKeys)
if (changed) {
persistenceWrapper.storeFavorites(favoritesAsListLocked())
}
- val removed = findRemovedLocked(favoritesForComponentKeys, it)
- callback(removed.map { currentFavorites.getValue(componentName).getValue(it) }
- .map(::createRemovedStatus) +
- it.map { ControlStatus(it, it.controlId in favoritesForComponentKeys) })
+ val removed = findRemovedLocked(favoritesForComponentKeys.toSet(), it)
+ val controlsWithFavorite =
+ it.map { ControlStatus(it, it.controlId in favoritesForComponentKeys) }
+ callback(
+ currentFavorites.getValue(componentName)
+ .filter { it.controlId in removed }
+ .map(::createRemovedStatus) + controlsWithFavorite,
+ favoritesForComponentKeys
+ )
}
}
}
private fun createRemovedStatus(controlInfo: ControlInfo): ControlStatus {
val intent = Intent(context, ControlsFavoritingActivity::class.java).apply {
- putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, controlInfo.component)
+ putExtra(Intent.EXTRA_COMPONENT_NAME, controlInfo.component)
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
}
val pendingIntent = PendingIntent.getActivity(context,
@@ -243,17 +248,24 @@
}
@GuardedBy("currentFavorites")
- private fun updateFavoritesLocked(componentName: ComponentName, list: List<Control>): Boolean {
- val favorites = currentFavorites.get(componentName) ?: mutableMapOf()
- val favoriteKeys = favorites.keys
+ private fun updateFavoritesLocked(
+ componentName: ComponentName,
+ list: List<Control>,
+ favoriteKeys: List<String>
+ ): Boolean {
+ val favorites = currentFavorites.get(componentName) ?: mutableListOf()
if (favoriteKeys.isEmpty()) return false // early return
var changed = false
- list.forEach {
- if (it.controlId in favoriteKeys) {
- val value = favorites.getValue(it.controlId)
- if (value.controlTitle != it.title || value.deviceType != it.deviceType) {
- favorites[it.controlId] = value.copy(controlTitle = it.title,
- deviceType = it.deviceType)
+ list.forEach { control ->
+ if (control.controlId in favoriteKeys) {
+ val index = favorites.indexOfFirst { it.controlId == control.controlId }
+ val value = favorites[index]
+ if (value.controlTitle != control.title ||
+ value.deviceType != control.deviceType) {
+ favorites[index] = value.copy(
+ controlTitle = control.title,
+ deviceType = control.deviceType
+ )
changed = true
}
}
@@ -263,14 +275,14 @@
@GuardedBy("currentFavorites")
private fun favoritesAsListLocked(): List<ControlInfo> {
- return currentFavorites.flatMap { it.value.values }
+ return currentFavorites.flatMap { it.value }
}
override fun subscribeToFavorites() {
if (!confirmAvailability()) return
// Make a copy of the favorites list
val favorites = synchronized(currentFavorites) {
- currentFavorites.flatMap { it.value.values.toList() }
+ currentFavorites.flatMap { it.value }
}
bindingController.subscribe(favorites)
}
@@ -286,22 +298,19 @@
val listOfControls = synchronized(currentFavorites) {
if (state) {
if (controlInfo.component !in currentFavorites) {
- currentFavorites.put(controlInfo.component, ArrayMap<String, ControlInfo>())
+ currentFavorites.put(controlInfo.component, mutableListOf())
changed = true
}
val controlsForComponent = currentFavorites.getValue(controlInfo.component)
- if (controlInfo.controlId !in controlsForComponent) {
- controlsForComponent.put(controlInfo.controlId, controlInfo)
+ if (controlsForComponent.firstOrNull {
+ it.controlId == controlInfo.controlId
+ } == null) {
+ controlsForComponent.add(controlInfo)
changed = true
- } else {
- if (controlsForComponent.getValue(controlInfo.controlId) != controlInfo) {
- controlsForComponent.put(controlInfo.controlId, controlInfo)
- changed = true
- }
}
} else {
changed = currentFavorites.get(controlInfo.component)
- ?.remove(controlInfo.controlId) != null
+ ?.remove(controlInfo) != null
}
favoritesAsListLocked()
}
@@ -310,6 +319,19 @@
}
}
+ override fun replaceFavoritesForComponent(
+ componentName: ComponentName,
+ favorites: List<ControlInfo>
+ ) {
+ if (!confirmAvailability()) return
+ val filtered = favorites.filter { it.component == componentName }
+ val listOfControls = synchronized(currentFavorites) {
+ currentFavorites.put(componentName, filtered.toMutableList())
+ favoritesAsListLocked()
+ }
+ persistenceWrapper.storeFavorites(listOfControls)
+ }
+
override fun refreshStatus(componentName: ComponentName, control: Control) {
if (!confirmAvailability()) {
Log.d(TAG, "Controls not available")
@@ -317,7 +339,13 @@
}
executor.execute {
synchronized(currentFavorites) {
- val changed = updateFavoritesLocked(componentName, listOf(control))
+ val favoriteKeysForComponent =
+ currentFavorites.get(componentName)?.map { it.controlId } ?: emptyList()
+ val changed = updateFavoritesLocked(
+ componentName,
+ listOf(control),
+ favoriteKeysForComponent
+ )
if (changed) {
persistenceWrapper.storeFavorites(favoritesAsListLocked())
}
@@ -361,6 +389,12 @@
}
}
+ override fun getFavoritesForComponent(componentName: ComponentName): List<ControlInfo> {
+ return synchronized(currentFavorites) {
+ currentFavorites.get(componentName) ?: emptyList()
+ }
+ }
+
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
pw.println("ControlsController state:")
pw.println(" Available: $available")
@@ -368,10 +402,8 @@
pw.println(" Current user: ${currentUser.identifier}")
pw.println(" Favorites:")
synchronized(currentFavorites) {
- currentFavorites.forEach {
- it.value.forEach {
- pw.println(" ${it.value}")
- }
+ favoritesAsListLocked().forEach {
+ pw.println(" ${ it }")
}
}
pw.println(bindingController.toString())
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
index b122439..89caace 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
@@ -29,6 +29,7 @@
import com.android.settingslib.applications.DefaultAppInfo
import com.android.settingslib.widget.CandidateInfo
import com.android.systemui.R
+import java.text.Collator
import java.util.concurrent.Executor
/**
@@ -44,23 +45,27 @@
* @param onAppSelected a callback to indicate that an app has been selected in the list.
*/
class AppAdapter(
+ backgroundExecutor: Executor,
uiExecutor: Executor,
lifecycle: Lifecycle,
controlsListingController: ControlsListingController,
private val layoutInflater: LayoutInflater,
private val onAppSelected: (ComponentName?) -> Unit = {},
- private val favoritesRenderer: FavoritesRenderer
+ private val favoritesRenderer: FavoritesRenderer,
+ private val resources: Resources
) : RecyclerView.Adapter<AppAdapter.Holder>() {
private var listOfServices = emptyList<CandidateInfo>()
private val callback = object : ControlsListingController.ControlsListingCallback {
override fun onServicesUpdated(list: List<CandidateInfo>) {
- uiExecutor.execute {
- listOfServices = list.sortedBy {
- it.loadLabel().toString()
+ backgroundExecutor.execute {
+ val collator = Collator.getInstance(resources.getConfiguration().locale)
+ val localeComparator = compareBy<CandidateInfo, CharSequence>(collator) {
+ it.loadLabel()
}
- notifyDataSetChanged()
+ listOfServices = list.sortedWith(localeComparator)
+ uiExecutor.execute(::notifyDataSetChanged)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
index 65dcc2b1..d3cabe6 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -25,101 +25,150 @@
import android.widget.CheckBox
import android.widget.ImageView
import android.widget.TextView
+import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.R
-import com.android.systemui.controls.ControlStatus
-import com.android.systemui.controls.controller.ControlInfo
import com.android.systemui.controls.ui.RenderInfo
+private typealias ModelFavoriteChanger = (String, Boolean) -> Unit
+
/**
* Adapter for binding [Control] information to views.
*
+ * The model for this adapter is provided by a [FavoriteModel] that is set using
+ * [changeFavoritesModel]. This allows for updating the model if there's a reload.
+ *
* @param layoutInflater an inflater for the views in the containing [RecyclerView]
- * @param favoriteCallback a callback to be called when the favorite status of a [Control] is
- * changed. The callback will take a [ControlInfo.Builder] that's
- * pre-populated with the [Control] information and the new favorite
- * status.
+ * @param onlyFavorites set to true to only display favorites instead of all controls
*/
class ControlAdapter(
private val layoutInflater: LayoutInflater,
- private val favoriteCallback: (ControlInfo.Builder, Boolean) -> Unit
-) : RecyclerView.Adapter<ControlAdapter.Holder>() {
+ private val onlyFavorites: Boolean = false
+) : RecyclerView.Adapter<Holder>() {
- var listOfControls = emptyList<ControlStatus>()
-
- override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder {
- return Holder(layoutInflater.inflate(R.layout.controls_base_item, parent, false).apply {
- layoutParams.apply {
- width = ViewGroup.LayoutParams.MATCH_PARENT
- }
- elevation = 15f
- })
+ companion object {
+ private const val TYPE_ZONE = 0
+ private const val TYPE_CONTROL = 1
}
- override fun getItemCount() = listOfControls.size
+ val spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
+ override fun getSpanSize(position: Int): Int {
+ return if (getItemViewType(position) == TYPE_ZONE) 2 else 1
+ }
+ }
+
+ var modelList: List<ElementWrapper> = emptyList()
+ private var favoritesModel: FavoriteModel? = null
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
+ return when (viewType) {
+ TYPE_CONTROL -> {
+ ControlHolder(
+ layoutInflater.inflate(R.layout.controls_base_item, parent, false).apply {
+ layoutParams.apply {
+ width = ViewGroup.LayoutParams.MATCH_PARENT
+ }
+ elevation = 15f
+ },
+ { id, favorite ->
+ favoritesModel?.changeFavoriteStatus(id, favorite)
+ })
+ }
+ TYPE_ZONE -> {
+ ZoneHolder(layoutInflater.inflate(R.layout.controls_zone_header, parent, false))
+ }
+ else -> throw IllegalStateException("Wrong viewType: $viewType")
+ }
+ }
+
+ fun changeFavoritesModel(favoritesModel: FavoriteModel) {
+ this.favoritesModel = favoritesModel
+ if (onlyFavorites) {
+ modelList = favoritesModel.favorites
+ } else {
+ modelList = favoritesModel.all
+ }
+ notifyDataSetChanged()
+ }
+
+ override fun getItemCount() = modelList.size
override fun onBindViewHolder(holder: Holder, index: Int) {
- holder.bindData(listOfControls[index], favoriteCallback)
+ holder.bindData(modelList[index])
}
+ override fun getItemViewType(position: Int): Int {
+ return when (modelList[position]) {
+ is ZoneNameWrapper -> TYPE_ZONE
+ is ControlWrapper -> TYPE_CONTROL
+ }
+ }
+}
+
+/**
+ * Holder for binding views in the [RecyclerView]-
+ * @param view the [View] for this [Holder]
+ */
+sealed class Holder(view: View) : RecyclerView.ViewHolder(view) {
+
/**
- * Holder for binding views in the [RecyclerView]-
+ * Bind the data from the model into the view
*/
- class Holder(view: View) : RecyclerView.ViewHolder(view) {
- private val icon: ImageView = itemView.requireViewById(R.id.icon)
- private val title: TextView = itemView.requireViewById(R.id.title)
- private val subtitle: TextView = itemView.requireViewById(R.id.subtitle)
- private val removed: TextView = itemView.requireViewById(R.id.status)
- private val favorite: CheckBox = itemView.requireViewById<CheckBox>(R.id.favorite).apply {
- visibility = View.VISIBLE
- }
+ abstract fun bindData(wrapper: ElementWrapper)
+}
- /**
- * Bind data to the view
- * @param data information about the [Control]
- * @param callback a callback to be called when the favorite status of the [Control] is
- * changed. The callback will take a [ControlInfo.Builder] that's
- * pre-populated with the [Control] information and the new favorite status.
- */
- fun bindData(data: ControlStatus, callback: (ControlInfo.Builder, Boolean) -> Unit) {
- val renderInfo = getRenderInfo(data.control.deviceType, data.favorite)
- title.text = data.control.title
- subtitle.text = data.control.subtitle
- favorite.isChecked = data.favorite
- removed.text = if (data.removed) "Removed" else ""
- favorite.setOnClickListener {
- val infoBuilder = ControlInfo.Builder().apply {
- controlId = data.control.controlId
- controlTitle = data.control.title
- deviceType = data.control.deviceType
- }
- callback(infoBuilder, favorite.isChecked)
- }
- itemView.setOnClickListener {
- favorite.performClick()
- }
- applyRenderInfo(renderInfo)
- }
+/**
+ * Holder for using with [ZoneNameWrapper] to display names of zones.
+ */
+private class ZoneHolder(view: View) : Holder(view) {
+ private val zone: TextView = itemView as TextView
- private fun getRenderInfo(
- @DeviceTypes.DeviceType deviceType: Int,
- favorite: Boolean
- ): RenderInfo {
- return RenderInfo.lookup(deviceType, favorite)
- }
+ override fun bindData(wrapper: ElementWrapper) {
+ wrapper as ZoneNameWrapper
+ zone.text = wrapper.zoneName
+ }
+}
- private fun applyRenderInfo(ri: RenderInfo) {
- val context = itemView.context
- val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme())
-
- icon.setImageIcon(Icon.createWithResource(context, ri.iconResourceId))
- icon.setImageTintList(fg)
- }
+/**
+ * Holder for using with [ControlWrapper] to display names of zones.
+ * @param favoriteCallback this callback will be called whenever the favorite state of the
+ * [Control] this view represents changes.
+ */
+private class ControlHolder(view: View, val favoriteCallback: ModelFavoriteChanger) : Holder(view) {
+ private val icon: ImageView = itemView.requireViewById(R.id.icon)
+ private val title: TextView = itemView.requireViewById(R.id.title)
+ private val subtitle: TextView = itemView.requireViewById(R.id.subtitle)
+ private val removed: TextView = itemView.requireViewById(R.id.status)
+ private val favorite: CheckBox = itemView.requireViewById<CheckBox>(R.id.favorite).apply {
+ visibility = View.VISIBLE
}
- fun setItems(list: List<ControlStatus>) {
- listOfControls = list
- notifyDataSetChanged()
+ override fun bindData(wrapper: ElementWrapper) {
+ wrapper as ControlWrapper
+ val data = wrapper.controlStatus
+ val renderInfo = getRenderInfo(data.control.deviceType)
+ title.text = data.control.title
+ subtitle.text = data.control.subtitle
+ favorite.isChecked = data.favorite
+ removed.text = if (data.removed) "Removed" else ""
+ favorite.setOnClickListener {
+ favoriteCallback(data.control.controlId, favorite.isChecked)
+ }
+ applyRenderInfo(renderInfo)
+ }
+
+ private fun getRenderInfo(
+ @DeviceTypes.DeviceType deviceType: Int
+ ): RenderInfo {
+ return RenderInfo.lookup(deviceType, true)
+ }
+
+ private fun applyRenderInfo(ri: RenderInfo) {
+ val context = itemView.context
+ val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme())
+
+ icon.setImageIcon(Icon.createWithResource(context, ri.iconResourceId))
+ icon.setImageTintList(fg)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index be52583..1e52371 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -18,10 +18,14 @@
import android.app.Activity
import android.content.ComponentName
+import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
+import android.view.ViewStub
+import android.widget.Button
import android.widget.TextView
import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -41,13 +45,36 @@
companion object {
private const val TAG = "ControlsFavoritingActivity"
const val EXTRA_APP = "extra_app_label"
- const val EXTRA_COMPONENT = "extra_component"
}
- private lateinit var recyclerView: RecyclerView
- private lateinit var adapter: ControlAdapter
+ private lateinit var recyclerViewAll: RecyclerView
+ private lateinit var adapterAll: ControlAdapter
+ private lateinit var recyclerViewFavorites: RecyclerView
+ private lateinit var adapterFavorites: ControlAdapter
private var component: ComponentName? = null
+ private var currentModel: FavoriteModel? = null
+ private var itemTouchHelperCallback = object : ItemTouchHelper.SimpleCallback(
+ /* dragDirs */ ItemTouchHelper.UP
+ or ItemTouchHelper.DOWN
+ or ItemTouchHelper.LEFT
+ or ItemTouchHelper.RIGHT,
+ /* swipeDirs */0
+ ) {
+ override fun onMove(
+ recyclerView: RecyclerView,
+ viewHolder: RecyclerView.ViewHolder,
+ target: RecyclerView.ViewHolder
+ ): Boolean {
+ return currentModel?.onMoveItem(
+ viewHolder.adapterPosition, target.adapterPosition) != null
+ }
+
+ override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {}
+
+ override fun isItemViewSwipeEnabled() = false
+ }
+
private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
private val startingUser = controller.currentUserId
@@ -62,41 +89,77 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.controls_management)
+ requireViewById<ViewStub>(R.id.stub).apply {
+ layoutResource = R.layout.controls_management_favorites
+ inflate()
+ }
val app = intent.getCharSequenceExtra(EXTRA_APP)
- component = intent.getParcelableExtra<ComponentName>(EXTRA_COMPONENT)
+ component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)
- // If we have no component name, there's not much we can do.
- val callback = component?.let {
- { infoBuilder: ControlInfo.Builder, status: Boolean ->
- infoBuilder.componentName = it
- controller.changeFavoriteStatus(infoBuilder.build(), status)
- }
- } ?: { _, _ -> Unit }
-
- recyclerView = requireViewById(R.id.list)
- adapter = ControlAdapter(LayoutInflater.from(applicationContext), callback)
- recyclerView.adapter = adapter
- recyclerView.layoutManager = GridLayoutManager(applicationContext, 2)
- val margin = resources.getDimensionPixelSize(R.dimen.controls_card_margin)
- recyclerView.addItemDecoration(MarginItemDecorator(margin, margin))
+ setUpRecyclerViews()
requireViewById<TextView>(R.id.title).text = app?.let { it }
?: resources.getText(R.string.controls_favorite_default_title)
requireViewById<TextView>(R.id.subtitle).text =
resources.getText(R.string.controls_favorite_subtitle)
- currentUserTracker.startTracking()
- }
+ requireViewById<Button>(R.id.done).setOnClickListener {
+ if (component == null) return@setOnClickListener
+ val favoritesForStorage = currentModel?.favorites?.map {
+ with(it.controlStatus.control) {
+ ControlInfo(component!!, controlId, title, deviceType)
+ }
+ }
+ if (favoritesForStorage != null) {
+ controller.replaceFavoritesForComponent(component!!, favoritesForStorage)
+ finishAffinity()
+ }
+ }
- override fun onResume() {
- super.onResume()
component?.let {
- controller.loadForComponent(it) {
+ controller.loadForComponent(it) { allControls, favoriteKeys ->
executor.execute {
- adapter.setItems(it)
+ val favoriteModel = FavoriteModel(
+ allControls,
+ favoriteKeys,
+ allAdapter = adapterAll,
+ favoritesAdapter = adapterFavorites)
+ adapterAll.changeFavoritesModel(favoriteModel)
+ adapterFavorites.changeFavoritesModel(favoriteModel)
+ currentModel = favoriteModel
}
}
}
+
+ currentUserTracker.startTracking()
+ }
+
+ private fun setUpRecyclerViews() {
+ val margin = resources.getDimensionPixelSize(R.dimen.controls_card_margin)
+ val itemDecorator = MarginItemDecorator(margin, margin)
+ val layoutInflater = LayoutInflater.from(applicationContext)
+
+ adapterAll = ControlAdapter(layoutInflater)
+ recyclerViewAll = requireViewById<RecyclerView>(R.id.listAll).apply {
+ adapter = adapterAll
+ layoutManager = GridLayoutManager(applicationContext, 2).apply {
+ spanSizeLookup = adapterAll.spanSizeLookup
+ }
+ addItemDecoration(itemDecorator)
+ }
+
+ adapterFavorites = ControlAdapter(layoutInflater, true)
+ recyclerViewFavorites = requireViewById<RecyclerView>(R.id.listFavorites).apply {
+ layoutManager = GridLayoutManager(applicationContext, 2)
+ adapter = adapterFavorites
+ addItemDecoration(itemDecorator)
+ }
+ ItemTouchHelper(itemTouchHelperCallback).attachToRecyclerView(recyclerViewFavorites)
+ }
+
+ override fun onDestroy() {
+ currentUserTracker.stopTracking()
+ super.onDestroy()
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index 645e929..ad4bdef 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -20,6 +20,7 @@
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
+import android.view.ViewStub
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@@ -63,11 +64,21 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.controls_management)
+ requireViewById<ViewStub>(R.id.stub).apply {
+ layoutResource = R.layout.controls_management_apps
+ inflate()
+ }
recyclerView = requireViewById(R.id.list)
- recyclerView.adapter = AppAdapter(executor, lifecycle, listingController,
- LayoutInflater.from(this), ::launchFavoritingActivity,
- FavoritesRenderer(resources, controlsController::countFavoritesForComponent))
+ recyclerView.adapter = AppAdapter(
+ backExecutor,
+ executor,
+ lifecycle,
+ listingController,
+ LayoutInflater.from(this),
+ ::launchFavoritingActivity,
+ FavoritesRenderer(resources, controlsController::countFavoritesForComponent),
+ resources)
recyclerView.layoutManager = LinearLayoutManager(applicationContext)
requireViewById<TextView>(R.id.title).text =
@@ -89,11 +100,16 @@
.apply {
putExtra(ControlsFavoritingActivity.EXTRA_APP,
listingController.getAppLabel(it))
- putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, it)
- flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
+ putExtra(Intent.EXTRA_COMPONENT_NAME, it)
+ flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
}
startActivity(intent)
}
}
}
+
+ override fun onDestroy() {
+ currentUserTracker.stopTracking()
+ super.onDestroy()
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt
new file mode 100644
index 0000000..6bade0a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.text.TextUtils
+import android.util.Log
+import com.android.systemui.controls.ControlStatus
+import java.util.Collections
+import java.util.Comparator
+
+/**
+ * Model for keeping track of current favorites and their order.
+ *
+ * This model is to be used with two [ControlAdapter] one that shows only favorites in the current
+ * order and another that shows all controls, separated by zone. When the favorite state of any
+ * control is modified or when the favorites are reordered, the adapters are notified of the change.
+ *
+ * @param listControls list of all the [ControlStatus] to display. This includes controls currently
+ * marked as favorites as well as those that have been removed (not returned
+ * from load)
+ * @param listFavoritesIds list of the [Control.controlId] for all the favorites, including those
+ * that have been removed.
+ * @param favoritesAdapter [ControlAdapter] used by the [RecyclerView] that shows only favorites
+ * @param allAdapter [ControlAdapter] used by the [RecyclerView] that shows all controls
+ */
+class FavoriteModel(
+ private val listControls: List<ControlStatus>,
+ listFavoritesIds: List<String>,
+ private val favoritesAdapter: ControlAdapter,
+ private val allAdapter: ControlAdapter
+) {
+
+ companion object {
+ private const val TAG = "FavoriteModel"
+ }
+
+ /**
+ * List of favorite controls ([ControlWrapper]) in order.
+ *
+ * Initially, this list will give a list of wrappers in the order specified by the constructor
+ * variable `listFavoriteIds`.
+ *
+ * As the favorites are added, removed or moved, this list will keep track of those changes.
+ */
+ val favorites: List<ControlWrapper> = listFavoritesIds.map { id ->
+ ControlWrapper(listControls.first { it.control.controlId == id })
+ }.toMutableList()
+
+ /**
+ * List of all controls by zones.
+ *
+ * Lists all the controls with the zone names interleaved as a flat list. After each zone name,
+ * the controls in that zone are listed. Zones are listed in alphabetical order
+ */
+ val all: List<ElementWrapper> = listControls.groupBy { it.control.zone }
+ .mapKeys { it.key ?: "" } // map null to empty
+ .toSortedMap(CharSequenceComparator())
+ .flatMap {
+ val controls = it.value.map { ControlWrapper(it) }
+ if (!TextUtils.isEmpty(it.key)) {
+ listOf(ZoneNameWrapper(it.key)) + controls
+ } else {
+ controls
+ }
+ }
+
+ /**
+ * Change the favorite status of a [Control].
+ *
+ * This can be invoked from any of the [ControlAdapter]. It will change the status of that
+ * control and either add it to the list of favorites (at the end) or remove it from it.
+ *
+ * Removing the favorite status from a Removed control will make it disappear completely if
+ * changes are saved.
+ *
+ * @param controlId the id of the [Control] to change the status
+ * @param favorite `true` if and only if it's set to be a favorite.
+ */
+ fun changeFavoriteStatus(controlId: String, favorite: Boolean) {
+ favorites as MutableList
+ val index = all.indexOfFirst {
+ it is ControlWrapper && it.controlStatus.control.controlId == controlId
+ }
+ val control = (all[index] as ControlWrapper).controlStatus
+ if (control.favorite == favorite) {
+ Log.d(TAG, "Changing favorite to same state for ${control.control.controlId} ")
+ return
+ } else {
+ control.favorite = favorite
+ }
+ allAdapter.notifyItemChanged(index)
+ if (favorite) {
+ favorites.add(all[index] as ControlWrapper)
+ favoritesAdapter.notifyItemInserted(favorites.size - 1)
+ } else {
+ val i = favorites.indexOfFirst { it.controlStatus.control.controlId == controlId }
+ favorites.removeAt(i)
+ favoritesAdapter.notifyItemRemoved(i)
+ }
+ }
+
+ /**
+ * Move items in the model and notify the [favoritesAdapter].
+ */
+ fun onMoveItem(from: Int, to: Int) {
+ if (from < to) {
+ for (i in from until to) {
+ Collections.swap(favorites, i, i + 1)
+ }
+ } else {
+ for (i in from downTo to + 1) {
+ Collections.swap(favorites, i, i - 1)
+ }
+ }
+ favoritesAdapter.notifyItemMoved(from, to)
+ }
+}
+
+/**
+ * Compares [CharSequence] as [String].
+ *
+ * It will have empty strings as the first element
+ */
+class CharSequenceComparator : Comparator<CharSequence> {
+ override fun compare(p0: CharSequence?, p1: CharSequence?): Int {
+ if (p0 == null && p1 == null) return 0
+ else if (p0 == null && p1 != null) return -1
+ else if (p0 != null && p1 == null) return 1
+ return p0.toString().compareTo(p1.toString())
+ }
+}
+
+/**
+ * Wrapper classes for the different types of elements shown in the [RecyclerView]s in
+ * [ControlsFavoritingActivity].
+ */
+sealed class ElementWrapper
+data class ZoneNameWrapper(val zoneName: CharSequence) : ElementWrapper()
+data class ControlWrapper(val controlStatus: ControlStatus) : ElementWrapper()
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index b3fc027..a3cd5fd 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -1045,7 +1045,9 @@
Action action = getItem(position);
View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext));
view.setOnClickListener(v -> onClickItem(position));
- view.setOnLongClickListener(v -> onLongClickItem(position));
+ if (action instanceof LongPressAction) {
+ view.setOnLongClickListener(v -> onLongClickItem(position));
+ }
return view;
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index f9b18cf..c7bfc06 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -280,7 +280,9 @@
if (mToActivityMessenger != null) {
Bundle data = new Bundle();
data.putInt(EXTRA_MENU_STATE, menuState);
- data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds);
+ if (stackBounds != null) {
+ data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds);
+ }
data.putParcelable(EXTRA_MOVEMENT_BOUNDS, movementBounds);
data.putBoolean(EXTRA_ALLOW_TIMEOUT, allowMenuTimeout);
data.putBoolean(EXTRA_WILL_RESIZE_MENU, willResizeMenu);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index ae61622..c118630f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -191,6 +191,7 @@
mTile.setLabel(tile.getLabel());
mTile.setSubtitle(tile.getSubtitle());
mTile.setContentDescription(tile.getContentDescription());
+ mTile.setStateDescription(tile.getStateDescription());
mTile.setState(tile.getState());
}
@@ -345,6 +346,12 @@
state.contentDescription = state.label;
}
+ if (mTile.getStateDescription() != null) {
+ state.stateDescription = mTile.getStateDescription();
+ } else {
+ state.stateDescription = null;
+ }
+
if (state instanceof BooleanState) {
state.expandedAccessibilityClassName = Switch.class.getName();
((BooleanState) state).value = (state.state == Tile.STATE_ACTIVE);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index 2fe64d2..8feee10 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -63,7 +63,6 @@
private String mAccessibilityClass;
private boolean mTileState;
private boolean mCollapsedView;
- private boolean mClicked;
private boolean mShowRippleEffect = true;
private final ImageView mBg;
@@ -230,13 +229,35 @@
setLongClickable(state.handlesLongClick);
mIcon.setIcon(state, allowAnimations);
setContentDescription(state.contentDescription);
+ final StringBuilder stateDescription = new StringBuilder();
+ switch (state.state) {
+ case Tile.STATE_UNAVAILABLE:
+ stateDescription.append(mContext.getString(R.string.tile_unavailable));
+ break;
+ case Tile.STATE_INACTIVE:
+ if (state instanceof QSTile.BooleanState) {
+ stateDescription.append(mContext.getString(R.string.switch_bar_off));
+ }
+ break;
+ case Tile.STATE_ACTIVE:
+ if (state instanceof QSTile.BooleanState) {
+ stateDescription.append(mContext.getString(R.string.switch_bar_on));
+ }
+ break;
+ default:
+ break;
+ }
+ if (!TextUtils.isEmpty(state.stateDescription)) {
+ stateDescription.append(", ");
+ stateDescription.append(state.stateDescription);
+ }
+ setStateDescription(stateDescription.toString());
mAccessibilityClass =
state.state == Tile.STATE_UNAVAILABLE ? null : state.expandedAccessibilityClassName;
if (state instanceof QSTile.BooleanState) {
boolean newState = ((BooleanState) state).value;
if (mTileState != newState) {
- mClicked = false;
mTileState = newState;
}
}
@@ -288,23 +309,10 @@
}
@Override
- public boolean performClick() {
- mClicked = true;
- return super.performClick();
- }
-
- @Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
if (!TextUtils.isEmpty(mAccessibilityClass)) {
event.setClassName(mAccessibilityClass);
- if (Switch.class.getName().equals(mAccessibilityClass)) {
- boolean b = mClicked ? !mTileState : mTileState;
- String label = getResources()
- .getString(b ? R.string.switch_bar_on : R.string.switch_bar_off);
- event.setContentDescription(label);
- event.setChecked(b);
- }
}
}
@@ -316,11 +324,13 @@
if (!TextUtils.isEmpty(mAccessibilityClass)) {
info.setClassName(mAccessibilityClass);
if (Switch.class.getName().equals(mAccessibilityClass)) {
- boolean b = mClicked ? !mTileState : mTileState;
- String label = getResources()
- .getString(b ? R.string.switch_bar_on : R.string.switch_bar_off);
+ String label = getResources().getString(
+ mTileState ? R.string.switch_bar_on : R.string.switch_bar_off);
+ // Set the text here for tests in
+ // android.platform.test.scenario.sysui.quicksettings. Can be removed when
+ // UiObject2 has a new getStateDescription() API and tests are updated.
info.setText(label);
- info.setChecked(b);
+ info.setChecked(mTileState);
info.setCheckable(true);
if (isLongClickable()) {
info.addAction(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 9282a2e..361b6c1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -134,25 +134,27 @@
state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
state.secondaryLabel = TextUtils.emptyIfNull(
getSecondaryLabel(enabled, connecting, connected, state.isTransient));
+ state.contentDescription = state.label;
+ state.stateDescription = "";
if (enabled) {
if (connected) {
state.icon = new BluetoothConnectedTileIcon();
if (!TextUtils.isEmpty(mController.getConnectedDeviceName())) {
state.label = mController.getConnectedDeviceName();
}
- state.contentDescription =
+ state.stateDescription =
mContext.getString(R.string.accessibility_bluetooth_name, state.label)
+ ", " + state.secondaryLabel;
} else if (state.isTransient) {
state.icon = ResourceIcon.get(
com.android.internal.R.drawable.ic_bluetooth_transient_animation);
- state.contentDescription = state.secondaryLabel;
+ state.stateDescription = state.secondaryLabel;
} else {
state.icon =
ResourceIcon.get(com.android.internal.R.drawable.ic_qs_bluetooth);
state.contentDescription = mContext.getString(
- R.string.accessibility_quick_settings_bluetooth) + ","
- + mContext.getString(R.string.accessibility_not_connected);
+ R.string.accessibility_quick_settings_bluetooth);
+ state.stateDescription = mContext.getString(R.string.accessibility_not_connected);
}
state.state = Tile.STATE_ACTIVE;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 32b051e..58de057 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -183,6 +183,7 @@
protected void handleUpdateState(BooleanState state, Object arg) {
state.label = mContext.getString(R.string.quick_settings_cast_title);
state.contentDescription = state.label;
+ state.stateDescription = "";
state.value = false;
final List<CastDevice> devices = mController.getCastDevices();
boolean connecting = false;
@@ -192,8 +193,9 @@
if (device.state == CastDevice.STATE_CONNECTED) {
state.value = true;
state.secondaryLabel = getDeviceName(device);
- state.contentDescription = state.contentDescription + ","
- + mContext.getString(R.string.accessibility_cast_name, state.label);
+ state.stateDescription = state.stateDescription + ","
+ + mContext.getString(
+ R.string.accessibility_cast_name, state.label);
connecting = false;
break;
} else if (device.state == CastDevice.STATE_CONNECTING) {
@@ -217,9 +219,8 @@
state.state = Tile.STATE_UNAVAILABLE;
String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi);
state.secondaryLabel = noWifi;
- state.contentDescription = state.contentDescription + ", " + mContext.getString(
- R.string.accessibility_quick_settings_not_available, noWifi);
}
+ state.stateDescription = state.stateDescription + ", " + state.secondaryLabel;
mDetailAdapter.updateItems(devices);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 22470c7..d5f86c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -194,17 +194,13 @@
state.secondaryLabel = r.getString(R.string.cell_data_off);
}
-
- // TODO(b/77881974): Instead of switching out the description via a string check for
- // we need to have two strings provided by the MobileIconGroup.
- final CharSequence contentDescriptionSuffix;
+ state.contentDescription = state.label;
if (state.state == Tile.STATE_INACTIVE) {
- contentDescriptionSuffix = r.getString(R.string.cell_data_off_content_description);
+ // This information is appended later by converting the Tile.STATE_INACTIVE state.
+ state.stateDescription = "";
} else {
- contentDescriptionSuffix = state.secondaryLabel;
+ state.stateDescription = state.secondaryLabel;
}
-
- state.contentDescription = state.label + ", " + contentDescriptionSuffix;
}
private CharSequence appendMobileDataType(CharSequence current, CharSequence dataType) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 52d1a5b3..9215da4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -240,6 +240,8 @@
zen != Global.ZEN_MODE_OFF, mController.getConfig(), false));
state.icon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_dnd);
checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME);
+ // Keeping the secondaryLabel in contentDescription instead of stateDescription is easier
+ // to understand.
switch (zen) {
case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
state.contentDescription =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index dafdd89..792c364 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -102,14 +102,13 @@
}
state.label = mHost.getContext().getString(R.string.quick_settings_flashlight_label);
state.secondaryLabel = "";
+ state.stateDescription = "";
if (!mFlashlightController.isAvailable()) {
state.icon = mIcon;
state.slash.isSlashed = true;
state.secondaryLabel = mContext.getString(
R.string.quick_settings_flashlight_camera_in_use);
- state.contentDescription = mContext.getString(
- R.string.accessibility_quick_settings_flashlight_unavailable)
- + ", " + state.secondaryLabel;
+ state.stateDescription = state.secondaryLabel;
state.state = Tile.STATE_UNAVAILABLE;
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 42f8010..91b3ae4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -148,6 +148,7 @@
state.secondaryLabel = getSecondaryLabel(
isTileActive, isTransient, isDataSaverEnabled, numConnectedDevices);
+ state.stateDescription = state.secondaryLabel;
}
@Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index fbdca3b..e617867 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -105,15 +105,8 @@
}
state.icon = mIcon;
state.slash.isSlashed = !state.value;
- if (locationEnabled) {
- state.label = mContext.getString(R.string.quick_settings_location_label);
- state.contentDescription = mContext.getString(
- R.string.accessibility_quick_settings_location_on);
- } else {
- state.label = mContext.getString(R.string.quick_settings_location_label);
- state.contentDescription = mContext.getString(
- R.string.accessibility_quick_settings_location_off);
- }
+ state.label = mContext.getString(R.string.quick_settings_location_label);
+ state.contentDescription = state.label;
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.expandedAccessibilityClassName = Switch.class.getName();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index b7ce101..6e8dcf3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -195,6 +195,7 @@
state.activityIn = cb.enabled && cb.activityIn;
state.activityOut = cb.enabled && cb.activityOut;
final StringBuffer minimalContentDescription = new StringBuffer();
+ final StringBuffer minimalStateDescription = new StringBuffer();
final Resources r = mContext.getResources();
if (isTransient) {
state.icon = ResourceIcon.get(
@@ -219,13 +220,14 @@
mContext.getString(R.string.quick_settings_wifi_label)).append(",");
if (state.value) {
if (wifiConnected) {
- minimalContentDescription.append(cb.wifiSignalContentDescription).append(",");
+ minimalStateDescription.append(cb.wifiSignalContentDescription);
minimalContentDescription.append(removeDoubleQuotes(cb.ssid));
if (!TextUtils.isEmpty(state.secondaryLabel)) {
minimalContentDescription.append(",").append(state.secondaryLabel);
}
}
}
+ state.stateDescription = minimalStateDescription.toString();
state.contentDescription = minimalContentDescription.toString();
state.dualLabelContentDescription = r.getString(
R.string.accessibility_quick_settings_open_settings, getTileLabel());
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 7853dc3..e54ee51 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -103,14 +103,11 @@
state.icon = mIcon;
if (state.value) {
state.slash.isSlashed = false;
- state.contentDescription = mContext.getString(
- R.string.accessibility_quick_settings_work_mode_on);
} else {
state.slash.isSlashed = true;
- state.contentDescription = mContext.getString(
- R.string.accessibility_quick_settings_work_mode_off);
}
state.label = mContext.getString(R.string.quick_settings_work_mode_label);
+ state.contentDescription = state.label;
state.expandedAccessibilityClassName = Switch.class.getName();
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 880b8f8..4f38a15 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -69,6 +69,7 @@
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -143,7 +144,7 @@
private static final float BACKGROUND_ALPHA = 0.5f;
private static final float SCREENSHOT_DROP_IN_MIN_SCALE = 0.725f;
private static final float ROUNDED_CORNER_RADIUS = .05f;
- private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 8000;
+ private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 6000;
private static final int MESSAGE_CORNER_TIMEOUT = 2;
private final ScreenshotNotificationsController mNotificationsController;
@@ -162,6 +163,7 @@
private final HorizontalScrollView mActionsContainer;
private final LinearLayout mActionsView;
private final ImageView mBackgroundProtection;
+ private final FrameLayout mDismissButton;
private Bitmap mScreenBitmap;
private AnimatorSet mScreenshotAnimation;
@@ -170,6 +172,7 @@
private float mScreenshotOffsetYPx;
private float mScreenshotHeightPx;
private float mCornerScale;
+ private float mDismissButtonSize;
private AsyncTask<Void, Void, Void> mSaveInBgTask;
@@ -216,19 +219,14 @@
mActionsView = mScreenshotLayout.findViewById(R.id.global_screenshot_actions);
mBackgroundProtection = mScreenshotLayout.findViewById(
R.id.global_screenshot_actions_background);
+ mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button);
+ mDismissButton.setOnClickListener(view -> clearScreenshot("dismiss_button"));
mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector);
mScreenshotLayout.setFocusable(true);
mScreenshotSelectorView.setFocusable(true);
mScreenshotSelectorView.setFocusableInTouchMode(true);
- mScreenshotLayout.setOnTouchListener((v, event) -> {
- if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
- clearScreenshot("tap_outside");
- }
- // Intercept and ignore all touch events
- return true;
- });
// Setup the window that we are going to use
mWindowLayoutParams = new WindowManager.LayoutParams(
@@ -254,6 +252,8 @@
resources.getDimensionPixelSize(R.dimen.screenshot_action_container_offset_y);
mCornerScale = resources.getDimensionPixelSize(R.dimen.global_screenshot_x_scale)
/ (float) mDisplayMetrics.widthPixels;
+ mDismissButtonSize = resources.getDimensionPixelSize(
+ R.dimen.screenshot_dismiss_button_tappable_size);
// Setup the Camera shutter sound
mCameraSound = new MediaActionSound();
@@ -271,6 +271,9 @@
Rect actionsRect = new Rect();
mActionsContainer.getBoundsOnScreen(actionsRect);
touchRegion.op(actionsRect, Region.Op.UNION);
+ Rect dismissRect = new Rect();
+ mDismissButton.getBoundsOnScreen(dismissRect);
+ touchRegion.op(dismissRect, Region.Op.UNION);
inoutInfo.touchableRegion.set(touchRegion);
}
@@ -408,6 +411,7 @@
mActionsContainer.setVisibility(View.GONE);
mBackgroundView.setVisibility(View.GONE);
mBackgroundProtection.setAlpha(0f);
+ mDismissButton.setVisibility(View.GONE);
mScreenshotView.setVisibility(View.GONE);
mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
}
@@ -615,6 +619,17 @@
mScreenshotView.setTranslationX(t * finalPos.x);
mScreenshotView.setTranslationY(t * finalPos.y);
});
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ Rect bounds = new Rect();
+ mScreenshotView.getBoundsOnScreen(bounds);
+ mDismissButton.setX(bounds.right - mDismissButtonSize / 2f);
+ mDismissButton.setY(bounds.top - mDismissButtonSize / 2f);
+ mDismissButton.setVisibility(View.VISIBLE);
+ }
+ });
return anim;
}
@@ -686,14 +701,6 @@
mBackgroundProtection.setAlpha(t);
mActionsContainer.setY(mDisplayMetrics.heightPixels - actionsViewHeight * t);
});
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- mScreenshotView.requestFocus();
- mScreenshotView.setElevation(50);
- }
- });
return animator;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index bb0681c..a0af4ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -23,14 +23,6 @@
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
-import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_BUBBLE;
-import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_DEMOTE;
-import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_FAVORITE;
-import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_HOME;
-import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_MUTE;
-import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_SNOOZE;
-import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_UNBUBBLE;
-
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
@@ -69,11 +61,13 @@
import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.phone.ShadeController;
import java.lang.annotation.Retention;
import java.util.Arrays;
@@ -92,6 +86,7 @@
ShortcutManager mShortcutManager;
private PackageManager mPm;
private VisualStabilityManager mVisualStabilityManager;
+ private ShadeController mShadeController;
private String mPackageName;
private String mAppName;
@@ -103,24 +98,30 @@
private NotificationEntry mEntry;
private StatusBarNotification mSbn;
private boolean mIsDeviceProvisioned;
- private int mStartingChannelImportance;
private boolean mStartedAsBubble;
private boolean mIsBubbleable;
- // TODO: remove when launcher api works
- @VisibleForTesting
- boolean mShowHomeScreen = false;
- private @UpdateChannelRunnable.Action int mSelectedAction = -1;
+ private @Action int mSelectedAction = -1;
private OnSnoozeClickListener mOnSnoozeClickListener;
private OnSettingsClickListener mOnSettingsClickListener;
- private OnAppSettingsClickListener mAppSettingsClickListener;
private NotificationGuts mGutsContainer;
private BubbleController mBubbleController;
@VisibleForTesting
boolean mSkipPost = false;
+ @Retention(SOURCE)
+ @IntDef({ACTION_BUBBLE, ACTION_HOME, ACTION_FAVORITE, ACTION_SNOOZE, ACTION_MUTE})
+ private @interface Action {}
+ static final int ACTION_BUBBLE = 0;
+ static final int ACTION_HOME = 1;
+ static final int ACTION_FAVORITE = 2;
+ static final int ACTION_SNOOZE = 3;
+ static final int ACTION_MUTE = 4;
+ static final int ACTION_SETTINGS = 5;
+ static final int ACTION_UNBUBBLE = 6;
+
private OnClickListener mOnBubbleClick = v -> {
mSelectedAction = mStartedAsBubble ? ACTION_UNBUBBLE : ACTION_BUBBLE;
if (mStartedAsBubble) {
@@ -136,12 +137,14 @@
private OnClickListener mOnHomeClick = v -> {
mSelectedAction = ACTION_HOME;
mShortcutManager.requestPinShortcut(mShortcutInfo, null);
+ mShadeController.animateCollapsePanels();
closeControls(v, true);
};
private OnClickListener mOnFavoriteClick = v -> {
mSelectedAction = ACTION_FAVORITE;
- closeControls(v, true);
+ updateChannel();
+
};
private OnClickListener mOnSnoozeClick = v -> {
@@ -151,13 +154,8 @@
};
private OnClickListener mOnMuteClick = v -> {
- mSelectedAction = ACTION_MUTE;
- closeControls(v, true);
- };
-
- private OnClickListener mOnDemoteClick = v -> {
- mSelectedAction = ACTION_DEMOTE;
- closeControls(v, true);
+ mSelectedAction = ACTION_MUTE;
+ updateChannel();
};
public NotificationConversationInfo(Context context, AttributeSet attrs) {
@@ -197,15 +195,14 @@
mEntry = entry;
mSbn = entry.getSbn();
mPm = pm;
- mAppSettingsClickListener = onAppSettingsClick;
mAppName = mPackageName;
mOnSettingsClickListener = onSettingsClick;
mNotificationChannel = notificationChannel;
- mStartingChannelImportance = mNotificationChannel.getImportance();
mAppUid = mSbn.getUid();
mDelegatePkg = mSbn.getOpPkg();
mIsDeviceProvisioned = isDeviceProvisioned;
mOnSnoozeClickListener = onSnoozeClickListener;
+ mShadeController = Dependency.get(ShadeController.class);
mShortcutManager = shortcutManager;
mLauncherApps = launcherApps;
@@ -251,9 +248,6 @@
mNotificationChannel = mINotificationManager.getConversationNotificationChannel(
mContext.getOpPackageName(), UserHandle.getUserId(mAppUid), mPackageName,
mNotificationChannel.getId(), false, mConversationId);
-
- // TODO: ask LA to pin the shortcut once api exists for pinning one shortcut at a
- // time
} catch (RemoteException e) {
Slog.e(TAG, "Could not create conversation channel", e);
}
@@ -274,40 +268,24 @@
Button home = findViewById(R.id.home);
home.setOnClickListener(mOnHomeClick);
- home.setVisibility(mShowHomeScreen && mShortcutInfo != null
+ home.setVisibility(mShortcutInfo != null
&& mShortcutManager.isRequestPinShortcutSupported()
? VISIBLE : GONE);
- Button favorite = findViewById(R.id.fave);
+ View favorite = findViewById(R.id.fave);
favorite.setOnClickListener(mOnFavoriteClick);
- if (mNotificationChannel.isImportantConversation()) {
- favorite.setText(R.string.notification_conversation_unfavorite);
- favorite.setCompoundDrawablesRelative(
- mContext.getDrawable(R.drawable.ic_star), null, null, null);
- } else {
- favorite.setText(R.string.notification_conversation_favorite);
- favorite.setCompoundDrawablesRelative(
- mContext.getDrawable(R.drawable.ic_star_border), null, null, null);
- }
Button snooze = findViewById(R.id.snooze);
snooze.setOnClickListener(mOnSnoozeClick);
- Button mute = findViewById(R.id.mute);
+ View mute = findViewById(R.id.mute);
mute.setOnClickListener(mOnMuteClick);
- if (mStartingChannelImportance >= IMPORTANCE_DEFAULT
- || mStartingChannelImportance == IMPORTANCE_UNSPECIFIED) {
- mute.setText(R.string.notification_conversation_mute);
- favorite.setCompoundDrawablesRelative(
- mContext.getDrawable(R.drawable.ic_notifications_silence), null, null, null);
- } else {
- mute.setText(R.string.notification_conversation_unmute);
- favorite.setCompoundDrawablesRelative(
- mContext.getDrawable(R.drawable.ic_notifications_alert), null, null, null);
- }
- ImageButton demote = findViewById(R.id.demote);
- demote.setOnClickListener(mOnDemoteClick);
+ final View settingsButton = findViewById(R.id.info);
+ settingsButton.setOnClickListener(getSettingsOnClickListener());
+ settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE);
+
+ updateToggleActions();
}
private void bindHeader() {
@@ -315,26 +293,6 @@
// Delegate
bindDelegate();
-
- // Set up app settings link (i.e. Customize)
- View settingsLinkView = findViewById(R.id.app_settings);
- Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName,
- mNotificationChannel,
- mSbn.getId(), mSbn.getTag());
- if (settingsIntent != null
- && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
- settingsLinkView.setVisibility(VISIBLE);
- settingsLinkView.setOnClickListener((View view) -> {
- mAppSettingsClickListener.onClick(view, settingsIntent);
- });
- } else {
- settingsLinkView.setVisibility(View.GONE);
- }
-
- // System Settings button.
- final View settingsButton = findViewById(R.id.info);
- settingsButton.setOnClickListener(getSettingsOnClickListener());
- settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE);
}
private OnClickListener getSettingsOnClickListener() {
@@ -424,15 +382,12 @@
private void bindDelegate() {
TextView delegateView = findViewById(R.id.delegate_name);
- TextView dividerView = findViewById(R.id.pkg_divider);
if (!TextUtils.equals(mPackageName, mDelegatePkg)) {
// this notification was posted by a delegate!
delegateView.setVisibility(View.VISIBLE);
- dividerView.setVisibility(View.VISIBLE);
} else {
delegateView.setVisibility(View.GONE);
- dividerView.setVisibility(View.GONE);
}
}
@@ -492,26 +447,37 @@
}
}
- private Intent getAppSettingsIntent(PackageManager pm, String packageName,
- NotificationChannel channel, int id, String tag) {
- Intent intent = new Intent(Intent.ACTION_MAIN)
- .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
- .setPackage(packageName);
- final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
- intent,
- PackageManager.MATCH_DEFAULT_ONLY
- );
- if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) {
- return null;
+ private void updateToggleActions() {
+ ImageButton favorite = findViewById(R.id.fave);
+ if (mNotificationChannel.isImportantConversation()) {
+ favorite.setContentDescription(
+ mContext.getString(R.string.notification_conversation_favorite));
+ favorite.setImageResource(R.drawable.ic_important);
+ } else {
+ favorite.setContentDescription(
+ mContext.getString(R.string.notification_conversation_unfavorite));
+ favorite.setImageResource(R.drawable.ic_important_outline);
}
- final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
- intent.setClassName(activityInfo.packageName, activityInfo.name);
- if (channel != null) {
- intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId());
+
+ ImageButton mute = findViewById(R.id.mute);
+ if (mNotificationChannel.getImportance() >= IMPORTANCE_DEFAULT
+ || mNotificationChannel.getImportance() == IMPORTANCE_UNSPECIFIED) {
+ mute.setContentDescription(
+ mContext.getString(R.string.notification_conversation_unmute));
+ mute.setImageResource(R.drawable.ic_notifications_alert);
+ } else {
+ mute.setContentDescription(
+ mContext.getString(R.string.notification_conversation_mute));
+ mute.setImageResource(R.drawable.ic_notifications_silence);
}
- intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id);
- intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag);
- return intent;
+ }
+
+ private void updateChannel() {
+ Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
+ bgHandler.post(
+ new UpdateChannelRunnable(mINotificationManager, mPackageName,
+ mAppUid, mSelectedAction, mNotificationChannel));
+ mVisualStabilityManager.temporarilyAllowReordering();
}
/**
@@ -556,11 +522,7 @@
@Override
public boolean handleCloseControls(boolean save, boolean force) {
if (save && mSelectedAction > -1) {
- Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
- bgHandler.post(
- new UpdateChannelRunnable(mINotificationManager, mPackageName,
- mAppUid, mSelectedAction, mNotificationChannel));
- mVisualStabilityManager.temporarilyAllowReordering();
+ updateChannel();
}
return false;
}
@@ -575,19 +537,7 @@
return false;
}
- static class UpdateChannelRunnable implements Runnable {
-
- @Retention(SOURCE)
- @IntDef({ACTION_BUBBLE, ACTION_HOME, ACTION_FAVORITE, ACTION_SNOOZE, ACTION_MUTE,
- ACTION_DEMOTE})
- private @interface Action {}
- static final int ACTION_BUBBLE = 0;
- static final int ACTION_HOME = 1;
- static final int ACTION_FAVORITE = 2;
- static final int ACTION_SNOOZE = 3;
- static final int ACTION_MUTE = 4;
- static final int ACTION_DEMOTE = 5;
- static final int ACTION_UNBUBBLE = 6;
+ class UpdateChannelRunnable implements Runnable {
private final INotificationManager mINotificationManager;
private final String mAppPkg;
@@ -633,10 +583,6 @@
mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT));
}
break;
- case ACTION_DEMOTE:
- mChannelToUpdate.setDemoted(!mChannelToUpdate.isDemoted());
- break;
-
}
if (channelSettingChanged) {
@@ -646,13 +592,7 @@
} catch (RemoteException e) {
Log.e(TAG, "Unable to update notification channel", e);
}
+ ThreadUtils.postOnMainThread(() -> updateToggleActions());
}
}
-
- @Retention(SOURCE)
- @IntDef({BEHAVIOR_ALERTING, BEHAVIOR_SILENT, BEHAVIOR_BUBBLE})
- private @interface AlertingBehavior {}
- private static final int BEHAVIOR_ALERTING = 0;
- private static final int BEHAVIOR_SILENT = 1;
- private static final int BEHAVIOR_BUBBLE = 2;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 6789c81..352abcf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -58,6 +58,7 @@
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -384,6 +385,7 @@
guts.resetFalsingCheck();
mOnSettingsClickListener.onSettingsClick(sbn.getKey());
startAppNotificationSettingsActivity(packageName, appUid, channel, row);
+ notificationInfoView.closeControls(v, false);
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 9e64748..3f5215e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -269,6 +269,17 @@
}
};
+ private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
+ new DeviceConfig.OnPropertiesChangedListener() {
+ @Override
+ public void onPropertiesChanged(DeviceConfig.Properties properties) {
+ if (properties.getKeyset().contains(NAV_BAR_HANDLE_FORCE_OPAQUE)) {
+ mForceNavBarHandleOpaque = properties.getBoolean(
+ NAV_BAR_HANDLE_FORCE_OPAQUE, /* defaultValue = */ true);
+ }
+ }
+ };
+
@Inject
public NavigationBarFragment(AccessibilityManagerWrapper accessibilityManagerWrapper,
DeviceProvisionedController deviceProvisionedController, MetricsLogger metricsLogger,
@@ -298,21 +309,6 @@
mDivider = divider;
mRecentsOptional = recentsOptional;
mHandler = mainHandler;
-
- mForceNavBarHandleOpaque = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI,
- NAV_BAR_HANDLE_FORCE_OPAQUE,
- /* defaultValue = */ true);
- DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post,
- new DeviceConfig.OnPropertiesChangedListener() {
- @Override
- public void onPropertiesChanged(DeviceConfig.Properties properties) {
- if (properties.getKeyset().contains(NAV_BAR_HANDLE_FORCE_OPAQUE)) {
- mForceNavBarHandleOpaque = properties.getBoolean(
- NAV_BAR_HANDLE_FORCE_OPAQUE, /* defaultValue = */ true);
- }
- }
- });
}
// ----- Fragment Lifecycle Callbacks -----
@@ -338,6 +334,13 @@
// Respect the latest disabled-flags.
mCommandQueue.recomputeDisableFlags(mDisplayId, false);
+
+ mForceNavBarHandleOpaque = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ NAV_BAR_HANDLE_FORCE_OPAQUE,
+ /* defaultValue = */ true);
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, mOnPropertiesChangedListener);
}
@Override
@@ -346,6 +349,8 @@
mNavigationModeController.removeListener(this);
mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener);
mContentResolver.unregisterContentObserver(mAssistContentObserver);
+
+ DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index e3bcdc8..751217f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -39,6 +39,7 @@
import com.android.systemui.util.time.FakeSystemClock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
@@ -225,7 +226,7 @@
val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
val control = builderFromInfo(newControlInfo).build()
- controller.loadForComponent(TEST_COMPONENT) {}
+ controller.loadForComponent(TEST_COMPONENT) { _, _ -> Unit }
reset(persistenceWrapper)
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
@@ -293,11 +294,13 @@
var loaded = false
val control = builderFromInfo(TEST_CONTROL_INFO).build()
- controller.loadForComponent(TEST_COMPONENT) {
+ controller.loadForComponent(TEST_COMPONENT) { controls, favorites ->
loaded = true
- assertEquals(1, it.size)
- val controlStatus = it[0]
+ assertEquals(1, controls.size)
+ val controlStatus = controls[0]
assertEquals(ControlStatus(control, false), controlStatus)
+
+ assertTrue(favorites.isEmpty())
}
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
@@ -315,14 +318,17 @@
val control2 = builderFromInfo(TEST_CONTROL_INFO_2).build()
controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
- controller.loadForComponent(TEST_COMPONENT) {
+ controller.loadForComponent(TEST_COMPONENT) { controls, favorites ->
loaded = true
- assertEquals(2, it.size)
- val controlStatus = it.first { it.control.controlId == TEST_CONTROL_ID }
+ assertEquals(2, controls.size)
+ val controlStatus = controls.first { it.control.controlId == TEST_CONTROL_ID }
assertEquals(ControlStatus(control, true), controlStatus)
- val controlStatus2 = it.first { it.control.controlId == TEST_CONTROL_ID_2 }
+ val controlStatus2 = controls.first { it.control.controlId == TEST_CONTROL_ID_2 }
assertEquals(ControlStatus(control2, false), controlStatus2)
+
+ assertEquals(1, favorites.size)
+ assertEquals(TEST_CONTROL_ID, favorites[0])
}
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
@@ -338,13 +344,16 @@
var loaded = false
controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
- controller.loadForComponent(TEST_COMPONENT) {
+ controller.loadForComponent(TEST_COMPONENT) { controls, favorites ->
loaded = true
- assertEquals(1, it.size)
- val controlStatus = it[0]
+ assertEquals(1, controls.size)
+ val controlStatus = controls[0]
assertEquals(TEST_CONTROL_ID, controlStatus.control.controlId)
assertTrue(controlStatus.favorite)
assertTrue(controlStatus.removed)
+
+ assertEquals(1, favorites.size)
+ assertEquals(TEST_CONTROL_ID, favorites[0])
}
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
@@ -361,7 +370,7 @@
val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
val control = builderFromInfo(newControlInfo).build()
- controller.loadForComponent(TEST_COMPONENT) {}
+ controller.loadForComponent(TEST_COMPONENT) { _, _ -> Unit }
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
capture(controlLoadCallbackCaptor))
@@ -483,4 +492,81 @@
assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT))
assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT_2))
}
+
+ @Test
+ fun testGetFavoritesForComponent() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT))
+ }
+
+ @Test
+ fun testGetFavoritesForComponent_otherComponent() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true)
+ assertTrue(controller.getFavoritesForComponent(TEST_COMPONENT).isEmpty())
+ }
+
+ @Test
+ fun testGetFavoritesForComponent_multipleInOrder() {
+ val controlInfo = ControlInfo(TEST_COMPONENT, "id", "title", 0)
+
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ controller.changeFavoriteStatus(controlInfo, true)
+
+ assertEquals(listOf(TEST_CONTROL_INFO, controlInfo),
+ controller.getFavoritesForComponent(TEST_COMPONENT))
+
+ controller.clearFavorites()
+
+ controller.changeFavoriteStatus(controlInfo, true)
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ assertEquals(listOf(controlInfo, TEST_CONTROL_INFO),
+ controller.getFavoritesForComponent(TEST_COMPONENT))
+ }
+
+ @Test
+ fun testReplaceFavoritesForComponent_noFavorites() {
+ controller.replaceFavoritesForComponent(TEST_COMPONENT, listOf(TEST_CONTROL_INFO))
+
+ assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT))
+ assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT))
+ }
+
+ @Test
+ fun testReplaceFavoritesForComponent_differentComponentsAreFilteredOut() {
+ controller.replaceFavoritesForComponent(TEST_COMPONENT,
+ listOf(TEST_CONTROL_INFO, TEST_CONTROL_INFO_2))
+
+ assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT))
+ assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT))
+ }
+
+ @Test
+ fun testReplaceFavoritesForComponent_oldFavoritesRemoved() {
+ val controlInfo = ControlInfo(TEST_COMPONENT, "id", "title", 0)
+ assertNotEquals(TEST_CONTROL_INFO, controlInfo)
+
+ controller.changeFavoriteStatus(controlInfo, true)
+ controller.replaceFavoritesForComponent(TEST_COMPONENT, listOf(TEST_CONTROL_INFO))
+
+ assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT))
+ assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT))
+ }
+
+ @Test
+ fun testReplaceFavoritesForComponent_favoritesInOrder() {
+ val controlInfo = ControlInfo(TEST_COMPONENT, "id", "title", 0)
+
+ val listOrder1 = listOf(TEST_CONTROL_INFO, controlInfo)
+ controller.replaceFavoritesForComponent(TEST_COMPONENT, listOrder1)
+
+ assertEquals(2, controller.countFavoritesForComponent(TEST_COMPONENT))
+ assertEquals(listOrder1, controller.getFavoritesForComponent(TEST_COMPONENT))
+
+ val listOrder2 = listOf(controlInfo, TEST_CONTROL_INFO)
+ controller.replaceFavoritesForComponent(TEST_COMPONENT, listOrder2)
+
+ assertEquals(2, controller.countFavoritesForComponent(TEST_COMPONENT))
+ assertEquals(listOrder2, controller.getFavoritesForComponent(TEST_COMPONENT))
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt
new file mode 100644
index 0000000..9ffc29e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.app.PendingIntent
+import android.service.controls.Control
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlStatus
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+open class FavoriteModelTest : SysuiTestCase() {
+
+ @Mock
+ lateinit var pendingIntent: PendingIntent
+ @Mock
+ lateinit var allAdapter: ControlAdapter
+ @Mock
+ lateinit var favoritesAdapter: ControlAdapter
+
+ val idPrefix = "controlId"
+ val favoritesIndices = listOf(7, 3, 1, 9)
+ val favoritesList = favoritesIndices.map { "controlId$it" }
+ lateinit var controls: List<ControlStatus>
+
+ lateinit var model: FavoriteModel
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ // controlId0 --> zone = 0
+ // controlId1 --> zone = 1, favorite
+ // controlId2 --> zone = 2
+ // controlId3 --> zone = 0, favorite
+ // controlId4 --> zone = 1
+ // controlId5 --> zone = 2
+ // controlId6 --> zone = 0
+ // controlId7 --> zone = 1, favorite
+ // controlId8 --> zone = 2
+ // controlId9 --> zone = 0, favorite
+ controls = (0..9).map {
+ ControlStatus(
+ Control.StatelessBuilder("$idPrefix$it", pendingIntent)
+ .setZone((it % 3).toString())
+ .build(),
+ it in favoritesIndices
+ )
+ }
+
+ model = FavoriteModel(controls, favoritesList, favoritesAdapter, allAdapter)
+ }
+}
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class FavoriteModelNonParametrizedTests : FavoriteModelTest() {
+ @Test
+ fun testAll() {
+ // Zones are sorted alphabetically
+ val expected = listOf(
+ ZoneNameWrapper("0"),
+ ControlWrapper(controls[0]),
+ ControlWrapper(controls[3]),
+ ControlWrapper(controls[6]),
+ ControlWrapper(controls[9]),
+ ZoneNameWrapper("1"),
+ ControlWrapper(controls[1]),
+ ControlWrapper(controls[4]),
+ ControlWrapper(controls[7]),
+ ZoneNameWrapper("2"),
+ ControlWrapper(controls[2]),
+ ControlWrapper(controls[5]),
+ ControlWrapper(controls[8])
+ )
+ assertEquals(expected, model.all)
+ }
+
+ @Test
+ fun testFavoritesInOrder() {
+ val expected = favoritesIndices.map { ControlWrapper(controls[it]) }
+ assertEquals(expected, model.favorites)
+ }
+
+ @Test
+ fun testChangeFavoriteStatus_addFavorite() {
+ val controlToAdd = 6
+ model.changeFavoriteStatus("$idPrefix$controlToAdd", true)
+
+ val pair = model.all.findControl(controlToAdd)
+ pair?.let {
+ assertTrue(it.second.favorite)
+ assertEquals(it.second, model.favorites.last().controlStatus)
+ verify(favoritesAdapter).notifyItemInserted(model.favorites.size - 1)
+ verify(allAdapter).notifyItemChanged(it.first)
+ verifyNoMoreInteractions(favoritesAdapter, allAdapter)
+ } ?: run {
+ fail("control not found")
+ }
+ }
+
+ @Test
+ fun testChangeFavoriteStatus_removeFavorite() {
+ val controlToRemove = 3
+ model.changeFavoriteStatus("$idPrefix$controlToRemove", false)
+
+ val pair = model.all.findControl(controlToRemove)
+ pair?.let {
+ assertFalse(it.second.favorite)
+ assertTrue(model.favorites.none {
+ it.controlStatus.control.controlId == "$idPrefix$controlToRemove"
+ })
+ verify(favoritesAdapter).notifyItemRemoved(favoritesIndices.indexOf(controlToRemove))
+ verify(allAdapter).notifyItemChanged(it.first)
+ verifyNoMoreInteractions(favoritesAdapter, allAdapter)
+ } ?: run {
+ fail("control not found")
+ }
+ }
+
+ @Test
+ fun testChangeFavoriteStatus_sameStatus() {
+ model.changeFavoriteStatus("${idPrefix}7", true)
+ model.changeFavoriteStatus("${idPrefix}6", false)
+
+ val expected = favoritesIndices.map { ControlWrapper(controls[it]) }
+ assertEquals(expected, model.favorites)
+
+ verifyNoMoreInteractions(favoritesAdapter, allAdapter)
+ }
+
+ private fun List<ElementWrapper>.findControl(controlIndex: Int): Pair<Int, ControlStatus>? {
+ val index = indexOfFirst {
+ it is ControlWrapper &&
+ it.controlStatus.control.controlId == "$idPrefix$controlIndex"
+ }
+ return if (index == -1) null else index to (get(index) as ControlWrapper).controlStatus
+ }
+}
+
+@SmallTest
+@RunWith(Parameterized::class)
+class FavoriteModelParameterizedTest(val from: Int, val to: Int) : FavoriteModelTest() {
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0} -> {1}")
+ fun data(): Collection<Array<Int>> {
+ return (0..3).flatMap { from ->
+ (0..3).map { to ->
+ arrayOf(from, to)
+ }
+ }.filterNot { it[0] == it[1] }
+ }
+ }
+
+ @Test
+ fun testMoveItem() {
+ val originalFavorites = model.favorites.toList()
+ val originalFavoritesIds =
+ model.favorites.map { it.controlStatus.control.controlId }.toSet()
+ model.onMoveItem(from, to)
+ assertEquals(originalFavorites[from], model.favorites[to])
+ // Check that we still have the same favorites
+ assertEquals(originalFavoritesIds,
+ model.favorites.map { it.controlStatus.control.controlId }.toSet())
+
+ verify(favoritesAdapter).notifyItemMoved(from, to)
+
+ verifyNoMoreInteractions(allAdapter, favoritesAdapter)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index f080d67..e8de10f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -75,6 +75,7 @@
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.phone.ShadeController;
import org.junit.Before;
import org.junit.Rule;
@@ -131,6 +132,8 @@
private ShortcutManager mShortcutManager;
@Mock
private NotificationGuts mNotificationGuts;
+ @Mock
+ private ShadeController mShadeController;
@Before
public void setUp() throws Exception {
@@ -139,12 +142,12 @@
mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
mDependency.injectTestDependency(BubbleController.class, mBubbleController);
+ mDependency.injectTestDependency(ShadeController.class, mShadeController);
// Inflate the layout
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
mNotificationInfo = (NotificationConversationInfo) layoutInflater.inflate(
R.layout.notification_conversation_info,
null);
- mNotificationInfo.mShowHomeScreen = true;
mNotificationInfo.setGutsParent(mNotificationGuts);
doAnswer((Answer<Object>) invocation -> {
mNotificationInfo.handleCloseControls(true, false);
@@ -173,7 +176,7 @@
when(mShortcutInfo.getShortLabel()).thenReturn("Convo name");
List<ShortcutInfo> shortcuts = Arrays.asList(mShortcutInfo);
when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts);
- mImage = mContext.getDrawable(R.drawable.ic_star);
+ mImage = mContext.getDrawable(R.drawable.ic_remove);
when(mLauncherApps.getShortcutBadgedIconDrawable(eq(mShortcutInfo),
anyInt())).thenReturn(mImage);
@@ -333,8 +336,6 @@
true);
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
assertEquals(GONE, nameView.getVisibility());
- final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
- assertEquals(GONE, dividerView.getVisibility());
}
@Test
@@ -364,8 +365,6 @@
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
assertEquals(VISIBLE, nameView.getVisibility());
assertTrue(nameView.getText().toString().contains("Proxied"));
- final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
- assertEquals(VISIBLE, dividerView.getVisibility());
}
@Test
@@ -502,6 +501,7 @@
verify(mShortcutManager, times(1)).requestPinShortcut(mShortcutInfo, null);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
+ verify(mShadeController).animateCollapsePanels();
}
@Test
@@ -644,9 +644,9 @@
true);
- Button fave = mNotificationInfo.findViewById(R.id.fave);
- assertEquals(mContext.getString(R.string.notification_conversation_favorite),
- fave.getText().toString());
+ ImageButton fave = mNotificationInfo.findViewById(R.id.fave);
+ assertEquals(mContext.getString(R.string.notification_conversation_unfavorite),
+ fave.getContentDescription().toString());
fave.performClick();
mTestableLooper.processAllMessages();
@@ -677,9 +677,9 @@
null,
true);
- Button fave = mNotificationInfo.findViewById(R.id.fave);
- assertEquals(mContext.getString(R.string.notification_conversation_unfavorite),
- fave.getText().toString());
+ ImageButton fave = mNotificationInfo.findViewById(R.id.fave);
+ assertEquals(mContext.getString(R.string.notification_conversation_favorite),
+ fave.getContentDescription().toString());
fave.performClick();
mTestableLooper.processAllMessages();
@@ -692,34 +692,6 @@
}
@Test
- public void testDemote() throws Exception {
- mNotificationInfo.bindNotification(
- mShortcutManager,
- mLauncherApps,
- mMockPackageManager,
- mMockINotificationManager,
- mVisualStabilityManager,
- TEST_PACKAGE_NAME,
- mNotificationChannel,
- mEntry,
- null,
- null,
- null,
- true);
-
-
- ImageButton demote = mNotificationInfo.findViewById(R.id.demote);
- demote.performClick();
- mTestableLooper.processAllMessages();
-
- ArgumentCaptor<NotificationChannel> captor =
- ArgumentCaptor.forClass(NotificationChannel.class);
- verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
- anyString(), anyInt(), captor.capture());
- assertTrue(captor.getValue().isDemoted());
- }
-
- @Test
public void testMute_mute() throws Exception {
mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
mConversationChannel.setImportance(IMPORTANCE_DEFAULT);
@@ -738,9 +710,9 @@
null,
true);
- Button mute = mNotificationInfo.findViewById(R.id.mute);
- assertEquals(mContext.getString(R.string.notification_conversation_mute),
- mute.getText().toString());
+ ImageButton mute = mNotificationInfo.findViewById(R.id.mute);
+ assertEquals(mContext.getString(R.string.notification_conversation_unmute),
+ mute.getContentDescription().toString());
mute.performClick();
mTestableLooper.processAllMessages();
@@ -774,9 +746,9 @@
true);
- Button mute = mNotificationInfo.findViewById(R.id.mute);
- assertEquals(mContext.getString(R.string.notification_conversation_unmute),
- mute.getText().toString());
+ ImageButton mute = mNotificationInfo.findViewById(R.id.mute);
+ assertEquals(mContext.getString(R.string.notification_conversation_mute),
+ mute.getContentDescription().toString());
mute.performClick();
mTestableLooper.processAllMessages();
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 54b4201..63dd99e 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -244,6 +244,10 @@
// Package: android
NOTE_SOFTAP_AUTO_DISABLED = 58;
+ // Notify the user that their admin has changed location settings.
+ // Package: android
+ NOTE_LOCATION_CHANGED = 59;
+
// ADD_NEW_IDS_ABOVE_THIS_LINE
// Legacy IDs with arbitrary values appear below
// Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index e3d2dcc..6247a63 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -1080,12 +1080,8 @@
}
}
- public Map<String, Set<String>> getExcludedRestoreKeys(String... packages) {
- return mBackupPreferences.getExcludedRestoreKeysForPackages(packages);
- }
-
- public Map<String, Set<String>> getAllExcludedRestoreKeys() {
- return mBackupPreferences.getAllExcludedRestoreKeys();
+ public Set<String> getExcludedRestoreKeys(String packageName) {
+ return mBackupPreferences.getExcludedRestoreKeysForPackage(packageName);
}
/** Used for generating random salts or passwords. */
@@ -3356,8 +3352,7 @@
restoreSet,
packageName,
token,
- listener,
- getExcludedRestoreKeys(packageName));
+ listener);
mBackupHandler.sendMessage(msg);
} catch (Exception e) {
// Calling into the transport broke; back off and proceed with the installation.
diff --git a/services/backup/java/com/android/server/backup/UserBackupPreferences.java b/services/backup/java/com/android/server/backup/UserBackupPreferences.java
index 41b9719..bb8bf52 100644
--- a/services/backup/java/com/android/server/backup/UserBackupPreferences.java
+++ b/services/backup/java/com/android/server/backup/UserBackupPreferences.java
@@ -48,16 +48,7 @@
mEditor.commit();
}
- Map<String, Set<String>> getExcludedRestoreKeysForPackages(String... packages) {
- Map<String, Set<String>> excludedKeys = new HashMap<>();
- for (String packageName : packages) {
- excludedKeys.put(packageName,
- mPreferences.getStringSet(packageName, Collections.emptySet()));
- }
- return excludedKeys;
- }
-
- Map<String, Set<String>> getAllExcludedRestoreKeys() {
- return (Map<String, Set<String>>) mPreferences.getAll();
+ Set<String> getExcludedRestoreKeysForPackage(String packageName) {
+ return mPreferences.getStringSet(packageName, Collections.emptySet());
}
}
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index 05396f3..87a8e49 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -299,8 +299,7 @@
params.pmToken,
params.isSystemRestore,
params.filterSet,
- params.listener,
- params.excludedKeys);
+ params.listener);
synchronized (backupManagerService.getPendingRestores()) {
if (backupManagerService.isRestoreInProgress()) {
diff --git a/services/backup/java/com/android/server/backup/params/RestoreParams.java b/services/backup/java/com/android/server/backup/params/RestoreParams.java
index 09b7e35..a6fea6c 100644
--- a/services/backup/java/com/android/server/backup/params/RestoreParams.java
+++ b/services/backup/java/com/android/server/backup/params/RestoreParams.java
@@ -37,7 +37,6 @@
public final boolean isSystemRestore;
@Nullable public final String[] filterSet;
public final OnTaskFinishedListener listener;
- public final Map<String, Set<String>> excludedKeys;
/**
* No kill after restore.
@@ -48,8 +47,7 @@
IBackupManagerMonitor monitor,
long token,
PackageInfo packageInfo,
- OnTaskFinishedListener listener,
- Map<String, Set<String>> excludedKeys) {
+ OnTaskFinishedListener listener) {
return new RestoreParams(
transportClient,
observer,
@@ -59,8 +57,7 @@
/* pmToken */ 0,
/* isSystemRestore */ false,
/* filterSet */ null,
- listener,
- excludedKeys);
+ listener);
}
/**
@@ -73,8 +70,7 @@
long token,
String packageName,
int pmToken,
- OnTaskFinishedListener listener,
- Map<String, Set<String>> excludedKeys) {
+ OnTaskFinishedListener listener) {
String[] filterSet = {packageName};
return new RestoreParams(
transportClient,
@@ -85,8 +81,7 @@
pmToken,
/* isSystemRestore */ false,
filterSet,
- listener,
- excludedKeys);
+ listener);
}
/**
@@ -97,8 +92,7 @@
IRestoreObserver observer,
IBackupManagerMonitor monitor,
long token,
- OnTaskFinishedListener listener,
- Map<String, Set<String>> excludedKeys) {
+ OnTaskFinishedListener listener) {
return new RestoreParams(
transportClient,
observer,
@@ -108,8 +102,7 @@
/* pmToken */ 0,
/* isSystemRestore */ true,
/* filterSet */ null,
- listener,
- excludedKeys);
+ listener);
}
/**
@@ -122,8 +115,7 @@
long token,
String[] filterSet,
boolean isSystemRestore,
- OnTaskFinishedListener listener,
- Map<String, Set<String>> excludedKeys) {
+ OnTaskFinishedListener listener) {
return new RestoreParams(
transportClient,
observer,
@@ -133,8 +125,7 @@
/* pmToken */ 0,
isSystemRestore,
filterSet,
- listener,
- excludedKeys);
+ listener);
}
private RestoreParams(
@@ -146,8 +137,7 @@
int pmToken,
boolean isSystemRestore,
@Nullable String[] filterSet,
- OnTaskFinishedListener listener,
- Map<String, Set<String>> excludedKeys) {
+ OnTaskFinishedListener listener) {
this.transportClient = transportClient;
this.observer = observer;
this.monitor = monitor;
@@ -157,6 +147,5 @@
this.isSystemRestore = isSystemRestore;
this.filterSet = filterSet;
this.listener = listener;
- this.excludedKeys = excludedKeys;
}
}
diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
index c0f76c3..5a57cdc 100644
--- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
+++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
@@ -178,8 +178,7 @@
observer,
monitor,
token,
- listener,
- mBackupManagerService.getAllExcludedRestoreKeys()),
+ listener),
"RestoreSession.restoreAll()");
} finally {
Binder.restoreCallingIdentity(oldId);
@@ -272,8 +271,7 @@
token,
packages,
/* isSystemRestore */ packages.length > 1,
- listener,
- mBackupManagerService.getExcludedRestoreKeys(packages)),
+ listener),
"RestoreSession.restorePackages(" + packages.length + " packages)");
} finally {
Binder.restoreCallingIdentity(oldId);
@@ -365,8 +363,7 @@
monitor,
token,
app,
- listener,
- mBackupManagerService.getExcludedRestoreKeys(app.packageName)),
+ listener),
"RestoreSession.restorePackage(" + packageName + ")");
} finally {
Binder.restoreCallingIdentity(oldId);
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index f90d936..3c37f73 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -155,8 +155,6 @@
// When finished call listener
private final OnTaskFinishedListener mListener;
- private final Map<String, Set<String>> mExcludedKeys;
-
// Key/value: bookkeeping about staged data and files for agent access
private File mBackupDataName;
private File mStageName;
@@ -168,14 +166,14 @@
private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
@VisibleForTesting
- PerformUnifiedRestoreTask(Map<String, Set<String>> excludedKeys) {
- mExcludedKeys = excludedKeys;
+ PerformUnifiedRestoreTask(UserBackupManagerService backupManagerService) {
mListener = null;
mAgentTimeoutParameters = null;
mTransportClient = null;
mTransportManager = null;
mEphemeralOpToken = 0;
mUserId = 0;
+ this.backupManagerService = backupManagerService;
}
// This task can assume that the wakelock is properly held for it and doesn't have to worry
@@ -190,8 +188,7 @@
int pmToken,
boolean isFullSystemRestore,
@Nullable String[] filterSet,
- OnTaskFinishedListener listener,
- Map<String, Set<String>> excludedKeys) {
+ OnTaskFinishedListener listener) {
this.backupManagerService = backupManagerService;
mUserId = backupManagerService.getUserId();
mTransportManager = backupManagerService.getTransportManager();
@@ -213,8 +210,6 @@
backupManagerService.getAgentTimeoutParameters(),
"Timeout parameters cannot be null");
- mExcludedKeys = excludedKeys;
-
if (targetPackage != null) {
// Single package restore
mAcceptSet = new ArrayList<>();
@@ -791,8 +786,9 @@
!getExcludedKeysForPackage(PLATFORM_PACKAGE_NAME).isEmpty();
}
- private Set<String> getExcludedKeysForPackage(String packageName) {
- return mExcludedKeys.getOrDefault(packageName, Collections.emptySet());
+ @VisibleForTesting
+ Set<String> getExcludedKeysForPackage(String packageName) {
+ return backupManagerService.getExcludedRestoreKeys(packageName);
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 63cddac..2c91a11 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -43,6 +43,7 @@
import android.location.GeocoderParams;
import android.location.Geofence;
import android.location.GnssMeasurementCorrections;
+import android.location.GnssRequest;
import android.location.IBatchedLocationCallback;
import android.location.IGnssMeasurementsListener;
import android.location.IGnssNavigationMessageListener;
@@ -2287,12 +2288,14 @@
}
@Override
- public boolean addGnssMeasurementsListener(IGnssMeasurementsListener listener,
- String packageName, String featureId, String listenerIdentifier) {
+ public boolean addGnssMeasurementsListener(@Nullable GnssRequest request,
+ IGnssMeasurementsListener listener,
+ String packageName, String featureId,
+ String listenerIdentifier) {
Objects.requireNonNull(listenerIdentifier);
return mGnssManagerService != null && mGnssManagerService.addGnssMeasurementsListener(
- listener, packageName, featureId, listenerIdentifier);
+ request, listener, packageName, featureId, listenerIdentifier);
}
@Override
@@ -2419,6 +2422,17 @@
}
@Override
+ public void setLocationEnabledForUser(boolean enabled, int userId) {
+ if (UserHandle.getCallingUserId() != userId) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS,
+ null);
+ }
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS,
+ "Requires WRITE_SECURE_SETTINGS permission");
+ mSettingsHelper.setLocationEnabled(enabled, userId);
+ }
+
+ @Override
public boolean isLocationEnabledForUser(int userId) {
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, false, "isLocationEnabledForUser", null);
diff --git a/services/core/java/com/android/server/LocationManagerServiceUtils.java b/services/core/java/com/android/server/LocationManagerServiceUtils.java
index 372e91e..ba1c81c 100644
--- a/services/core/java/com/android/server/LocationManagerServiceUtils.java
+++ b/services/core/java/com/android/server/LocationManagerServiceUtils.java
@@ -17,6 +17,7 @@
package com.android.server;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -37,22 +38,31 @@
/**
* Listener that can be linked to a binder.
* @param <TListener> listener type
+ * @param <TRequest> request type
*/
- public static class LinkedListener<TListener> extends
+ public static class LinkedListener<TRequest, TListener> extends
LinkedListenerBase {
+ @Nullable protected final TRequest mRequest;
private final TListener mListener;
private final Consumer<TListener> mBinderDeathCallback;
public LinkedListener(
+ @Nullable TRequest request,
@NonNull TListener listener,
String listenerName,
@NonNull CallerIdentity callerIdentity,
@NonNull Consumer<TListener> binderDeathCallback) {
super(callerIdentity, listenerName);
mListener = listener;
+ mRequest = request;
mBinderDeathCallback = binderDeathCallback;
}
+ @Nullable
+ public TRequest getRequest() {
+ return mRequest;
+ }
+
@Override
public void binderDied() {
if (D) Log.d(TAG, "Remote " + mListenerName + " died.");
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index b994e6c..f2d5a9b 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -302,15 +302,19 @@
private final ContentObserver mDarkThemeObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange, Uri uri) {
- int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE,
- mNightMode, 0);
- if (mode == MODE_NIGHT_AUTO || mode == MODE_NIGHT_CUSTOM) {
- mode = MODE_NIGHT_YES;
- }
- SystemProperties.set(SYSTEM_PROPERTY_DEVICE_THEME, Integer.toString(mode));
+ updateSystemProperties();
}
};
+ private void updateSystemProperties() {
+ int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE,
+ mNightMode, 0);
+ if (mode == MODE_NIGHT_AUTO || mode == MODE_NIGHT_CUSTOM) {
+ mode = MODE_NIGHT_YES;
+ }
+ SystemProperties.set(SYSTEM_PROPERTY_DEVICE_THEME, Integer.toString(mode));
+ }
+
@Override
public void onSwitchUser(int userHandle) {
super.onSwitchUser(userHandle);
@@ -392,6 +396,7 @@
context.getContentResolver().registerContentObserver(Secure.getUriFor(Secure.UI_NIGHT_MODE),
false, mDarkThemeObserver, 0);
+ updateSystemProperties();
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0686e3e..b6dc3de 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -466,9 +466,18 @@
// How long we wait for a launched process to attach to the activity manager
// before we decide it's never going to come up for real.
static final int PROC_START_TIMEOUT = 10*1000;
+ // How long we wait for an attached process to publish its content providers
+ // before we decide it must be hung.
+ static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000;
+
// How long we wait to kill an application zygote, after the last process using
// it has gone away.
static final int KILL_APP_ZYGOTE_DELAY_MS = 5 * 1000;
+ /**
+ * How long we wait for an provider to be published. Should be longer than
+ * {@link #CONTENT_PROVIDER_PUBLISH_TIMEOUT}.
+ */
+ static final int CONTENT_PROVIDER_WAIT_TIMEOUT = 20 * 1000;
// How long we wait for a launched process to attach to the activity manager
// before we decide it's never going to come up for real, when the process was
@@ -4925,8 +4934,7 @@
if (providers != null && checkAppInLaunchingProvidersLocked(app)) {
Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);
msg.obj = app;
- mHandler.sendMessageDelayed(msg,
- ContentResolver.CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS);
+ mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT);
}
checkTime(startTime, "attachApplicationLocked: before bindApplication");
@@ -7193,8 +7201,7 @@
}
// Wait for the provider to be published...
- final long timeout =
- SystemClock.uptimeMillis() + ContentResolver.CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS;
+ final long timeout = SystemClock.uptimeMillis() + CONTENT_PROVIDER_WAIT_TIMEOUT;
boolean timedOut = false;
synchronized (cpr) {
while (cpr.provider == null) {
@@ -7231,14 +7238,12 @@
}
}
if (timedOut) {
- // Note we do it after releasing the lock.
+ // Note we do it afer releasing the lock.
String callerName = "unknown";
- if (caller != null) {
- synchronized (this) {
- final ProcessRecord record = mProcessList.getLRURecordForAppLocked(caller);
- if (record != null) {
- callerName = record.processName;
- }
+ synchronized (this) {
+ final ProcessRecord record = mProcessList.getLRURecordForAppLocked(caller);
+ if (record != null) {
+ callerName = record.processName;
}
}
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 145f91b..789f719 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -798,7 +798,6 @@
boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
- final String packageName;
final int userId;
synchronized (mService) {
final ProcessRecord proc = data.proc;
@@ -807,7 +806,6 @@
Slog.e(TAG, "handleShowAppErrorUi: proc is null");
return;
}
- packageName = proc.info.packageName;
userId = proc.userId;
if (proc.getDialogController().hasCrashDialogs()) {
Slog.e(TAG, "App already has crash dialog: " + proc);
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index f3d8bc8..e63da9b 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1910,8 +1910,7 @@
mWaitDialog = null;
}
- void forAllDialogs(List<? extends BaseErrorDialog> dialogs,
- Consumer<BaseErrorDialog> c) {
+ void forAllDialogs(List<? extends BaseErrorDialog> dialogs, Consumer<BaseErrorDialog> c) {
for (int i = dialogs.size() - 1; i >= 0; i--) {
c.accept(dialogs.get(i));
}
@@ -1920,42 +1919,72 @@
void showCrashDialogs(AppErrorDialog.Data data) {
List<Context> contexts = getDisplayContexts(false /* lastUsedOnly */);
mCrashDialogs = new ArrayList<>();
-
for (int i = contexts.size() - 1; i >= 0; i--) {
final Context c = contexts.get(i);
mCrashDialogs.add(new AppErrorDialog(c, mService, data));
}
- mService.mUiHandler.post(() -> mCrashDialogs.forEach(Dialog::show));
+ mService.mUiHandler.post(() -> {
+ List<AppErrorDialog> dialogs;
+ synchronized (mService) {
+ dialogs = mCrashDialogs;
+ }
+ if (dialogs != null) {
+ forAllDialogs(dialogs, Dialog::show);
+ }
+ });
}
void showAnrDialogs(AppNotRespondingDialog.Data data) {
List<Context> contexts = getDisplayContexts(isSilentAnr() /* lastUsedOnly */);
mAnrDialogs = new ArrayList<>();
-
for (int i = contexts.size() - 1; i >= 0; i--) {
final Context c = contexts.get(i);
mAnrDialogs.add(new AppNotRespondingDialog(mService, c, data));
}
- mService.mUiHandler.post(() -> mAnrDialogs.forEach(Dialog::show));
+ mService.mUiHandler.post(() -> {
+ List<AppNotRespondingDialog> dialogs;
+ synchronized (mService) {
+ dialogs = mAnrDialogs;
+ }
+ if (dialogs != null) {
+ forAllDialogs(dialogs, Dialog::show);
+ }
+ });
}
void showViolationDialogs(AppErrorResult res) {
List<Context> contexts = getDisplayContexts(false /* lastUsedOnly */);
mViolationDialogs = new ArrayList<>();
-
for (int i = contexts.size() - 1; i >= 0; i--) {
final Context c = contexts.get(i);
mViolationDialogs.add(
new StrictModeViolationDialog(c, mService, res, ProcessRecord.this));
}
- mService.mUiHandler.post(() -> mViolationDialogs.forEach(Dialog::show));
+ mService.mUiHandler.post(() -> {
+ List<StrictModeViolationDialog> dialogs;
+ synchronized (mService) {
+ dialogs = mViolationDialogs;
+ }
+ if (dialogs != null) {
+ forAllDialogs(dialogs, Dialog::show);
+ }
+ });
}
void showDebugWaitingDialogs() {
List<Context> contexts = getDisplayContexts(true /* lastUsedOnly */);
final Context c = contexts.get(0);
mWaitDialog = new AppWaitingForDebuggerDialog(mService, c, ProcessRecord.this);
- mService.mUiHandler.post(() -> mWaitDialog.show());
+
+ mService.mUiHandler.post(() -> {
+ Dialog dialog;
+ synchronized (mService) {
+ dialog = mWaitDialog;
+ }
+ if (dialog != null) {
+ dialog.show();
+ }
+ });
}
/**
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index c75ee04..b2d0441 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -414,6 +414,7 @@
Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null);
intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
+ | Intent.FLAG_RECEIVER_OFFLOAD
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mInjector.broadcastIntent(intent, null, resultTo, 0, null, null,
new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 07fc9b7..ed11740 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -25,6 +25,7 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import static android.hardware.biometrics.BiometricManager.Authenticators;
+import android.annotation.IntDef;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.UserSwitchObserver;
@@ -83,6 +84,25 @@
static final String TAG = "BiometricService";
private static final boolean DEBUG = true;
+ private static final int BIOMETRIC_NO_HARDWARE = 0;
+ private static final int BIOMETRIC_OK = 1;
+ private static final int BIOMETRIC_DISABLED_BY_DEVICE_POLICY = 2;
+ private static final int BIOMETRIC_INSUFFICIENT_STRENGTH = 3;
+ private static final int BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE = 4;
+ private static final int BIOMETRIC_HARDWARE_NOT_DETECTED = 5;
+ private static final int BIOMETRIC_NOT_ENROLLED = 6;
+ private static final int BIOMETRIC_NOT_ENABLED_FOR_APPS = 7;
+
+ @IntDef({BIOMETRIC_NO_HARDWARE,
+ BIOMETRIC_OK,
+ BIOMETRIC_DISABLED_BY_DEVICE_POLICY,
+ BIOMETRIC_INSUFFICIENT_STRENGTH,
+ BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE,
+ BIOMETRIC_HARDWARE_NOT_DETECTED,
+ BIOMETRIC_NOT_ENROLLED,
+ BIOMETRIC_NOT_ENABLED_FOR_APPS})
+ @interface BiometricStatus {}
+
private static final int MSG_ON_AUTHENTICATION_SUCCEEDED = 2;
private static final int MSG_ON_AUTHENTICATION_REJECTED = 3;
private static final int MSG_ON_ERROR = 4;
@@ -206,7 +226,7 @@
}
boolean isAllowDeviceCredential() {
- return Utils.isDeviceCredentialAllowed(mBundle);
+ return Utils.isCredentialRequested(mBundle);
}
}
@@ -372,16 +392,20 @@
* strength.
* @return a bitfield, see {@link Authenticators}
*/
- public int getActualStrength() {
+ int getActualStrength() {
return OEMStrength | updatedStrength;
}
+ boolean isDowngraded() {
+ return OEMStrength != updatedStrength;
+ }
+
/**
* Stores the updated strength, which takes effect whenever {@link #getActualStrength()}
* is checked.
* @param newStrength
*/
- public void updateStrength(int newStrength) {
+ void updateStrength(int newStrength) {
String log = "updateStrength: Before(" + toString() + ")";
updatedStrength = newStrength;
log += " After(" + toString() + ")";
@@ -1007,6 +1031,79 @@
return isBiometricDisabled;
}
+ private static int biometricStatusToBiometricConstant(@BiometricStatus int status) {
+ switch (status) {
+ case BIOMETRIC_NO_HARDWARE:
+ return BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT;
+ case BIOMETRIC_OK:
+ return BiometricConstants.BIOMETRIC_SUCCESS;
+ case BIOMETRIC_DISABLED_BY_DEVICE_POLICY:
+ return BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
+ case BIOMETRIC_INSUFFICIENT_STRENGTH:
+ return BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT;
+ case BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE:
+ return BiometricConstants.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED;
+ case BIOMETRIC_HARDWARE_NOT_DETECTED:
+ return BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
+ case BIOMETRIC_NOT_ENROLLED:
+ return BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS;
+ case BIOMETRIC_NOT_ENABLED_FOR_APPS:
+ return BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
+ default:
+ return BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE;
+ }
+ }
+
+ /**
+ * Returns the status of the authenticator, with errors returned in a specific priority order.
+ * For example, {@link #BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE} is only returned
+ * if it has enrollments, and is enabled for apps.
+ *
+ * We should only return the modality if the authenticator should be exposed. e.g.
+ * BIOMETRIC_NOT_ENROLLED_FOR_APPS should not expose the authenticator's type.
+ *
+ * @return A Pair with `first` being modality, and `second` being @BiometricStatus
+ */
+ private Pair<Integer, Integer> getStatusForBiometricAuthenticator(
+ AuthenticatorWrapper authenticator, int userId, String opPackageName,
+ boolean checkDevicePolicyManager, int requestedStrength) {
+ if (checkDevicePolicyManager) {
+ if (isBiometricDisabledByDevicePolicy(authenticator.modality, userId)) {
+ return new Pair<>(TYPE_NONE, BIOMETRIC_DISABLED_BY_DEVICE_POLICY);
+ }
+ }
+
+ final boolean wasStrongEnough =
+ Utils.isAtLeastStrength(authenticator.OEMStrength, requestedStrength);
+ final boolean isStrongEnough =
+ Utils.isAtLeastStrength(authenticator.getActualStrength(), requestedStrength);
+
+ if (wasStrongEnough && !isStrongEnough) {
+ return new Pair<>(authenticator.modality,
+ BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE);
+ } else if (!wasStrongEnough) {
+ return new Pair<>(TYPE_NONE, BIOMETRIC_INSUFFICIENT_STRENGTH);
+ }
+
+ try {
+ if (!authenticator.impl.isHardwareDetected(opPackageName)) {
+ return new Pair<>(authenticator.modality, BIOMETRIC_HARDWARE_NOT_DETECTED);
+ }
+
+ if (!authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) {
+ return new Pair<>(authenticator.modality, BIOMETRIC_NOT_ENROLLED);
+ }
+ } catch (RemoteException e) {
+ return new Pair<>(authenticator.modality, BIOMETRIC_HARDWARE_NOT_DETECTED);
+ }
+
+ if (!isEnabledForApp(authenticator.modality, userId)) {
+ return new Pair<>(TYPE_NONE, BIOMETRIC_NOT_ENABLED_FOR_APPS);
+ }
+
+ return new Pair<>(authenticator.modality, BIOMETRIC_OK);
+ }
+
/**
* Depending on the requested authentication (credential/biometric combination), checks their
* availability.
@@ -1029,10 +1126,9 @@
private Pair<Integer, Integer> checkAndGetAuthenticators(int userId, Bundle bundle,
String opPackageName, boolean checkDevicePolicyManager) throws RemoteException {
- final boolean biometricRequested = Utils.isBiometricAllowed(bundle);
- final boolean credentialRequested = Utils.isDeviceCredentialAllowed(bundle);
+ final boolean biometricRequested = Utils.isBiometricRequested(bundle);
+ final boolean credentialRequested = Utils.isCredentialRequested(bundle);
- final boolean biometricOk;
final boolean credentialOk = mTrustManager.isDeviceSecure(userId);
// Assuming that biometric authenticators are listed in priority-order, the rest of this
@@ -1041,96 +1137,56 @@
// the correct error. Error strings that are modality-specific should also respect the
// priority-order.
- // Find first biometric authenticator that's strong enough, detected, enrolled, and enabled.
- boolean disabledByDevicePolicy = false;
- boolean hasSufficientStrength = false;
- boolean isHardwareDetected = false;
- boolean hasTemplatesEnrolled = false;
- boolean enabledForApps = false;
+ int firstBiometricModality = TYPE_NONE;
+ @BiometricStatus int firstBiometricStatus = BIOMETRIC_NO_HARDWARE;
- int modality = TYPE_NONE;
- int firstHwAvailable = TYPE_NONE;
+ int biometricModality = TYPE_NONE;
+ @BiometricStatus int biometricStatus = BIOMETRIC_NO_HARDWARE;
+
for (AuthenticatorWrapper authenticator : mAuthenticators) {
- final int actualStrength = authenticator.getActualStrength();
final int requestedStrength = Utils.getPublicBiometricStrength(bundle);
+ Pair<Integer, Integer> result = getStatusForBiometricAuthenticator(
+ authenticator, userId, opPackageName, checkDevicePolicyManager,
+ requestedStrength);
- if (isBiometricDisabledByDevicePolicy(authenticator.modality, userId)) {
- disabledByDevicePolicy = true;
- continue;
- }
- disabledByDevicePolicy = false;
+ biometricStatus = result.second;
- if (!Utils.isAtLeastStrength(actualStrength, requestedStrength)) {
- continue;
- }
- hasSufficientStrength = true;
+ Slog.d(TAG, "Authenticator ID: " + authenticator.id
+ + " Modality: " + authenticator.modality
+ + " ReportedModality: " + result.first
+ + " Status: " + biometricStatus);
- if (!authenticator.impl.isHardwareDetected(opPackageName)) {
- continue;
- }
- isHardwareDetected = true;
-
- if (firstHwAvailable == TYPE_NONE) {
- // Store the first one since we want to return the error in correct
- // priority order.
- firstHwAvailable = authenticator.modality;
+ if (firstBiometricModality == TYPE_NONE) {
+ firstBiometricModality = result.first;
+ firstBiometricStatus = biometricStatus;
}
- if (!authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) {
- continue;
+ if (biometricStatus == BIOMETRIC_OK) {
+ biometricModality = result.first;
+ break;
}
- hasTemplatesEnrolled = true;
-
- if (!isEnabledForApp(authenticator.modality, userId)) {
- continue;
- }
- enabledForApps = true;
- modality = authenticator.modality;
- break;
}
- biometricOk = !disabledByDevicePolicy
- && hasSufficientStrength && isHardwareDetected
- && hasTemplatesEnrolled && enabledForApps;
-
- Slog.d(TAG, "checkAndGetAuthenticators: user=" + userId
- + " checkDevicePolicyManager=" + checkDevicePolicyManager
- + " isHardwareDetected=" + isHardwareDetected
- + " hasTemplatesEnrolled=" + hasTemplatesEnrolled
- + " enabledForApps=" + enabledForApps
- + " disabledByDevicePolicy=" + disabledByDevicePolicy);
-
if (biometricRequested && credentialRequested) {
- if (credentialOk || biometricOk) {
- if (!biometricOk) {
+ if (credentialOk || biometricStatus == BIOMETRIC_OK) {
+ if (biometricStatus != BIOMETRIC_OK) {
// If there's a problem with biometrics but device credential is
// allowed, only show credential UI.
bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED,
Authenticators.DEVICE_CREDENTIAL);
}
- return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS);
+ return new Pair<>(biometricModality, BiometricConstants.BIOMETRIC_SUCCESS);
} else {
- return new Pair<>(firstHwAvailable,
+ return new Pair<>(firstBiometricModality,
BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS);
}
} else if (biometricRequested) {
- if (biometricOk) {
- return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS);
- } else if (disabledByDevicePolicy) {
- return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
- } else if (!hasSufficientStrength) {
- return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT);
- } else if (!isHardwareDetected) {
- return new Pair<>(firstHwAvailable,
- BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
- } else if (!hasTemplatesEnrolled) {
- return new Pair<>(firstHwAvailable,
- BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS);
- } else if (!enabledForApps) {
- return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
+ if (biometricStatus == BIOMETRIC_OK) {
+ return new Pair<>(biometricModality,
+ biometricStatusToBiometricConstant(biometricStatus));
} else {
- Slog.e(TAG, "Unexpected case");
- return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE);
+ return new Pair<>(firstBiometricModality,
+ biometricStatusToBiometricConstant(firstBiometricStatus));
}
} else if (credentialRequested) {
if (credentialOk) {
diff --git a/services/core/java/com/android/server/biometrics/LoggableMonitor.java b/services/core/java/com/android/server/biometrics/LoggableMonitor.java
index c03c77f..c73b26e 100644
--- a/services/core/java/com/android/server/biometrics/LoggableMonitor.java
+++ b/services/core/java/com/android/server/biometrics/LoggableMonitor.java
@@ -124,7 +124,7 @@
error,
vendorCode,
Utils.isDebugEnabled(context, targetUserId),
- latency);
+ sanitizeLatency(latency));
}
protected final void logOnAuthenticated(Context context, boolean authenticated,
@@ -165,7 +165,7 @@
statsClient(),
requireConfirmation,
authState,
- latency,
+ sanitizeLatency(latency),
Utils.isDebugEnabled(context, targetUserId));
}
@@ -183,8 +183,16 @@
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ENROLLED,
statsModality(),
targetUserId,
- latency,
+ sanitizeLatency(latency),
enrollSuccessful);
}
+ private long sanitizeLatency(long latency) {
+ if (latency < 0) {
+ Slog.w(TAG, "found a negative latency : " + latency);
+ return -1;
+ }
+ return latency;
+ }
+
}
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 2d4ab63..8f3fd36 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -80,7 +80,7 @@
* @param authenticators composed of one or more values from {@link Authenticators}
* @return true if device credential is allowed.
*/
- public static boolean isDeviceCredentialAllowed(@Authenticators.Types int authenticators) {
+ public static boolean isCredentialRequested(@Authenticators.Types int authenticators) {
return (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0;
}
@@ -88,8 +88,8 @@
* @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)}
* @return true if device credential is allowed.
*/
- public static boolean isDeviceCredentialAllowed(Bundle bundle) {
- return isDeviceCredentialAllowed(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
+ public static boolean isCredentialRequested(Bundle bundle) {
+ return isCredentialRequested(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED));
}
/**
@@ -120,7 +120,7 @@
* @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)}
* @return true if biometric authentication is allowed.
*/
- public static boolean isBiometricAllowed(Bundle bundle) {
+ public static boolean isBiometricRequested(Bundle bundle) {
return getPublicBiometricStrength(bundle) != 0;
}
@@ -169,7 +169,7 @@
// should be set.
final int biometricBits = authenticators & Authenticators.BIOMETRIC_MIN_STRENGTH;
if (biometricBits == Authenticators.EMPTY_SET
- && isDeviceCredentialAllowed(authenticators)) {
+ && isCredentialRequested(authenticators)) {
return true;
} else if (biometricBits == Authenticators.BIOMETRIC_STRONG) {
return true;
@@ -209,6 +209,9 @@
case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT:
biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE;
break;
+ case BiometricConstants.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED:
+ biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED;
+ break;
default:
Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode);
biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
diff --git a/services/core/java/com/android/server/compat/TEST_MAPPING b/services/core/java/com/android/server/compat/TEST_MAPPING
new file mode 100644
index 0000000..0c30c79
--- /dev/null
+++ b/services/core/java/com/android/server/compat/TEST_MAPPING
@@ -0,0 +1,21 @@
+{
+ "presubmit": [
+ // Unit tests
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.compat"
+ }
+ ]
+ },
+ // Tests for the TestRule
+ {
+ "name": "PlatformCompatGating"
+ },
+ // CTS tests
+ {
+ "name": "CtsAppCompatHostTestCases#"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
index b0e2e64..6a5e963 100644
--- a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
+++ b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
@@ -16,9 +16,7 @@
package com.android.server.incremental;
-import static android.content.pm.InstallationFile.FILE_TYPE_OBB;
import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
-import static android.content.pm.PackageInstaller.LOCATION_MEDIA_OBB;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -183,10 +181,8 @@
session = packageInstaller.openSession(sessionId);
for (int i = 0; i < numFiles; i++) {
InstallationFile file = installationFiles.get(i);
- final int location = file.getFileType() == FILE_TYPE_OBB ? LOCATION_MEDIA_OBB
- : LOCATION_DATA_APP;
- session.addFile(location, file.getName(), file.getSize(), file.getMetadata(),
- null);
+ session.addFile(file.getLocation(), file.getName(), file.getLengthBytes(),
+ file.getMetadata(), file.getSignature());
}
session.commit(localReceiver.getIntentSender());
final Intent result = localReceiver.getResult();
@@ -304,7 +300,8 @@
}
final byte[] metadata = String.valueOf(index).getBytes(
StandardCharsets.UTF_8);
- fileList.add(new InstallationFile(name, size, metadata));
+ fileList.add(
+ new InstallationFile(LOCATION_DATA_APP, name, size, metadata, null));
break;
}
default:
diff --git a/services/core/java/com/android/server/location/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/GnssMeasurementsProvider.java
index 55e427f..6ba5f07 100644
--- a/services/core/java/com/android/server/location/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/GnssMeasurementsProvider.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.location.GnssMeasurementsEvent;
+import android.location.GnssRequest;
import android.location.IGnssMeasurementsListener;
import android.os.Handler;
import android.os.RemoteException;
@@ -33,14 +34,14 @@
* @hide
*/
public abstract class GnssMeasurementsProvider
- extends RemoteListenerHelper<IGnssMeasurementsListener> {
- private static final String TAG = "GnssMeasurementsProvider";
+ extends RemoteListenerHelper<GnssRequest, IGnssMeasurementsListener> {
+ private static final String TAG = "GnssMeasProvider";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final GnssMeasurementProviderNative mNative;
- private boolean mIsCollectionStarted;
- private boolean mEnableFullTracking;
+ private boolean mStartedCollection;
+ private boolean mStartedFullTracking;
protected GnssMeasurementsProvider(Context context, Handler handler) {
this(context, handler, new GnssMeasurementProviderNative());
@@ -57,8 +58,8 @@
if (DEBUG) {
Log.d(TAG, "resumeIfStarted");
}
- if (mIsCollectionStarted) {
- mNative.startMeasurementCollection(mEnableFullTracking);
+ if (mStartedCollection) {
+ mNative.startMeasurementCollection(mStartedFullTracking);
}
}
@@ -67,18 +68,35 @@
return mNative.isMeasurementSupported();
}
- @Override
- protected int registerWithService() {
+ private boolean getMergedFullTracking() {
int devOptions = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0);
- int fullTrackingToggled = Settings.Global.getInt(mContext.getContentResolver(),
+ int enableFullTracking = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING, 0);
- boolean enableFullTracking = (devOptions == 1 /* Developer Mode enabled */)
- && (fullTrackingToggled == 1 /* Raw Measurements Full Tracking enabled */);
+ boolean enableFullTrackingBySetting = (devOptions == 1 /* Developer Mode enabled */)
+ && (enableFullTracking == 1 /* Raw Measurements Full Tracking enabled */);
+ if (enableFullTrackingBySetting) {
+ return true;
+ }
+
+ synchronized (mListenerMap) {
+ for (IdentifiedListener identifiedListener : mListenerMap.values()) {
+ GnssRequest request = identifiedListener.getRequest();
+ if (request != null && request.isFullTracking()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected int registerWithService() {
+ boolean enableFullTracking = getMergedFullTracking();
boolean result = mNative.startMeasurementCollection(enableFullTracking);
if (result) {
- mIsCollectionStarted = true;
- mEnableFullTracking = enableFullTracking;
+ mStartedCollection = true;
+ mStartedFullTracking = enableFullTracking;
return RemoteListenerHelper.RESULT_SUCCESS;
} else {
return RemoteListenerHelper.RESULT_INTERNAL_ERROR;
@@ -89,7 +107,7 @@
protected void unregisterFromService() {
boolean stopped = mNative.stopMeasurementCollection();
if (stopped) {
- mIsCollectionStarted = false;
+ mStartedCollection = false;
}
}
diff --git a/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java b/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java
index 983d1da..fb901e8 100644
--- a/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java
+++ b/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java
@@ -33,7 +33,7 @@
* @hide
*/
public abstract class GnssNavigationMessageProvider
- extends RemoteListenerHelper<IGnssNavigationMessageListener> {
+ extends RemoteListenerHelper<Void, IGnssNavigationMessageListener> {
private static final String TAG = "GnssNavigationMessageProvider";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
diff --git a/services/core/java/com/android/server/location/GnssStatusListenerHelper.java b/services/core/java/com/android/server/location/GnssStatusListenerHelper.java
index eaf63c8..1d16c03 100644
--- a/services/core/java/com/android/server/location/GnssStatusListenerHelper.java
+++ b/services/core/java/com/android/server/location/GnssStatusListenerHelper.java
@@ -24,7 +24,8 @@
/**
* Implementation of a handler for {@link IGnssStatusListener}.
*/
-public abstract class GnssStatusListenerHelper extends RemoteListenerHelper<IGnssStatusListener> {
+public abstract class GnssStatusListenerHelper extends
+ RemoteListenerHelper<Void, IGnssStatusListener> {
private static final String TAG = "GnssStatusListenerHelper";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
diff --git a/services/core/java/com/android/server/location/RemoteListenerHelper.java b/services/core/java/com/android/server/location/RemoteListenerHelper.java
index 01522739..11f0685 100644
--- a/services/core/java/com/android/server/location/RemoteListenerHelper.java
+++ b/services/core/java/com/android/server/location/RemoteListenerHelper.java
@@ -17,6 +17,7 @@
package com.android.server.location;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
import android.os.Handler;
@@ -32,9 +33,10 @@
/**
* A helper class that handles operations in remote listeners.
*
+ * @param <TRequest> the type of request.
* @param <TListener> the type of GNSS data listener.
*/
-public abstract class RemoteListenerHelper<TListener extends IInterface> {
+public abstract class RemoteListenerHelper<TRequest, TListener extends IInterface> {
protected static final int RESULT_SUCCESS = 0;
protected static final int RESULT_NOT_AVAILABLE = 1;
@@ -47,7 +49,7 @@
protected final Handler mHandler;
private final String mTag;
- private final Map<IBinder, IdentifiedListener> mListenerMap = new HashMap<>();
+ protected final Map<IBinder, IdentifiedListener> mListenerMap = new HashMap<>();
protected final Context mContext;
protected final AppOpsManager mAppOps;
@@ -75,7 +77,8 @@
/**
* Adds GNSS data listener {@code listener} with caller identify {@code callerIdentify}.
*/
- public void addListener(@NonNull TListener listener, CallerIdentity callerIdentity) {
+ public void addListener(@Nullable TRequest request, @NonNull TListener listener,
+ CallerIdentity callerIdentity) {
Objects.requireNonNull(listener, "Attempted to register a 'null' listener.");
IBinder binder = listener.asBinder();
synchronized (mListenerMap) {
@@ -84,7 +87,7 @@
return;
}
- IdentifiedListener identifiedListener = new IdentifiedListener(listener,
+ IdentifiedListener identifiedListener = new IdentifiedListener(request, listener,
callerIdentity);
mListenerMap.put(binder, identifiedListener);
@@ -257,14 +260,22 @@
return RESULT_SUCCESS;
}
- private class IdentifiedListener {
+ protected class IdentifiedListener {
+ @Nullable private final TRequest mRequest;
private final TListener mListener;
private final CallerIdentity mCallerIdentity;
- private IdentifiedListener(@NonNull TListener listener, CallerIdentity callerIdentity) {
+ private IdentifiedListener(@Nullable TRequest request, @NonNull TListener listener,
+ CallerIdentity callerIdentity) {
mListener = listener;
+ mRequest = request;
mCallerIdentity = callerIdentity;
}
+
+ @Nullable
+ protected TRequest getRequest() {
+ return mRequest;
+ }
}
private class HandlerRunnable implements Runnable {
diff --git a/services/core/java/com/android/server/location/SettingsHelper.java b/services/core/java/com/android/server/location/SettingsHelper.java
index 9163490..6d1d1f9 100644
--- a/services/core/java/com/android/server/location/SettingsHelper.java
+++ b/services/core/java/com/android/server/location/SettingsHelper.java
@@ -135,6 +135,24 @@
}
/**
+ * Set location enabled for a user.
+ */
+ public void setLocationEnabled(boolean enabled, int userId) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ Settings.Secure.putIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_MODE,
+ enabled
+ ? Settings.Secure.LOCATION_MODE_ON
+ : Settings.Secure.LOCATION_MODE_OFF,
+ userId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
* Add a listener for changes to the location enabled setting. Callbacks occur on an unspecified
* thread.
*/
diff --git a/services/core/java/com/android/server/location/TEST_MAPPING b/services/core/java/com/android/server/location/TEST_MAPPING
index 2e21fa6..214d2f3 100644
--- a/services/core/java/com/android/server/location/TEST_MAPPING
+++ b/services/core/java/com/android/server/location/TEST_MAPPING
@@ -1,10 +1,19 @@
{
"presubmit": [
{
+ "name": "CtsLocationFineTestCases"
+ },
+ {
"name": "CtsLocationCoarseTestCases"
},
{
"name": "CtsLocationNoneTestCases"
+ },
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [{
+ "include-filter": "com.android.server.location"
+ }]
}
]
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
index 2bab9fa..02c1bdd 100644
--- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java
+++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
@@ -25,6 +25,7 @@
import android.content.Context;
import android.location.GnssCapabilities;
import android.location.GnssMeasurementCorrections;
+import android.location.GnssRequest;
import android.location.IBatchedLocationCallback;
import android.location.IGnssMeasurementsListener;
import android.location.IGnssNavigationMessageListener;
@@ -96,15 +97,15 @@
private final IGpsGeofenceHardware mGpsGeofenceProxy;
@GuardedBy("mGnssMeasurementsListeners")
- private final ArrayMap<IBinder, LinkedListener<IGnssMeasurementsListener>>
+ private final ArrayMap<IBinder, LinkedListener<GnssRequest, IGnssMeasurementsListener>>
mGnssMeasurementsListeners = new ArrayMap<>();
@GuardedBy("mGnssNavigationMessageListeners")
- private final ArrayMap<IBinder, LinkedListener<IGnssNavigationMessageListener>>
+ private final ArrayMap<IBinder, LinkedListener<Void, IGnssNavigationMessageListener>>
mGnssNavigationMessageListeners = new ArrayMap<>();
@GuardedBy("mGnssStatusListeners")
- private final ArrayMap<IBinder, LinkedListener<IGnssStatusListener>>
+ private final ArrayMap<IBinder, LinkedListener<Void, IGnssStatusListener>>
mGnssStatusListeners = new ArrayMap<>();
@GuardedBy("this")
@@ -118,7 +119,8 @@
@Nullable private IBatchedLocationCallback mGnssBatchingCallback;
@GuardedBy("mGnssBatchingLock")
- @Nullable private LinkedListener<IBatchedLocationCallback> mGnssBatchingDeathCallback;
+ @Nullable
+ private LinkedListener<Void, IBatchedLocationCallback> mGnssBatchingDeathCallback;
@GuardedBy("mGnssBatchingLock")
private boolean mGnssBatchingInProgress = false;
@@ -272,6 +274,7 @@
mGnssBatchingCallback = callback;
mGnssBatchingDeathCallback =
new LinkedListener<>(
+ /* request= */ null,
callback,
"BatchedLocationCallback",
callerIdentity,
@@ -356,36 +359,39 @@
}
}
- private <TListener extends IInterface> void updateListenersOnForegroundChangedLocked(
- Map<IBinder, ? extends LinkedListenerBase> gnssDataListeners,
- RemoteListenerHelper<TListener> gnssDataProvider,
+ private <TRequest, TListener extends IInterface> void updateListenersOnForegroundChangedLocked(
+ Map<IBinder, LinkedListener<TRequest, TListener>> gnssDataListeners,
+ RemoteListenerHelper<TRequest, TListener> gnssDataProvider,
Function<IBinder, TListener> mapBinderToListener,
int uid,
boolean foreground) {
- for (Map.Entry<IBinder, ? extends LinkedListenerBase> entry :
+ for (Map.Entry<IBinder, LinkedListener<TRequest, TListener>> entry :
gnssDataListeners.entrySet()) {
- LinkedListenerBase linkedListener = entry.getValue();
+ LinkedListener<TRequest, TListener> linkedListener = entry.getValue();
CallerIdentity callerIdentity = linkedListener.getCallerIdentity();
+ TRequest request = linkedListener.getRequest();
if (callerIdentity.mUid != uid) {
continue;
}
TListener listener = mapBinderToListener.apply(entry.getKey());
if (foreground || isThrottlingExempt(callerIdentity)) {
- gnssDataProvider.addListener(listener, callerIdentity);
+ gnssDataProvider.addListener(request, listener, callerIdentity);
} else {
gnssDataProvider.removeListener(listener);
}
}
}
- private <TListener extends IInterface> boolean addGnssDataListenerLocked(
+ private <TListener extends IInterface, TRequest> boolean addGnssDataListenerLocked(
+ @Nullable TRequest request,
TListener listener,
String packageName,
@Nullable String featureId,
@NonNull String listenerIdentifier,
- RemoteListenerHelper<TListener> gnssDataProvider,
- ArrayMap<IBinder, LinkedListener<TListener>> gnssDataListeners,
+ RemoteListenerHelper<TRequest, TListener> gnssDataProvider,
+ ArrayMap<IBinder,
+ LinkedListener<TRequest, TListener>> gnssDataListeners,
Consumer<TListener> binderDeathCallback) {
mContext.enforceCallingPermission(Manifest.permission.ACCESS_FINE_LOCATION, null);
@@ -395,7 +401,7 @@
CallerIdentity callerIdentity = new CallerIdentity(Binder.getCallingUid(),
Binder.getCallingPid(), packageName, featureId, listenerIdentifier);
- LinkedListener<TListener> linkedListener = new LinkedListener<>(listener,
+ LinkedListener<TRequest, TListener> linkedListener = new LinkedListener<>(request, listener,
listenerIdentifier, callerIdentity, binderDeathCallback);
IBinder binder = listener.asBinder();
if (!linkedListener.linkToListenerDeathNotificationLocked(binder)) {
@@ -419,15 +425,15 @@
}
if (mAppForegroundHelper.isAppForeground(callerIdentity.mUid)
|| isThrottlingExempt(callerIdentity)) {
- gnssDataProvider.addListener(listener, callerIdentity);
+ gnssDataProvider.addListener(request, listener, callerIdentity);
}
return true;
}
- private <TListener extends IInterface> void removeGnssDataListenerLocked(
+ private <TRequest, TListener extends IInterface> void removeGnssDataListenerLocked(
TListener listener,
- RemoteListenerHelper<TListener> gnssDataProvider,
- ArrayMap<IBinder, LinkedListener<TListener>> gnssDataListeners) {
+ RemoteListenerHelper<TRequest, TListener> gnssDataProvider,
+ ArrayMap<IBinder, LinkedListener<TRequest, TListener>> gnssDataListeners) {
if (gnssDataProvider == null) {
Log.e(
TAG,
@@ -437,7 +443,7 @@
}
IBinder binder = listener.asBinder();
- LinkedListener<TListener> linkedListener =
+ LinkedListener<TRequest, TListener> linkedListener =
gnssDataListeners.remove(binder);
if (linkedListener == null) {
return;
@@ -467,6 +473,7 @@
@Nullable String featureId) {
synchronized (mGnssStatusListeners) {
return addGnssDataListenerLocked(
+ /* request= */ null,
listener,
packageName,
featureId,
@@ -489,11 +496,17 @@
/**
* Adds a GNSS measurements listener.
*/
- public boolean addGnssMeasurementsListener(
- IGnssMeasurementsListener listener, String packageName, @Nullable String featureId,
+ public boolean addGnssMeasurementsListener(@Nullable GnssRequest request,
+ IGnssMeasurementsListener listener, String packageName,
+ @Nullable String featureId,
@NonNull String listenerIdentifier) {
+ if (request != null && request.isFullTracking()) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.LOCATION_HARDWARE,
+ null);
+ }
synchronized (mGnssMeasurementsListeners) {
return addGnssDataListenerLocked(
+ request,
listener,
packageName,
featureId,
@@ -538,6 +551,7 @@
@Nullable String featureId, @NonNull String listenerIdentifier) {
synchronized (mGnssNavigationMessageListeners) {
return addGnssDataListenerLocked(
+ /* request= */ null,
listener,
packageName,
featureId,
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 0cd1c25..e9f84fd 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import android.Manifest.permission;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -305,6 +306,10 @@
final int callingUserId = injectCallingUserId();
if (targetUserId == callingUserId) return true;
+ if (mContext.checkCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS_FULL)
+ == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
long ident = injectClearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 944280d..97defcd 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -214,7 +214,7 @@
private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
private static final int[] EMPTY_CHILD_SESSION_ARRAY = EmptyArray.INT;
- private static final FileInfo[] EMPTY_FILE_INFO_ARRAY = {};
+ private static final InstallationFile[] EMPTY_INSTALLATION_FILE_ARRAY = {};
private static final String SYSTEM_DATA_LOADER_PACKAGE = "android";
@@ -309,33 +309,8 @@
@GuardedBy("mLock")
private int mParentSessionId;
- static class FileInfo {
- public final int location;
- public final String name;
- public final Long lengthBytes;
- public final byte[] metadata;
- public final byte[] signature;
-
- public static FileInfo added(int location, String name, Long lengthBytes, byte[] metadata,
- byte[] signature) {
- return new FileInfo(location, name, lengthBytes, metadata, signature);
- }
-
- public static FileInfo removed(int location, String name) {
- return new FileInfo(location, name, -1L, null, null);
- }
-
- FileInfo(int location, String name, Long lengthBytes, byte[] metadata, byte[] signature) {
- this.location = location;
- this.name = name;
- this.lengthBytes = lengthBytes;
- this.metadata = metadata;
- this.signature = signature;
- }
- }
-
@GuardedBy("mLock")
- private ArrayList<FileInfo> mFiles = new ArrayList<>();
+ private ArrayList<InstallationFile> mFiles = new ArrayList<>();
@GuardedBy("mLock")
private boolean mStagedSessionApplied;
@@ -508,7 +483,7 @@
PackageSessionProvider sessionProvider, Looper looper, StagingManager stagingManager,
int sessionId, int userId, int installerUid, @NonNull InstallSource installSource,
SessionParams params, long createdMillis,
- File stageDir, String stageCid, FileInfo[] files, boolean prepared,
+ File stageDir, String stageCid, InstallationFile[] files, boolean prepared,
boolean committed, boolean sealed,
@Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
boolean isFailed, boolean isApplied, int stagedSessionErrorCode,
@@ -539,7 +514,7 @@
this.mParentSessionId = parentSessionId;
if (files != null) {
- for (FileInfo file : files) {
+ for (InstallationFile file : files) {
mFiles.add(file);
}
}
@@ -738,7 +713,7 @@
String[] result = new String[mFiles.size()];
for (int i = 0, size = mFiles.size(); i < size; ++i) {
- result[i] = mFiles.get(i).name;
+ result[i] = mFiles.get(i).getName();
}
return result;
}
@@ -2425,7 +2400,7 @@
assertCallerIsOwnerOrRootLocked();
assertPreparedAndNotSealedLocked("addFile");
- mFiles.add(FileInfo.added(location, name, lengthBytes, metadata, signature));
+ mFiles.add(new InstallationFile(location, name, lengthBytes, metadata, signature));
}
}
@@ -2443,7 +2418,7 @@
assertCallerIsOwnerOrRootLocked();
assertPreparedAndNotSealedLocked("removeFile");
- mFiles.add(FileInfo.removed(location, getRemoveMarkerName(name)));
+ mFiles.add(new InstallationFile(location, getRemoveMarkerName(name), -1, null, null));
}
}
@@ -2461,17 +2436,16 @@
}
final List<InstallationFile> addedFiles = new ArrayList<>(mFiles.size());
- for (FileInfo file : mFiles) {
- if (sAddedFilter.accept(new File(this.stageDir, file.name))) {
- addedFiles.add(new InstallationFile(
- file.name, file.lengthBytes, file.metadata));
+ for (InstallationFile file : mFiles) {
+ if (sAddedFilter.accept(new File(this.stageDir, file.getName()))) {
+ addedFiles.add(file);
}
}
final List<String> removedFiles = new ArrayList<>(mFiles.size());
- for (FileInfo file : mFiles) {
- if (sRemovedFilter.accept(new File(this.stageDir, file.name))) {
- String name = file.name.substring(
- 0, file.name.length() - REMOVE_MARKER_EXTENSION.length());
+ for (InstallationFile file : mFiles) {
+ if (sRemovedFilter.accept(new File(this.stageDir, file.getName()))) {
+ String name = file.getName().substring(
+ 0, file.getName().length() - REMOVE_MARKER_EXTENSION.length());
removedFiles.add(name);
}
}
@@ -2970,13 +2944,13 @@
writeIntAttribute(out, ATTR_SESSION_ID, childSessionId);
out.endTag(null, TAG_CHILD_SESSION);
}
- for (FileInfo fileInfo : mFiles) {
+ for (InstallationFile file : mFiles) {
out.startTag(null, TAG_SESSION_FILE);
- writeIntAttribute(out, ATTR_LOCATION, fileInfo.location);
- writeStringAttribute(out, ATTR_NAME, fileInfo.name);
- writeLongAttribute(out, ATTR_LENGTH_BYTES, fileInfo.lengthBytes);
- writeByteArrayAttribute(out, ATTR_METADATA, fileInfo.metadata);
- writeByteArrayAttribute(out, ATTR_SIGNATURE, fileInfo.signature);
+ writeIntAttribute(out, ATTR_LOCATION, file.getLocation());
+ writeStringAttribute(out, ATTR_NAME, file.getName());
+ writeLongAttribute(out, ATTR_LENGTH_BYTES, file.getLengthBytes());
+ writeByteArrayAttribute(out, ATTR_METADATA, file.getMetadata());
+ writeByteArrayAttribute(out, ATTR_SIGNATURE, file.getSignature());
out.endTag(null, TAG_SESSION_FILE);
}
}
@@ -3088,7 +3062,7 @@
List<String> grantedRuntimePermissions = new ArrayList<>();
List<String> whitelistedRestrictedPermissions = new ArrayList<>();
List<Integer> childSessionIds = new ArrayList<>();
- List<FileInfo> files = new ArrayList<>();
+ List<InstallationFile> files = new ArrayList<>();
int outerDepth = in.getDepth();
int type;
while ((type = in.next()) != XmlPullParser.END_DOCUMENT
@@ -3107,7 +3081,7 @@
childSessionIds.add(readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID));
}
if (TAG_SESSION_FILE.equals(in.getName())) {
- files.add(new FileInfo(
+ files.add(new InstallationFile(
readIntAttribute(in, ATTR_LOCATION, 0),
readStringAttribute(in, ATTR_NAME),
readLongAttribute(in, ATTR_LENGTH_BYTES, -1),
@@ -3135,16 +3109,16 @@
childSessionIdsArray = EMPTY_CHILD_SESSION_ARRAY;
}
- FileInfo[] fileInfosArray = null;
+ InstallationFile[] fileArray = null;
if (!files.isEmpty()) {
- fileInfosArray = files.toArray(EMPTY_FILE_INFO_ARRAY);
+ fileArray = files.toArray(EMPTY_INSTALLATION_FILE_ARRAY);
}
InstallSource installSource = InstallSource.create(installInitiatingPackageName,
installOriginatingPackageName, installerPackageName);
return new PackageInstallerSession(callback, context, pm, sessionProvider,
installerThread, stagingManager, sessionId, userId, installerUid,
- installSource, params, createdMillis, stageDir, stageCid, fileInfosArray,
+ installSource, params, createdMillis, stageDir, stageCid, fileArray,
prepared, committed, sealed, childSessionIdsArray, parentSessionId,
isReady, isFailed, isApplied, stagedSessionErrorCode, stagedSessionErrorMessage);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index cb94043..c69a62d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1163,7 +1163,7 @@
final InstallParams params = makeInstallParams();
if (params.sessionParams.dataLoaderParams == null) {
params.sessionParams.setDataLoaderParams(
- PackageManagerShellCommandDataLoader.getDataLoaderParams(this));
+ PackageManagerShellCommandDataLoader.getStreamingDataLoaderParams(this));
}
return doRunInstall(params);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
index 5dca9e1..4170be4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
@@ -54,7 +54,7 @@
private static final String STDIN_PATH = "-";
- static DataLoaderParams getDataLoaderParams(ShellCommand shellCommand) {
+ private static String getDataLoaderParamsArgs(ShellCommand shellCommand) {
int commandId;
synchronized (sShellCommands) {
// Clean up old references.
@@ -78,8 +78,12 @@
sShellCommands.put(commandId, new WeakReference<>(shellCommand));
}
- final String args = SHELL_COMMAND_ID_PREFIX + commandId;
- return DataLoaderParams.forStreaming(new ComponentName(PACKAGE, CLASS), args);
+ return SHELL_COMMAND_ID_PREFIX + commandId;
+ }
+
+ static DataLoaderParams getStreamingDataLoaderParams(ShellCommand shellCommand) {
+ return DataLoaderParams.forStreaming(new ComponentName(PACKAGE, CLASS),
+ getDataLoaderParamsArgs(shellCommand));
}
private static int extractShellCommandId(String args) {
@@ -133,17 +137,17 @@
return false;
}
try {
- for (InstallationFile fileInfo : addedFiles) {
- String filePath = new String(fileInfo.getMetadata(), StandardCharsets.UTF_8);
+ for (InstallationFile file : addedFiles) {
+ String filePath = new String(file.getMetadata(), StandardCharsets.UTF_8);
if (STDIN_PATH.equals(filePath) || TextUtils.isEmpty(filePath)) {
final ParcelFileDescriptor inFd = ParcelFileDescriptor.dup(
shellCommand.getInFileDescriptor());
- mConnector.writeData(fileInfo.getName(), 0, fileInfo.getSize(), inFd);
+ mConnector.writeData(file.getName(), 0, file.getLengthBytes(), inFd);
} else {
ParcelFileDescriptor incomingFd = null;
try {
incomingFd = shellCommand.openFileForSystem(filePath, "r");
- mConnector.writeData(fileInfo.getName(), 0, incomingFd.getStatSize(),
+ mConnector.writeData(file.getName(), 0, incomingFd.getStatSize(),
incomingFd);
} finally {
IoUtils.closeQuietly(incomingFd);
@@ -159,7 +163,8 @@
}
@Override
- public DataLoaderService.DataLoader onCreateDataLoader() {
+ public DataLoaderService.DataLoader onCreateDataLoader(
+ @NonNull DataLoaderParams dataLoaderParams) {
return new DataLoader();
}
}
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index 77bb48e..4c40448 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -89,7 +89,7 @@
* <ul>
* <li> 0 - disable whitelist (install all system packages; no logging)</li>
* <li> 1 - enforce (only install system packages if they are whitelisted)</li>
- * <li> 2 - log (log when a non-whitelisted package is run)</li>
+ * <li> 2 - log (log non-whitelisted packages)</li>
* <li> 4 - for all users: implicitly whitelist any package not mentioned in the whitelist</li>
* <li> 8 - for SYSTEM: implicitly whitelist any package not mentioned in the whitelist</li>
* <li> 16 - ignore OTAs (don't install system packages during OTAs)</li>
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 34d2c16..58455ca 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -36,7 +36,7 @@
import android.service.textclassifier.TextClassifierService;
import android.service.textclassifier.TextClassifierService.ConnectionState;
import android.text.TextUtils;
-import android.util.LruCache;
+import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseArray;
import android.view.textclassifier.ConversationActions;
@@ -65,6 +65,7 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Queue;
@@ -146,11 +147,7 @@
private final Object mLock;
@GuardedBy("mLock")
final SparseArray<UserState> mUserStates = new SparseArray<>();
- // SystemTextClassifier.onDestroy() is not guaranteed to be called, use LruCache here
- // to avoid leak.
- @GuardedBy("mLock")
- private final LruCache<TextClassificationSessionId, TextClassificationContext>
- mSessionContextCache = new LruCache<>(40);
+ private final SessionCache mSessionCache;
private final TextClassificationConstants mSettings;
@Nullable
private final String mDefaultTextClassifierPackage;
@@ -165,6 +162,7 @@
PackageManager packageManager = mContext.getPackageManager();
mDefaultTextClassifierPackage = packageManager.getDefaultTextClassifierPackageName();
mSystemTextClassifierPackage = packageManager.getSystemTextClassifierPackageName();
+ mSessionCache = new SessionCache(mLock);
}
private void startListenSettings() {
@@ -314,7 +312,7 @@
classificationContext.getUseDefaultTextClassifier(),
service -> {
service.onCreateTextClassificationSession(classificationContext, sessionId);
- mSessionContextCache.put(sessionId, classificationContext);
+ mSessionCache.put(sessionId, classificationContext);
},
"onCreateTextClassificationSession",
NO_OP_CALLBACK);
@@ -326,14 +324,14 @@
Objects.requireNonNull(sessionId);
synchronized (mLock) {
- TextClassificationContext textClassificationContext =
- mSessionContextCache.get(sessionId);
+ final StrippedTextClassificationContext textClassificationContext =
+ mSessionCache.get(sessionId);
final int userId = textClassificationContext != null
- ? textClassificationContext.getUserId()
+ ? textClassificationContext.userId
: UserHandle.getCallingUserId();
final boolean useDefaultTextClassifier =
textClassificationContext != null
- ? textClassificationContext.getUseDefaultTextClassifier()
+ ? textClassificationContext.useDefaultTextClassifier
: true;
handleRequest(
userId,
@@ -342,7 +340,7 @@
useDefaultTextClassifier,
service -> {
service.onDestroyTextClassificationSession(sessionId);
- mSessionContextCache.remove(sessionId);
+ mSessionCache.remove(sessionId);
},
"onDestroyTextClassificationSession",
NO_OP_CALLBACK);
@@ -409,7 +407,7 @@
pw.decreaseIndent();
}
}
- pw.println("Number of active sessions: " + mSessionContextCache.size());
+ pw.println("Number of active sessions: " + mSessionCache.size());
}
}
@@ -568,6 +566,81 @@
}
}
+ /**
+ * Stores the stripped down version of {@link TextClassificationContext}s, i.e. {@link
+ * StrippedTextClassificationContext}, keyed by {@link TextClassificationSessionId}. Sessions
+ * are cleaned up automatically when the client process is dead.
+ */
+ static final class SessionCache {
+ @NonNull
+ private final Object mLock;
+ @NonNull
+ @GuardedBy("mLock")
+ private final Map<TextClassificationSessionId, StrippedTextClassificationContext> mCache =
+ new ArrayMap<>();
+ @NonNull
+ @GuardedBy("mLock")
+ private final Map<TextClassificationSessionId, DeathRecipient> mDeathRecipients =
+ new ArrayMap<>();
+
+ SessionCache(@NonNull Object lock) {
+ mLock = Objects.requireNonNull(lock);
+ }
+
+ void put(@NonNull TextClassificationSessionId sessionId,
+ @NonNull TextClassificationContext textClassificationContext) {
+ synchronized (mLock) {
+ mCache.put(sessionId,
+ new StrippedTextClassificationContext(textClassificationContext));
+ try {
+ DeathRecipient deathRecipient = () -> remove(sessionId);
+ sessionId.getToken().linkToDeath(deathRecipient, /* flags= */ 0);
+ mDeathRecipients.put(sessionId, deathRecipient);
+ } catch (RemoteException e) {
+ Slog.w(LOG_TAG, "SessionCache: Failed to link to death", e);
+ }
+ }
+ }
+
+ @Nullable
+ StrippedTextClassificationContext get(@NonNull TextClassificationSessionId sessionId) {
+ Objects.requireNonNull(sessionId);
+ synchronized (mLock) {
+ return mCache.get(sessionId);
+ }
+ }
+
+ void remove(@NonNull TextClassificationSessionId sessionId) {
+ Objects.requireNonNull(sessionId);
+ synchronized (mLock) {
+ DeathRecipient deathRecipient = mDeathRecipients.get(sessionId);
+ if (deathRecipient != null) {
+ sessionId.getToken().unlinkToDeath(deathRecipient, /* flags= */ 0);
+ }
+ mDeathRecipients.remove(sessionId);
+ mCache.remove(sessionId);
+ }
+ }
+
+ int size() {
+ synchronized (mLock) {
+ return mCache.size();
+ }
+ }
+ }
+
+ /** A stripped down version of {@link TextClassificationContext}. */
+ static class StrippedTextClassificationContext {
+ @UserIdInt
+ public final int userId;
+ public final boolean useDefaultTextClassifier;
+
+ StrippedTextClassificationContext(TextClassificationContext textClassificationContext) {
+ userId = textClassificationContext.getUserId();
+ useDefaultTextClassifier = textClassificationContext.getUseDefaultTextClassifier();
+ }
+ }
+
private final class UserState {
@UserIdInt
final int mUserId;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a54f5d4..aa6d854 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7674,7 +7674,7 @@
mainWindow.getContentInsets(insets);
InsetUtils.addInsets(insets, getLetterboxInsets());
return new RemoteAnimationTarget(task.mTaskId, record.getMode(),
- record.mAdapter.mCapturedLeash, !task.fillsParent(),
+ record.mAdapter.mCapturedLeash, !fillsParent(),
mainWindow.mWinAnimator.mLastClipRect, insets,
getPrefixOrderIndex(), record.mAdapter.mPosition,
record.mAdapter.mStackBounds, task.getWindowConfiguration(),
@@ -7692,4 +7692,9 @@
}
win.getAnimationFrames(outFrame, outInsets, outStableInsets, outSurfaceInsets);
}
+
+ void setPictureInPictureParams(PictureInPictureParams p) {
+ pictureInPictureArgs.copyOnlySet(p);
+ getTask().getRootTask().setPictureInPictureParams(p);
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 5f3e3a3..4b96ea0 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2795,6 +2795,7 @@
}
}
+ // TODO(148895075): deprecate and replace with task equivalents
@Override
public List<ActivityManager.StackInfo> getAllStackInfos() {
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getAllStackInfos()");
@@ -2821,6 +2822,7 @@
}
}
+ // TODO(148895075): deprecate and replace with task equivalents
@Override
public List<ActivityManager.StackInfo> getAllStackInfosOnDisplay(int displayId) {
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getAllStackInfos()");
@@ -4153,7 +4155,7 @@
return;
}
// Only update the saved args from the args that are set
- r.pictureInPictureArgs.copyOnlySet(params);
+ r.setPictureInPictureParams(params);
final float aspectRatio = r.pictureInPictureArgs.getAspectRatio();
final List<RemoteAction> actions = r.pictureInPictureArgs.getActions();
// Adjust the source bounds by the insets for the transition down
@@ -4201,7 +4203,7 @@
"setPictureInPictureParams", token, params);
// Only update the saved args from the args that are set
- r.pictureInPictureArgs.copyOnlySet(params);
+ r.setPictureInPictureParams(params);
if (r.inPinnedWindowingMode()) {
// If the activity is already in picture-in-picture, update the pinned stack now
// if it is not already expanding to fullscreen. Otherwise, the arguments will
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index 06e7b48..9e93e14 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -18,6 +18,9 @@
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import android.content.res.Resources;
+import android.text.TextUtils;
+
import com.android.server.wm.DisplayContent.TaskContainers;
/**
@@ -42,7 +45,18 @@
*/
protected final TaskContainers mTaskContainers;
- DisplayAreaPolicy(WindowManagerService wmService,
+ /**
+ * Construct a new {@link DisplayAreaPolicy}
+ *
+ * @param wmService the window manager service instance
+ * @param content the display content for which the policy applies
+ * @param root the root display area under which the policy operates
+ * @param imeContainer the ime container that the policy must attach
+ * @param taskContainers the task container that the policy must attach
+ *
+ * @see #attachDisplayAreas()
+ */
+ protected DisplayAreaPolicy(WindowManagerService wmService,
DisplayContent content, DisplayArea.Root root,
DisplayArea<? extends WindowContainer> imeContainer, TaskContainers taskContainers) {
mWmService = wmService;
@@ -119,5 +133,55 @@
throw new IllegalArgumentException("don't know how to sort " + token);
}
}
+
+ /** Provider for {@link DisplayAreaPolicy.Default platform-default display area policy}. */
+ static class Provider implements DisplayAreaPolicy.Provider {
+ @Override
+ public DisplayAreaPolicy instantiate(WindowManagerService wmService,
+ DisplayContent content, DisplayArea.Root root,
+ DisplayArea<? extends WindowContainer> imeContainer,
+ TaskContainers taskContainers) {
+ return new DisplayAreaPolicy.Default(wmService, content, root, imeContainer,
+ taskContainers);
+ }
+ }
+ }
+
+ /**
+ * Provider for {@link DisplayAreaPolicy} instances.
+ *
+ * By implementing this interface and overriding the
+ * {@code config_deviceSpecificDisplayAreaPolicyProvider}, a device-specific implementations
+ * of {@link DisplayAreaPolicy} can be supplied.
+ */
+ public interface Provider {
+ /**
+ * Instantiate a new DisplayAreaPolicy.
+ *
+ * @see DisplayAreaPolicy#DisplayAreaPolicy
+ */
+ DisplayAreaPolicy instantiate(WindowManagerService wmService,
+ DisplayContent content, DisplayArea.Root root,
+ DisplayArea<? extends WindowContainer> imeContainer,
+ TaskContainers taskContainers);
+
+ /**
+ * Instantiate the device-specific {@link Provider}.
+ */
+ static Provider fromResources(Resources res) {
+ String name = res.getString(
+ com.android.internal.R.string.config_deviceSpecificDisplayAreaPolicyProvider);
+ if (TextUtils.isEmpty(name)) {
+ return new DisplayAreaPolicy.Default.Provider();
+ }
+ try {
+ return (Provider) Class.forName(name).newInstance();
+ } catch (ReflectiveOperationException | ClassCastException e) {
+ throw new IllegalStateException("Couldn't instantiate class " + name
+ + " for config_deviceSpecificDisplayAreaPolicyProvider:"
+ + " make sure it has a public zero-argument constructor"
+ + " and implements DisplayAreaPolicy.Provider", e);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 810aa34..3b658c0 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -304,8 +304,7 @@
private final DisplayArea.Root mRootDisplayArea = new DisplayArea.Root(mWmService);
- private final DisplayAreaPolicy mDisplayAreaPolicy = new DisplayAreaPolicy.Default(
- mWmService, this, mRootDisplayArea, mImeWindowsContainers, mTaskContainers);
+ private final DisplayAreaPolicy mDisplayAreaPolicy;
private WindowState mTmpWindow;
private WindowState mTmpWindow2;
@@ -1027,6 +1026,8 @@
super.addChild(mWindowContainers, null);
super.addChild(mOverlayContainers, null);
+ mDisplayAreaPolicy = mWmService.mDisplayAreaPolicyProvider.instantiate(
+ mWmService, this, mRootDisplayArea, mImeWindowsContainers, mTaskContainers);
mWindowContainers.addChildren();
// Sets the display content for the children.
@@ -5659,7 +5660,8 @@
return activityType == ACTIVITY_TYPE_STANDARD
&& (windowingMode == WINDOWING_MODE_FULLSCREEN
|| windowingMode == WINDOWING_MODE_FREEFORM
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+ || windowingMode == WINDOWING_MODE_MULTI_WINDOW);
}
/**
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index d0179ad..51b9916 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -44,6 +44,7 @@
import android.view.WindowInsetsAnimationCallback;
import android.view.WindowInsetsAnimationControlListener;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.DisplayThread;
/**
@@ -107,11 +108,11 @@
changed = true;
}
if (changed) {
- startAnimation(mShowingTransientTypes, true, () -> {
+ mPolicy.getStatusBarManagerInternal().showTransient(mDisplayContent.getDisplayId(),
+ mShowingTransientTypes.toArray());
+ updateBarControlTarget(mFocusedWin);
+ startAnimation(true /* show */, () -> {
synchronized (mDisplayContent.mWmService.mGlobalLock) {
- mPolicy.getStatusBarManagerInternal().showTransient(
- mDisplayContent.getDisplayId(),
- mShowingTransientTypes.toArray());
mStateController.notifyInsetsChanged();
}
});
@@ -122,7 +123,7 @@
if (mShowingTransientTypes.size() == 0) {
return;
}
- startAnimation(mShowingTransientTypes, false, () -> {
+ startAnimation(false /* show */, () -> {
synchronized (mDisplayContent.mWmService.mGlobalLock) {
mShowingTransientTypes.clear();
mStateController.notifyInsetsChanged();
@@ -268,18 +269,20 @@
return isDockedStackVisible || isFreeformStackVisible || isResizing;
}
- private void startAnimation(IntArray internalTypes, boolean show, Runnable callback) {
+ @VisibleForTesting
+ void startAnimation(boolean show, Runnable callback) {
int typesReady = 0;
final SparseArray<InsetsSourceControl> controls = new SparseArray<>();
- updateBarControlTarget(mFocusedWin);
- for (int i = internalTypes.size() - 1; i >= 0; i--) {
+ final IntArray showingTransientTypes = mShowingTransientTypes;
+ for (int i = showingTransientTypes.size() - 1; i >= 0; i--) {
InsetsSourceProvider provider =
- mStateController.getSourceProvider(internalTypes.get(i));
- if (provider == null) continue;
- InsetsSourceControl control = provider.getControl(provider.getControlTarget());
- if (control == null || control.getLeash() == null) continue;
- typesReady |= InsetsState.toPublicType(internalTypes.get(i));
- controls.put(control.getType(), control);
+ mStateController.getSourceProvider(showingTransientTypes.get(i));
+ InsetsSourceControl control = provider.getControl(mTransientControlTarget);
+ if (control == null || control.getLeash() == null) {
+ continue;
+ }
+ typesReady |= InsetsState.toPublicType(showingTransientTypes.get(i));
+ controls.put(control.getType(), new InsetsSourceControl(control));
}
controlAnimationUnchecked(typesReady, controls, show, callback);
}
@@ -335,7 +338,6 @@
private InsetsPolicyAnimationControlListener mListener;
InsetsPolicyAnimationControlCallbacks(InsetsPolicyAnimationControlListener listener) {
- super();
mListener = listener;
}
@@ -353,9 +355,11 @@
InsetsController.INTERPOLATOR, true,
show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
: LAYOUT_INSETS_DURING_ANIMATION_HIDDEN);
+ SurfaceAnimationThread.getHandler().post(
+ () -> mListener.onReady(mAnimationControl, typesReady));
}
- /** Called on SurfaceAnimationThread lock without global WM lock held. */
+ /** Called on SurfaceAnimationThread without global WM lock held. */
@Override
public void scheduleApplyChangeInsets() {
InsetsState state = getState();
@@ -384,7 +388,7 @@
return overrideState;
}
- /** Called on SurfaceAnimationThread lock without global WM lock held. */
+ /** Called on SurfaceAnimationThread without global WM lock held. */
@Override
public void applySurfaceParams(
final SyncRtSurfaceTransactionApplier.SurfaceParams... params) {
@@ -396,14 +400,12 @@
t.apply();
}
- /** Called on SurfaceAnimationThread lock without global WM lock held. */
@Override
public void startAnimation(InsetsAnimationControlImpl controller,
WindowInsetsAnimationControlListener listener, int types,
WindowInsetsAnimationCallback.InsetsAnimation animation,
WindowInsetsAnimationCallback.AnimationBounds bounds,
int layoutDuringAnimation) {
- SurfaceAnimationThread.getHandler().post(() -> listener.onReady(controller, types));
}
}
}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 7986659..0d3f6b9 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -59,6 +59,7 @@
private final InsetsSourceControl mFakeControl;
private @Nullable InsetsSourceControl mControl;
private @Nullable InsetsControlTarget mControlTarget;
+ private @Nullable InsetsControlTarget mPendingControlTarget;
private @Nullable InsetsControlTarget mFakeControlTarget;
private @Nullable ControlAdapter mAdapter;
@@ -140,8 +141,9 @@
mSource.setVisibleFrame(null);
} else if (mControllable) {
mWin.setControllableInsetProvider(this);
- if (mControlTarget != null) {
- updateControlForTarget(mControlTarget, true /* force */);
+ if (mPendingControlTarget != null) {
+ updateControlForTarget(mPendingControlTarget, true /* force */);
+ mPendingControlTarget = null;
}
}
}
@@ -245,7 +247,7 @@
setWindow(null, null, null);
}
if (mWin == null) {
- mControlTarget = target;
+ mPendingControlTarget = target;
return;
}
if (target == mControlTarget && !force) {
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index caaa430..2d7d3f1 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.view.InsetsState.ITYPE_CAPTION_BAR;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
@@ -89,6 +90,12 @@
if (type == ITYPE_NAVIGATION_BAR) {
state.removeSource(ITYPE_IME);
state.removeSource(ITYPE_STATUS_BAR);
+ state.removeSource(ITYPE_CAPTION_BAR);
+ }
+
+ // Status bar doesn't get influenced by caption bar
+ if (type == ITYPE_STATUS_BAR) {
+ state.removeSource(ITYPE_CAPTION_BAR);
}
// IME needs different frames for certain cases (e.g. navigation bar in gesture nav).
@@ -212,18 +219,18 @@
/**
* Called when the focused window that is able to control the system bars changes.
*
- * @param topControlling The target that is now able to control the top bar appearance
- * and visibility.
+ * @param statusControlling The target that is now able to control the status bar appearance
+ * and visibility.
* @param navControlling The target that is now able to control the nav bar appearance
* and visibility.
*/
- void onBarControlTargetChanged(@Nullable InsetsControlTarget topControlling,
- @Nullable InsetsControlTarget fakeTopControlling,
+ void onBarControlTargetChanged(@Nullable InsetsControlTarget statusControlling,
+ @Nullable InsetsControlTarget fakeStatusControlling,
@Nullable InsetsControlTarget navControlling,
@Nullable InsetsControlTarget fakeNavControlling) {
- onControlChanged(ITYPE_STATUS_BAR, topControlling);
+ onControlChanged(ITYPE_STATUS_BAR, statusControlling);
onControlChanged(ITYPE_NAVIGATION_BAR, navControlling);
- onControlFakeTargetChanged(ITYPE_STATUS_BAR, fakeTopControlling);
+ onControlFakeTargetChanged(ITYPE_STATUS_BAR, fakeStatusControlling);
onControlFakeTargetChanged(ITYPE_NAVIGATION_BAR, fakeNavControlling);
notifyPendingInsetsControlChanged();
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 302e759..326335e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -99,6 +99,7 @@
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.AppGlobals;
+import android.app.PictureInPictureParams;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.content.ComponentName;
@@ -461,6 +462,11 @@
*/
ITaskOrganizer mTaskOrganizer;
+ /**
+ * Last Picture-in-Picture params applicable to the task. Updated when the app
+ * enters Picture-in-Picture or when setPictureInPictureParams is called.
+ */
+ PictureInPictureParams mPictureInPictureParams = new PictureInPictureParams.Builder().build();
/**
* Don't use constructor directly. Use {@link #create(ActivityTaskManagerService, int,
@@ -3217,6 +3223,12 @@
// order changes.
final Task top = getTopMostTask();
info.resizeMode = top != null ? top.mResizeMode : mResizeMode;
+
+ if (mPictureInPictureParams.empty()) {
+ info.pictureInPictureParams = null;
+ } else {
+ info.pictureInPictureParams = mPictureInPictureParams;
+ }
}
/**
@@ -3947,4 +3959,10 @@
void onWindowFocusChanged(boolean hasFocus) {
updateShadowsRadius(hasFocus, getPendingTransaction());
}
+
+ void setPictureInPictureParams(PictureInPictureParams p) {
+ mPictureInPictureParams.copyOnlySet(p);
+ mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(
+ this, true /* force */);
+ }
}
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 0a0530c9..4b13a0c 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -43,6 +43,7 @@
import android.view.SurfaceControl;
import android.view.WindowContainerTransaction;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.function.pooled.PooledConsumer;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -379,7 +380,8 @@
}
@Override
- public List<RunningTaskInfo> getChildTasks(IWindowContainer parent) {
+ public List<RunningTaskInfo> getChildTasks(IWindowContainer parent,
+ @Nullable int[] activityTypes) {
enforceStackPermission("getChildTasks()");
final long ident = Binder.clearCallingIdentity();
try {
@@ -405,6 +407,10 @@
for (int i = dc.getStackCount() - 1; i >= 0; --i) {
final ActivityStack as = dc.getStackAt(i);
if (as.getTile() == container) {
+ if (activityTypes != null
+ && !ArrayUtils.contains(activityTypes, as.getActivityType())) {
+ continue;
+ }
final RunningTaskInfo info = new RunningTaskInfo();
as.fillTaskInfo(info);
out.add(info);
@@ -417,6 +423,40 @@
}
}
+ @Override
+ public List<RunningTaskInfo> getRootTasks(int displayId, @Nullable int[] activityTypes) {
+ enforceStackPermission("getRootTasks()");
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ final DisplayContent dc =
+ mService.mRootWindowContainer.getDisplayContent(displayId);
+ if (dc == null) {
+ throw new IllegalArgumentException("Display " + displayId + " doesn't exist");
+ }
+ ArrayList<RunningTaskInfo> out = new ArrayList<>();
+ for (int i = dc.getStackCount() - 1; i >= 0; --i) {
+ final ActivityStack task = dc.getStackAt(i);
+ if (task.getTile() != null) {
+ // a tile is supposed to look like a parent, so don't include their
+ // "children" here. They can be accessed via getChildTasks()
+ continue;
+ }
+ if (activityTypes != null
+ && !ArrayUtils.contains(activityTypes, task.getActivityType())) {
+ continue;
+ }
+ final RunningTaskInfo info = new RunningTaskInfo();
+ task.fillTaskInfo(info);
+ out.add(info);
+ }
+ return out;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
private int sanitizeAndApplyChange(WindowContainer container,
WindowContainerTransaction.Change change) {
if (!(container instanceof Task)) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a370093..d98c18c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -187,6 +187,7 @@
import android.os.Trace;
import android.os.UserHandle;
import android.os.WorkSource;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
@@ -309,6 +310,8 @@
implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowManagerService" : TAG_WM;
+ private static final String WM_USE_BLAST_ADAPTER_FLAG = "wm_use_blast_adapter";
+
static final int LAYOUT_REPEAT_THRESHOLD = 4;
static final boolean PROFILE_ORIENTATION = false;
@@ -413,6 +416,8 @@
final WindowTracing mWindowTracing;
+ final DisplayAreaPolicy.Provider mDisplayAreaPolicyProvider;
+
final private KeyguardDisableHandler mKeyguardDisableHandler;
// TODO: eventually unify all keyguard state in a common place instead of having it spread over
// AM's KeyguardController and the policy's KeyguardServiceDelegate.
@@ -623,6 +628,9 @@
// The root of the device window hierarchy.
RootWindowContainer mRoot;
+ // Whether the system should use BLAST for ViewRootImpl
+ final boolean mUseBLAST;
+
int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
Rect mDockedStackCreateBounds;
@@ -1137,6 +1145,10 @@
mAnimator = new WindowAnimator(this);
mRoot = new RootWindowContainer(this);
+ mUseBLAST = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT,
+ WM_USE_BLAST_ADAPTER_FLAG, false);
+
mWindowPlacerLocked = new WindowSurfacePlacer(this);
mTaskSnapshotController = new TaskSnapshotController(this);
@@ -1255,6 +1267,10 @@
LocalServices.addService(WindowManagerInternal.class, new LocalService());
mEmbeddedWindowController = new EmbeddedWindowController(mGlobalLock);
+
+ mDisplayAreaPolicyProvider = DisplayAreaPolicy.Provider.fromResources(
+ mContext.getResources());
+
setGlobalShadowSettings();
}
@@ -5051,6 +5067,11 @@
}
@Override
+ public boolean useBLAST() {
+ return mUseBLAST;
+ }
+
+ @Override
public void getInitialDisplaySize(int displayId, Point size) {
synchronized (mGlobalLock) {
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 109f39d..cb687c9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -553,6 +553,16 @@
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
private static final long ADMIN_APP_PASSWORD_COMPLEXITY = 123562444L;
+ /**
+ * Admin apps targeting Android R+ may not use
+ * {@link android.app.admin.DevicePolicyManager#setSecureSetting} to change the deprecated
+ * {@link android.provider.Settings.Secure#LOCATION_MODE} setting. Instead they should use
+ * {@link android.app.admin.DevicePolicyManager#setLocationEnabled}.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ private static final long USE_SET_LOCATION_ENABLED = 117835097L;
+
final Context mContext;
final Injector mInjector;
final IPackageManager mIPackageManager;
@@ -11554,13 +11564,22 @@
@Override
public void setLocationEnabled(ComponentName who, boolean locationEnabled) {
- Objects.requireNonNull(who, "ComponentName is null");
- enforceDeviceOwner(who);
+ enforceDeviceOwner(Objects.requireNonNull(who));
- UserHandle userHandle = mInjector.binderGetCallingUserHandle();
- mInjector.binderWithCleanCallingIdentity(
- () -> mInjector.getLocationManager().setLocationEnabledForUser(locationEnabled,
- userHandle));
+ UserHandle user = mInjector.binderGetCallingUserHandle();
+
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ boolean wasLocationEnabled = mInjector.getLocationManager().isLocationEnabledForUser(
+ user);
+ mInjector.getLocationManager().setLocationEnabledForUser(locationEnabled, user);
+
+ // make a best effort to only show the notification if the admin is actually changing
+ // something. this is subject to race conditions with settings changes, but those are
+ // unlikely to realistically interfere
+ if (wasLocationEnabled != locationEnabled) {
+ showLocationSettingsChangedNotification(user);
+ }
+ });
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_SECURE_SETTING)
@@ -11571,6 +11590,25 @@
.write();
}
+ private void showLocationSettingsChangedNotification(UserHandle user) {
+ PendingIntent locationSettingsIntent = mInjector.pendingIntentGetActivityAsUser(mContext, 0,
+ new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), PendingIntent.FLAG_UPDATE_CURRENT,
+ null, user);
+ Notification notification = new Notification.Builder(mContext,
+ SystemNotificationChannels.DEVICE_ADMIN)
+ .setSmallIcon(R.drawable.ic_info_outline)
+ .setContentTitle(mContext.getString(R.string.location_changed_notification_title))
+ .setContentText(mContext.getString(R.string.location_changed_notification_text))
+ .setColor(mContext.getColor(R.color.system_notification_accent_color))
+ .setShowWhen(true)
+ .setContentIntent(locationSettingsIntent)
+ .setAutoCancel(true)
+ .build();
+ mInjector.getNotificationManager().notify(SystemMessage.NOTE_LOCATION_CHANGED,
+ notification);
+ }
+
@Override
public void requestSetLocationProviderAllowed(ComponentName who, String provider,
boolean providerAllowed) {
@@ -11641,6 +11679,12 @@
throw new SecurityException(String.format(
"Permission denial: Profile owners cannot update %1$s", setting));
}
+ if (setting.equals(Settings.Secure.LOCATION_MODE)
+ && isSetSecureSettingLocationModeCheckEnabled(who.getPackageName(),
+ callingUserId)) {
+ throw new UnsupportedOperationException(Settings.Secure.LOCATION_MODE + " is "
+ + "deprecated. Please use setLocationEnabled() instead.");
+ }
if (setting.equals(Settings.Secure.INSTALL_NON_MARKET_APPS)) {
if (getTargetSdk(who.getPackageName(), callingUserId) >= Build.VERSION_CODES.O) {
throw new UnsupportedOperationException(Settings.Secure.INSTALL_NON_MARKET_APPS
@@ -11685,6 +11729,9 @@
saveSettingsLocked(callingUserId);
}
mInjector.settingsSecurePutStringForUser(setting, value, callingUserId);
+ if (setting.equals(Settings.Secure.LOCATION_MODE)) {
+ showLocationSettingsChangedNotification(UserHandle.of(callingUserId));
+ }
});
}
DevicePolicyEventLogger
@@ -11694,6 +11741,16 @@
.write();
}
+ private boolean isSetSecureSettingLocationModeCheckEnabled(String packageName, int userId) {
+ try {
+ return mIPlatformCompat.isChangeEnabledByPackageName(USE_SET_LOCATION_ENABLED,
+ packageName, userId);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Failed to get a response from PLATFORM_COMPAT_SERVICE", e);
+ return getTargetSdk(packageName, userId) > Build.VERSION_CODES.Q;
+ }
+ }
+
@Override
public void setMasterVolumeMuted(ComponentName who, boolean on) {
Objects.requireNonNull(who, "ComponentName is null");
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 3b51377..8012e74 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -1180,7 +1180,7 @@
}
// Create new lib file without signature info
- incfs::NewFileParams libFileParams;
+ incfs::NewFileParams libFileParams{};
libFileParams.size = uncompressedLen;
libFileParams.verification.hashAlgorithm = INCFS_HASH_NONE;
// Metadata of the new lib file is its relative path
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index 2444dde..2e7ced3 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -122,7 +122,6 @@
}
RawMetadata getMetadata(StorageId storage, FileId node) const;
- std::string getSignatureData(StorageId storage, FileId node) const;
std::vector<std::string> listFiles(StorageId storage) const;
bool startLoading(StorageId storage) const;
diff --git a/services/people/java/com/android/server/people/data/AbstractProtoDiskReadWriter.java b/services/people/java/com/android/server/people/data/AbstractProtoDiskReadWriter.java
new file mode 100644
index 0000000..203e980
--- /dev/null
+++ b/services/people/java/com/android/server/people/data/AbstractProtoDiskReadWriter.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.people.data;
+
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.WorkerThread;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Base class for reading and writing protobufs on disk from a root directory. Callers should
+ * ensure that the root directory is unlocked before doing I/O operations using this class.
+ *
+ * @param <T> is the data class representation of a protobuf.
+ */
+abstract class AbstractProtoDiskReadWriter<T> {
+
+ private static final String TAG = AbstractProtoDiskReadWriter.class.getSimpleName();
+ private static final long SHUTDOWN_DISK_WRITE_TIMEOUT = 5L * DateUtils.SECOND_IN_MILLIS;
+
+ private final File mRootDir;
+ private final ScheduledExecutorService mScheduledExecutorService;
+ private final long mWriteDelayMs;
+
+ @GuardedBy("this")
+ private ScheduledFuture<?> mScheduledFuture;
+
+ @GuardedBy("this")
+ private Map<String, T> mScheduledFileDataMap = new ArrayMap<>();
+
+ /**
+ * Child class shall provide a {@link ProtoStreamWriter} to facilitate the writing of data as a
+ * protobuf on disk.
+ */
+ abstract ProtoStreamWriter<T> protoStreamWriter();
+
+ /**
+ * Child class shall provide a {@link ProtoStreamReader} to facilitate the reading of protobuf
+ * data on disk.
+ */
+ abstract ProtoStreamReader<T> protoStreamReader();
+
+ AbstractProtoDiskReadWriter(@NonNull File rootDir, long writeDelayMs,
+ @NonNull ScheduledExecutorService scheduledExecutorService) {
+ mRootDir = rootDir;
+ mWriteDelayMs = writeDelayMs;
+ mScheduledExecutorService = scheduledExecutorService;
+ }
+
+ @WorkerThread
+ void delete(@NonNull String fileName) {
+ final File file = getFile(fileName);
+ if (!file.exists()) {
+ return;
+ }
+ if (!file.delete()) {
+ Slog.e(TAG, "Failed to delete file: " + file.getPath());
+ }
+ }
+
+ @WorkerThread
+ void writeTo(@NonNull String fileName, @NonNull T data) {
+ final File file = getFile(fileName);
+ final AtomicFile atomicFile = new AtomicFile(file);
+
+ FileOutputStream fileOutputStream = null;
+ try {
+ fileOutputStream = atomicFile.startWrite();
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to write to protobuf file.", e);
+ return;
+ }
+
+ try {
+ final ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream);
+ protoStreamWriter().write(protoOutputStream, data);
+ protoOutputStream.flush();
+ atomicFile.finishWrite(fileOutputStream);
+ fileOutputStream = null;
+ } finally {
+ // When fileInputStream is null (successful write), this will no-op.
+ atomicFile.failWrite(fileOutputStream);
+ }
+ }
+
+ @WorkerThread
+ @Nullable
+ T read(@NonNull String fileName) {
+ File[] files = mRootDir.listFiles(
+ pathname -> pathname.isFile() && pathname.getName().equals(fileName));
+ if (files == null || files.length == 0) {
+ return null;
+ } else if (files.length > 1) {
+ // This can't possibly happen, but sanity check.
+ Slog.w(TAG, "Found multiple files with the same name: " + Arrays.toString(files));
+ }
+ return parseFile(files[0]);
+ }
+
+ /**
+ * Reads all files in directory and returns a map with file names as keys and parsed file
+ * contents as values.
+ */
+ @WorkerThread
+ @Nullable
+ Map<String, T> readAll() {
+ File[] files = mRootDir.listFiles(File::isFile);
+ if (files == null) {
+ return null;
+ }
+
+ Map<String, T> results = new ArrayMap<>();
+ for (File file : files) {
+ T result = parseFile(file);
+ if (result != null) {
+ results.put(file.getName(), result);
+ }
+ }
+ return results;
+ }
+
+ /**
+ * Schedules the specified data to be flushed to a file in the future. Subsequent
+ * calls for the same file before the flush occurs will replace the previous data but will not
+ * reset when the flush will occur. All unique files will be flushed at the same time.
+ */
+ @MainThread
+ synchronized void scheduleSave(@NonNull String fileName, @NonNull T data) {
+ mScheduledFileDataMap.put(fileName, data);
+
+ if (mScheduledExecutorService.isShutdown()) {
+ Slog.e(TAG, "Worker is shutdown, failed to schedule data saving.");
+ return;
+ }
+
+ // Skip scheduling another flush when one is pending.
+ if (mScheduledFuture != null) {
+ return;
+ }
+
+ mScheduledFuture = mScheduledExecutorService.schedule(this::flushScheduledData,
+ mWriteDelayMs, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Saves specified data immediately on a background thread, and blocks until its completed. This
+ * is useful for when device is powering off.
+ */
+ @MainThread
+ synchronized void saveImmediately(@NonNull String fileName, @NonNull T data) {
+ if (mScheduledExecutorService.isShutdown()) {
+ return;
+ }
+ // Cancel existing future.
+ if (mScheduledFuture != null) {
+
+ // We shouldn't need to interrupt as this method and threaded task
+ // #flushScheduledData are both synchronized.
+ mScheduledFuture.cancel(true);
+ }
+
+ mScheduledFileDataMap.put(fileName, data);
+ // Submit flush and blocks until it completes. Blocking will prevent the device from
+ // shutting down before flushing completes.
+ Future<?> future = mScheduledExecutorService.submit(this::flushScheduledData);
+ try {
+ future.get(SHUTDOWN_DISK_WRITE_TIMEOUT, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ Slog.e(TAG, "Failed to save data immediately.", e);
+ }
+ }
+
+ @WorkerThread
+ private synchronized void flushScheduledData() {
+ if (mScheduledFileDataMap.isEmpty()) {
+ mScheduledFuture = null;
+ return;
+ }
+ for (String fileName : mScheduledFileDataMap.keySet()) {
+ T data = mScheduledFileDataMap.remove(fileName);
+ writeTo(fileName, data);
+ }
+ mScheduledFuture = null;
+ }
+
+ @WorkerThread
+ @Nullable
+ private T parseFile(@NonNull File file) {
+ final AtomicFile atomicFile = new AtomicFile(file);
+ try (FileInputStream fileInputStream = atomicFile.openRead()) {
+ final ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
+ return protoStreamReader().read(protoInputStream);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to parse protobuf file.", e);
+ }
+ return null;
+ }
+
+ @NonNull
+ private File getFile(String fileName) {
+ return new File(mRootDir, fileName);
+ }
+
+ /**
+ * {@code ProtoStreamWriter} writes {@code T} fields to {@link ProtoOutputStream}.
+ *
+ * @param <T> is the data class representation of a protobuf.
+ */
+ interface ProtoStreamWriter<T> {
+
+ /**
+ * Writes {@code T} to {@link ProtoOutputStream}.
+ */
+ void write(@NonNull ProtoOutputStream protoOutputStream, @NonNull T data);
+ }
+
+ /**
+ * {@code ProtoStreamReader} reads {@link ProtoInputStream} and translate it to {@code T}.
+ *
+ * @param <T> is the data class representation of a protobuf.
+ */
+ interface ProtoStreamReader<T> {
+ /**
+ * Reads {@link ProtoInputStream} and translates it to {@code T}.
+ */
+ @Nullable
+ T read(@NonNull ProtoInputStream protoInputStream);
+ }
+}
diff --git a/services/people/java/com/android/server/people/data/ConversationInfo.java b/services/people/java/com/android/server/people/data/ConversationInfo.java
index b60ed3e..ce35366 100644
--- a/services/people/java/com/android/server/people/data/ConversationInfo.java
+++ b/services/people/java/com/android/server/people/data/ConversationInfo.java
@@ -20,12 +20,18 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.LocusId;
+import android.content.LocusIdProto;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutInfo.ShortcutFlags;
import android.net.Uri;
+import android.util.Slog;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.util.Preconditions;
+import com.android.server.people.ConversationInfoProto;
+import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
@@ -35,6 +41,8 @@
*/
public class ConversationInfo {
+ private static final String TAG = ConversationInfo.class.getSimpleName();
+
private static final int FLAG_IMPORTANT = 1;
private static final int FLAG_NOTIFICATION_SILENCED = 1 << 1;
@@ -241,6 +249,72 @@
return (mConversationFlags & flags) == flags;
}
+ /** Writes field members to {@link ProtoOutputStream}. */
+ void writeToProto(@NonNull ProtoOutputStream protoOutputStream) {
+ protoOutputStream.write(ConversationInfoProto.SHORTCUT_ID, mShortcutId);
+ if (mLocusId != null) {
+ long locusIdToken = protoOutputStream.start(ConversationInfoProto.LOCUS_ID_PROTO);
+ protoOutputStream.write(LocusIdProto.LOCUS_ID, mLocusId.getId());
+ protoOutputStream.end(locusIdToken);
+ }
+ if (mContactUri != null) {
+ protoOutputStream.write(ConversationInfoProto.CONTACT_URI, mContactUri.toString());
+ }
+ if (mNotificationChannelId != null) {
+ protoOutputStream.write(ConversationInfoProto.NOTIFICATION_CHANNEL_ID,
+ mNotificationChannelId);
+ }
+ protoOutputStream.write(ConversationInfoProto.SHORTCUT_FLAGS, mShortcutFlags);
+ protoOutputStream.write(ConversationInfoProto.CONVERSATION_FLAGS, mConversationFlags);
+ }
+
+ /** Reads from {@link ProtoInputStream} and constructs a {@link ConversationInfo}. */
+ @NonNull
+ static ConversationInfo readFromProto(@NonNull ProtoInputStream protoInputStream)
+ throws IOException {
+ ConversationInfo.Builder builder = new ConversationInfo.Builder();
+ while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (protoInputStream.getFieldNumber()) {
+ case (int) ConversationInfoProto.SHORTCUT_ID:
+ builder.setShortcutId(
+ protoInputStream.readString(ConversationInfoProto.SHORTCUT_ID));
+ break;
+ case (int) ConversationInfoProto.LOCUS_ID_PROTO:
+ long locusIdToken = protoInputStream.start(
+ ConversationInfoProto.LOCUS_ID_PROTO);
+ while (protoInputStream.nextField()
+ != ProtoInputStream.NO_MORE_FIELDS) {
+ if (protoInputStream.getFieldNumber() == (int) LocusIdProto.LOCUS_ID) {
+ builder.setLocusId(new LocusId(
+ protoInputStream.readString(LocusIdProto.LOCUS_ID)));
+ }
+ }
+ protoInputStream.end(locusIdToken);
+ break;
+ case (int) ConversationInfoProto.CONTACT_URI:
+ builder.setContactUri(Uri.parse(protoInputStream.readString(
+ ConversationInfoProto.CONTACT_URI)));
+ break;
+ case (int) ConversationInfoProto.NOTIFICATION_CHANNEL_ID:
+ builder.setNotificationChannelId(protoInputStream.readString(
+ ConversationInfoProto.NOTIFICATION_CHANNEL_ID));
+ break;
+ case (int) ConversationInfoProto.SHORTCUT_FLAGS:
+ builder.setShortcutFlags(protoInputStream.readInt(
+ ConversationInfoProto.SHORTCUT_FLAGS));
+ break;
+ case (int) ConversationInfoProto.CONVERSATION_FLAGS:
+ builder.setConversationFlags(protoInputStream.readInt(
+ ConversationInfoProto.CONVERSATION_FLAGS));
+ break;
+ default:
+ Slog.w(TAG, "Could not read undefined field: "
+ + protoInputStream.getFieldNumber());
+ }
+ }
+ return builder.build();
+ }
+
/**
* Builder class for {@link ConversationInfo} objects.
*/
diff --git a/services/people/java/com/android/server/people/data/ConversationStore.java b/services/people/java/com/android/server/people/data/ConversationStore.java
index 3649921..ea36d38 100644
--- a/services/people/java/com/android/server/people/data/ConversationStore.java
+++ b/services/people/java/com/android/server/people/data/ConversationStore.java
@@ -16,58 +16,124 @@
package com.android.server.people.data;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.WorkerThread;
import android.content.LocusId;
import android.net.Uri;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.proto.ProtoInputStream;
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.people.ConversationInfosProto;
+
+import com.google.android.collect.Lists;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer;
-/** The store that stores and accesses the conversations data for a package. */
+/**
+ * The store that stores and accesses the conversations data for a package.
+ */
class ConversationStore {
+ private static final String TAG = ConversationStore.class.getSimpleName();
+
+ private static final String CONVERSATIONS_FILE_NAME = "conversations";
+
+ private static final long DISK_WRITE_DELAY = 2L * DateUtils.MINUTE_IN_MILLIS;
+
// Shortcut ID -> Conversation Info
+ @GuardedBy("this")
private final Map<String, ConversationInfo> mConversationInfoMap = new ArrayMap<>();
// Locus ID -> Shortcut ID
+ @GuardedBy("this")
private final Map<LocusId, String> mLocusIdToShortcutIdMap = new ArrayMap<>();
// Contact URI -> Shortcut ID
+ @GuardedBy("this")
private final Map<Uri, String> mContactUriToShortcutIdMap = new ArrayMap<>();
// Phone Number -> Shortcut ID
+ @GuardedBy("this")
private final Map<String, String> mPhoneNumberToShortcutIdMap = new ArrayMap<>();
// Notification Channel ID -> Shortcut ID
+ @GuardedBy("this")
private final Map<String, String> mNotifChannelIdToShortcutIdMap = new ArrayMap<>();
- void addOrUpdate(@NonNull ConversationInfo conversationInfo) {
- mConversationInfoMap.put(conversationInfo.getShortcutId(), conversationInfo);
+ private final ScheduledExecutorService mScheduledExecutorService;
+ private final File mPackageDir;
+ private final ContactsQueryHelper mHelper;
- LocusId locusId = conversationInfo.getLocusId();
- if (locusId != null) {
- mLocusIdToShortcutIdMap.put(locusId, conversationInfo.getShortcutId());
- }
+ private ConversationInfosProtoDiskReadWriter mConversationInfosProtoDiskReadWriter;
- Uri contactUri = conversationInfo.getContactUri();
- if (contactUri != null) {
- mContactUriToShortcutIdMap.put(contactUri, conversationInfo.getShortcutId());
- }
+ ConversationStore(@NonNull File packageDir,
+ @NonNull ScheduledExecutorService scheduledExecutorService,
+ @NonNull ContactsQueryHelper helper) {
+ mScheduledExecutorService = scheduledExecutorService;
+ mPackageDir = packageDir;
+ mHelper = helper;
+ }
- String phoneNumber = conversationInfo.getContactPhoneNumber();
- if (phoneNumber != null) {
- mPhoneNumberToShortcutIdMap.put(phoneNumber, conversationInfo.getShortcutId());
- }
+ /**
+ * Loads conversations from disk to memory in a background thread. This should be called
+ * after the device powers on and the user has been unlocked.
+ */
+ @MainThread
+ void loadConversationsFromDisk() {
+ mScheduledExecutorService.submit(() -> {
+ synchronized (this) {
+ ConversationInfosProtoDiskReadWriter conversationInfosProtoDiskReadWriter =
+ getConversationInfosProtoDiskReadWriter();
+ if (conversationInfosProtoDiskReadWriter == null) {
+ return;
+ }
+ List<ConversationInfo> conversationsOnDisk =
+ conversationInfosProtoDiskReadWriter.read(CONVERSATIONS_FILE_NAME);
+ if (conversationsOnDisk == null) {
+ return;
+ }
+ for (ConversationInfo conversationInfo : conversationsOnDisk) {
+ conversationInfo = restoreConversationPhoneNumber(conversationInfo);
+ updateConversationsInMemory(conversationInfo);
+ }
+ }
+ });
+ }
- String notifChannelId = conversationInfo.getNotificationChannelId();
- if (notifChannelId != null) {
- mNotifChannelIdToShortcutIdMap.put(notifChannelId, conversationInfo.getShortcutId());
+ /**
+ * Immediately flushes current conversations to disk. This should be called when device is
+ * powering off.
+ */
+ @MainThread
+ synchronized void saveConversationsToDisk() {
+ ConversationInfosProtoDiskReadWriter conversationInfosProtoDiskReadWriter =
+ getConversationInfosProtoDiskReadWriter();
+ if (conversationInfosProtoDiskReadWriter != null) {
+ conversationInfosProtoDiskReadWriter.saveConversationsImmediately(
+ new ArrayList<>(mConversationInfoMap.values()));
}
}
- void deleteConversation(@NonNull String shortcutId) {
+ @MainThread
+ synchronized void addOrUpdate(@NonNull ConversationInfo conversationInfo) {
+ updateConversationsInMemory(conversationInfo);
+ scheduleUpdateConversationsOnDisk();
+ }
+
+ @MainThread
+ synchronized void deleteConversation(@NonNull String shortcutId) {
ConversationInfo conversationInfo = mConversationInfoMap.remove(shortcutId);
if (conversationInfo == null) {
return;
@@ -92,31 +158,32 @@
if (notifChannelId != null) {
mNotifChannelIdToShortcutIdMap.remove(notifChannelId);
}
+ scheduleUpdateConversationsOnDisk();
}
- void forAllConversations(@NonNull Consumer<ConversationInfo> consumer) {
+ synchronized void forAllConversations(@NonNull Consumer<ConversationInfo> consumer) {
for (ConversationInfo ci : mConversationInfoMap.values()) {
consumer.accept(ci);
}
}
@Nullable
- ConversationInfo getConversation(@Nullable String shortcutId) {
+ synchronized ConversationInfo getConversation(@Nullable String shortcutId) {
return shortcutId != null ? mConversationInfoMap.get(shortcutId) : null;
}
@Nullable
- ConversationInfo getConversationByLocusId(@NonNull LocusId locusId) {
+ synchronized ConversationInfo getConversationByLocusId(@NonNull LocusId locusId) {
return getConversation(mLocusIdToShortcutIdMap.get(locusId));
}
@Nullable
- ConversationInfo getConversationByContactUri(@NonNull Uri contactUri) {
+ synchronized ConversationInfo getConversationByContactUri(@NonNull Uri contactUri) {
return getConversation(mContactUriToShortcutIdMap.get(contactUri));
}
@Nullable
- ConversationInfo getConversationByPhoneNumber(@NonNull String phoneNumber) {
+ synchronized ConversationInfo getConversationByPhoneNumber(@NonNull String phoneNumber) {
return getConversation(mPhoneNumberToShortcutIdMap.get(phoneNumber));
}
@@ -124,4 +191,140 @@
ConversationInfo getConversationByNotificationChannelId(@NonNull String notifChannelId) {
return getConversation(mNotifChannelIdToShortcutIdMap.get(notifChannelId));
}
+
+ @MainThread
+ private synchronized void updateConversationsInMemory(
+ @NonNull ConversationInfo conversationInfo) {
+ mConversationInfoMap.put(conversationInfo.getShortcutId(), conversationInfo);
+
+ LocusId locusId = conversationInfo.getLocusId();
+ if (locusId != null) {
+ mLocusIdToShortcutIdMap.put(locusId, conversationInfo.getShortcutId());
+ }
+
+ Uri contactUri = conversationInfo.getContactUri();
+ if (contactUri != null) {
+ mContactUriToShortcutIdMap.put(contactUri, conversationInfo.getShortcutId());
+ }
+
+ String phoneNumber = conversationInfo.getContactPhoneNumber();
+ if (phoneNumber != null) {
+ mPhoneNumberToShortcutIdMap.put(phoneNumber, conversationInfo.getShortcutId());
+ }
+
+ String notifChannelId = conversationInfo.getNotificationChannelId();
+ if (notifChannelId != null) {
+ mNotifChannelIdToShortcutIdMap.put(notifChannelId, conversationInfo.getShortcutId());
+ }
+ }
+
+ /** Schedules a dump of all conversations onto disk, overwriting existing values. */
+ @MainThread
+ private synchronized void scheduleUpdateConversationsOnDisk() {
+ ConversationInfosProtoDiskReadWriter conversationInfosProtoDiskReadWriter =
+ getConversationInfosProtoDiskReadWriter();
+ if (conversationInfosProtoDiskReadWriter != null) {
+ conversationInfosProtoDiskReadWriter.scheduleConversationsSave(
+ new ArrayList<>(mConversationInfoMap.values()));
+ }
+ }
+
+ @Nullable
+ private ConversationInfosProtoDiskReadWriter getConversationInfosProtoDiskReadWriter() {
+ if (!mPackageDir.exists()) {
+ Slog.e(TAG, "Package data directory does not exist: " + mPackageDir.getAbsolutePath());
+ return null;
+ }
+ if (mConversationInfosProtoDiskReadWriter == null) {
+ mConversationInfosProtoDiskReadWriter = new ConversationInfosProtoDiskReadWriter(
+ mPackageDir, CONVERSATIONS_FILE_NAME, DISK_WRITE_DELAY,
+ mScheduledExecutorService);
+ }
+ return mConversationInfosProtoDiskReadWriter;
+ }
+
+ /**
+ * Conversation's phone number is not saved on disk, so it has to be fetched.
+ */
+ @WorkerThread
+ private ConversationInfo restoreConversationPhoneNumber(
+ @NonNull ConversationInfo conversationInfo) {
+ if (conversationInfo.getContactUri() != null) {
+ if (mHelper.query(conversationInfo.getContactUri().toString())) {
+ String phoneNumber = mHelper.getPhoneNumber();
+ if (!TextUtils.isEmpty(phoneNumber)) {
+ conversationInfo = new ConversationInfo.Builder(
+ conversationInfo).setContactPhoneNumber(
+ phoneNumber).build();
+ }
+ }
+ }
+ return conversationInfo;
+ }
+
+ /** Reads and writes {@link ConversationInfo} on disk. */
+ static class ConversationInfosProtoDiskReadWriter extends
+ AbstractProtoDiskReadWriter<List<ConversationInfo>> {
+
+ private final String mConversationInfoFileName;
+
+ ConversationInfosProtoDiskReadWriter(@NonNull File baseDir,
+ @NonNull String conversationInfoFileName,
+ long writeDelayMs, @NonNull ScheduledExecutorService scheduledExecutorService) {
+ super(baseDir, writeDelayMs, scheduledExecutorService);
+ mConversationInfoFileName = conversationInfoFileName;
+ }
+
+ @Override
+ ProtoStreamWriter<List<ConversationInfo>> protoStreamWriter() {
+ return (protoOutputStream, data) -> {
+ for (ConversationInfo conversationInfo : data) {
+ long token = protoOutputStream.start(ConversationInfosProto.CONVERSATION_INFOS);
+ conversationInfo.writeToProto(protoOutputStream);
+ protoOutputStream.end(token);
+ }
+ };
+ }
+
+ @Override
+ ProtoStreamReader<List<ConversationInfo>> protoStreamReader() {
+ return protoInputStream -> {
+ List<ConversationInfo> results = Lists.newArrayList();
+ try {
+ while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (protoInputStream.getFieldNumber()
+ != (int) ConversationInfosProto.CONVERSATION_INFOS) {
+ continue;
+ }
+ long token = protoInputStream.start(
+ ConversationInfosProto.CONVERSATION_INFOS);
+ ConversationInfo conversationInfo = ConversationInfo.readFromProto(
+ protoInputStream);
+ protoInputStream.end(token);
+ results.add(conversationInfo);
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to read protobuf input stream.", e);
+ }
+ return results;
+ };
+ }
+
+ /**
+ * Schedules a flush of the specified conversations to disk.
+ */
+ @MainThread
+ void scheduleConversationsSave(@NonNull List<ConversationInfo> conversationInfos) {
+ scheduleSave(mConversationInfoFileName, conversationInfos);
+ }
+
+ /**
+ * Saves the specified conversations immediately. This should be used when device is
+ * powering off.
+ */
+ @MainThread
+ void saveConversationsImmediately(@NonNull List<ConversationInfo> conversationInfos) {
+ saveImmediately(mConversationInfoFileName, conversationInfos);
+ }
+ }
}
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 7a3ed53..c8673f8 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -86,6 +86,7 @@
private final Context mContext;
private final Injector mInjector;
private final ScheduledExecutorService mUsageStatsQueryExecutor;
+ private final ScheduledExecutorService mDiskReadWriterExecutor;
private final SparseArray<UserData> mUserDataArray = new SparseArray<>();
private final SparseArray<BroadcastReceiver> mBroadcastReceivers = new SparseArray<>();
@@ -113,6 +114,7 @@
BackgroundThread.getHandler());
mMmsSmsContentObserver = new MmsSmsContentObserver(
BackgroundThread.getHandler());
+ mDiskReadWriterExecutor = mInjector.createScheduledExecutor();
}
/** Initialization. Called when the system services are up running. */
@@ -122,13 +124,18 @@
mUserManager = mContext.getSystemService(UserManager.class);
mShortcutServiceInternal.addListener(new ShortcutServiceListener());
+
+ IntentFilter shutdownIntentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
+ BroadcastReceiver shutdownBroadcastReceiver = new ShutdownBroadcastReceiver();
+ mContext.registerReceiver(shutdownBroadcastReceiver, shutdownIntentFilter);
}
/** This method is called when a user is unlocked. */
public void onUserUnlocked(int userId) {
UserData userData = mUserDataArray.get(userId);
if (userData == null) {
- userData = new UserData(userId);
+ userData = new UserData(userId, mDiskReadWriterExecutor,
+ mInjector.createContactsQueryHelper(mContext));
mUserDataArray.put(userId, userData);
}
userData.setUserUnlocked();
@@ -662,6 +669,14 @@
}
}
+ private class ShutdownBroadcastReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ forAllPackages(PackageData::saveToDisk);
+ }
+ }
+
@VisibleForTesting
static class Injector {
diff --git a/services/people/java/com/android/server/people/data/PackageData.java b/services/people/java/com/android/server/people/data/PackageData.java
index 75b870c..f67699c 100644
--- a/services/people/java/com/android/server/people/data/PackageData.java
+++ b/services/people/java/com/android/server/people/data/PackageData.java
@@ -22,6 +22,8 @@
import android.content.LocusId;
import android.text.TextUtils;
+import java.io.File;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -43,17 +45,36 @@
private final Predicate<String> mIsDefaultSmsAppPredicate;
+ private final File mPackageDataDir;
+
PackageData(@NonNull String packageName, @UserIdInt int userId,
@NonNull Predicate<String> isDefaultDialerPredicate,
- @NonNull Predicate<String> isDefaultSmsAppPredicate) {
+ @NonNull Predicate<String> isDefaultSmsAppPredicate,
+ @NonNull ScheduledExecutorService scheduledExecutorService,
+ @NonNull File perUserPeopleDataDir,
+ @NonNull ContactsQueryHelper helper) {
mPackageName = packageName;
mUserId = userId;
- mConversationStore = new ConversationStore();
+
+ mPackageDataDir = new File(perUserPeopleDataDir, mPackageName);
+ mConversationStore = new ConversationStore(mPackageDataDir, scheduledExecutorService,
+ helper);
mEventStore = new EventStore();
mIsDefaultDialerPredicate = isDefaultDialerPredicate;
mIsDefaultSmsAppPredicate = isDefaultSmsAppPredicate;
}
+ /** Called when user is unlocked. */
+ void loadFromDisk() {
+ mPackageDataDir.mkdirs();
+ mConversationStore.loadConversationsFromDisk();
+ }
+
+ /** Called when device is shutting down. */
+ void saveToDisk() {
+ mConversationStore.saveConversationsToDisk();
+ }
+
@NonNull
public String getPackageName() {
return mPackageName;
diff --git a/services/people/java/com/android/server/people/data/UserData.java b/services/people/java/com/android/server/people/data/UserData.java
index 4e8fd16..aaa5db8 100644
--- a/services/people/java/com/android/server/people/data/UserData.java
+++ b/services/people/java/com/android/server/people/data/UserData.java
@@ -19,10 +19,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.os.Environment;
import android.text.TextUtils;
import android.util.ArrayMap;
+import java.io.File;
import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer;
/** The data associated with a user profile. */
@@ -30,6 +33,12 @@
private final @UserIdInt int mUserId;
+ private final File mPerUserPeopleDataDir;
+
+ private final ScheduledExecutorService mScheduledExecutorService;
+
+ private final ContactsQueryHelper mHelper;
+
private boolean mIsUnlocked;
private Map<String, PackageData> mPackageDataMap = new ArrayMap<>();
@@ -40,8 +49,12 @@
@Nullable
private String mDefaultSmsApp;
- UserData(@UserIdInt int userId) {
+ UserData(@UserIdInt int userId, @NonNull ScheduledExecutorService scheduledExecutorService,
+ ContactsQueryHelper helper) {
mUserId = userId;
+ mPerUserPeopleDataDir = new File(Environment.getDataSystemCeDirectory(mUserId), "people");
+ mScheduledExecutorService = scheduledExecutorService;
+ mHelper = helper;
}
@UserIdInt int getUserId() {
@@ -56,6 +69,13 @@
void setUserUnlocked() {
mIsUnlocked = true;
+
+ // Ensures per user root directory for people data is present, and attempt to load
+ // data from disk.
+ mPerUserPeopleDataDir.mkdirs();
+ for (PackageData packageData : mPackageDataMap.values()) {
+ packageData.loadFromDisk();
+ }
}
void setUserStopped() {
@@ -103,7 +123,8 @@
}
private PackageData createPackageData(String packageName) {
- return new PackageData(packageName, mUserId, this::isDefaultDialer, this::isDefaultSmsApp);
+ return new PackageData(packageName, mUserId, this::isDefaultDialer, this::isDefaultSmsApp,
+ mScheduledExecutorService, mPerUserPeopleDataDir, mHelper);
}
private boolean isDefaultDialer(String packageName) {
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java
index 223a98b..8daef5f 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java
@@ -67,8 +67,7 @@
int pmToken,
boolean isFullSystemRestore,
@Nullable String[] filterSet,
- OnTaskFinishedListener listener,
- Map<String, Set<String>> excludedKeys) {
+ OnTaskFinishedListener listener) {
mBackupManagerService = backupManagerService;
mPackage = targetPackage;
mIsFullSystemRestore = isFullSystemRestore;
diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java
index d6efe35..5800aca 100644
--- a/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java
@@ -16,8 +16,6 @@
package com.android.server.backup;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import androidx.test.InstrumentationRegistry;
@@ -33,18 +31,15 @@
import org.mockito.MockitoAnnotations;
import java.util.Arrays;
-import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Set;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class UserBackupPreferencesTest {
private static final String EXCLUDED_PACKAGE_1 = "package1";
- private static final String EXCLUDED_PACKAGE_2 = "package2";
private static final List<String> EXCLUDED_KEYS_1 = Arrays.asList("key1", "key2");
- private static final List<String> EXCLUDED_KEYS_2 = Arrays.asList("key1");
+ private static final List<String> EXCLUDED_KEYS_2 = Arrays.asList("key3");
@Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
@@ -60,27 +55,13 @@
}
@Test
- public void testGetExcludedKeysForPackages_returnsExcludedKeys() {
+ public void testGetExcludedKeysForPackage_returnsExcludedKeys() {
mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_1, EXCLUDED_KEYS_1);
- mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_2, EXCLUDED_KEYS_2);
+ mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_1, EXCLUDED_KEYS_2);
- Map<String, Set<String>> excludedKeys =
- mExcludedRestoreKeysStorage.getExcludedRestoreKeysForPackages(EXCLUDED_PACKAGE_1);
- assertTrue(excludedKeys.containsKey(EXCLUDED_PACKAGE_1));
- assertFalse(excludedKeys.containsKey(EXCLUDED_PACKAGE_2));
- assertEquals(new HashSet<>(EXCLUDED_KEYS_1), excludedKeys.get(EXCLUDED_PACKAGE_1));
- }
-
- @Test
- public void testGetExcludedKeysForPackages_withEmpty_list_returnsAllExcludedKeys() {
- mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_1, EXCLUDED_KEYS_1);
- mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_2, EXCLUDED_KEYS_2);
-
- Map<String, Set<String>> excludedKeys =
- mExcludedRestoreKeysStorage.getAllExcludedRestoreKeys();
- assertTrue(excludedKeys.containsKey(EXCLUDED_PACKAGE_1));
- assertTrue(excludedKeys.containsKey(EXCLUDED_PACKAGE_2));
- assertEquals(new HashSet<>(EXCLUDED_KEYS_1), excludedKeys.get(EXCLUDED_PACKAGE_1));
- assertEquals(new HashSet<>(EXCLUDED_KEYS_2), excludedKeys.get(EXCLUDED_PACKAGE_2));
+ Set<String> excludedKeys =
+ mExcludedRestoreKeysStorage.getExcludedRestoreKeysForPackage(EXCLUDED_PACKAGE_1);
+ assertTrue(excludedKeys.containsAll(EXCLUDED_KEYS_1));
+ assertTrue(excludedKeys.containsAll(EXCLUDED_KEYS_2));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
index 3d22043..017c939 100644
--- a/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
@@ -20,7 +20,9 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import androidx.test.InstrumentationRegistry;
@@ -30,6 +32,8 @@
import android.app.backup.BackupDataOutput;
import android.platform.test.annotations.Presubmit;
+import com.android.server.backup.UserBackupManagerService;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -40,6 +44,7 @@
import org.mockito.stubbing.Answer;
import java.util.ArrayDeque;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -59,6 +64,7 @@
@Mock private BackupDataInput mBackupDataInput;
@Mock private BackupDataOutput mBackupDataOutput;
+ @Mock private UserBackupManagerService mBackupManagerService;
private Set<String> mExcludedkeys = new HashSet<>();
private Map<String, String> mBackupData = new HashMap<>();
@@ -99,6 +105,8 @@
return null;
}
});
+
+ mRestoreTask = new PerformUnifiedRestoreTask(mBackupManagerService);
}
private void populateTestData() {
@@ -114,8 +122,9 @@
@Test
public void testFilterExcludedKeys() throws Exception {
- mRestoreTask = new PerformUnifiedRestoreTask(Collections.singletonMap(
- PACKAGE_NAME, mExcludedkeys));
+ when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME))).thenReturn(
+ mExcludedkeys);
+
mRestoreTask.filterExcludedKeys(PACKAGE_NAME, mBackupDataInput, mBackupDataOutput);
// Verify only the correct were written into BackupDataOutput object.
@@ -125,32 +134,49 @@
}
@Test
+ public void testGetExcludedKeysForPackage_alwaysReturnsLatestKeys() {
+ Set<String> firstExcludedKeys = new HashSet<>(Collections.singletonList(EXCLUDED_KEY_1));
+ when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME))).thenReturn(
+ firstExcludedKeys);
+ assertEquals(firstExcludedKeys, mRestoreTask.getExcludedKeysForPackage(PACKAGE_NAME));
+
+
+ Set<String> secondExcludedKeys = new HashSet<>(Arrays.asList(EXCLUDED_KEY_1,
+ EXCLUDED_KEY_2));
+ when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME))).thenReturn(
+ secondExcludedKeys);
+ assertEquals(secondExcludedKeys, mRestoreTask.getExcludedKeysForPackage(PACKAGE_NAME));
+ }
+
+ @Test
public void testStageBackupData_stageForNonSystemPackageWithKeysToExclude() {
- mRestoreTask = new PerformUnifiedRestoreTask(Collections.singletonMap(
- PACKAGE_NAME, mExcludedkeys));
+ when(mBackupManagerService.getExcludedRestoreKeys(eq(NON_SYSTEM_PACKAGE_NAME))).thenReturn(
+ mExcludedkeys);
assertTrue(mRestoreTask.shouldStageBackupData(NON_SYSTEM_PACKAGE_NAME));
}
@Test
public void testStageBackupData_stageForNonSystemPackageWithNoKeysToExclude() {
- mRestoreTask = new PerformUnifiedRestoreTask(Collections.emptyMap());
+ when(mBackupManagerService.getExcludedRestoreKeys(any())).thenReturn(
+ Collections.emptySet());
assertTrue(mRestoreTask.shouldStageBackupData(NON_SYSTEM_PACKAGE_NAME));
}
@Test
public void testStageBackupData_doNotStageForSystemPackageWithNoKeysToExclude() {
- mRestoreTask = new PerformUnifiedRestoreTask(Collections.emptyMap());
+ when(mBackupManagerService.getExcludedRestoreKeys(any())).thenReturn(
+ Collections.emptySet());
assertFalse(mRestoreTask.shouldStageBackupData(SYSTEM_PACKAGE_NAME));
}
@Test
public void testStageBackupData_stageForSystemPackageWithKeysToExclude() {
- mRestoreTask = new PerformUnifiedRestoreTask(Collections.singletonMap(
- PACKAGE_NAME, mExcludedkeys));
+ when(mBackupManagerService.getExcludedRestoreKeys(eq(SYSTEM_PACKAGE_NAME))).thenReturn(
+ mExcludedkeys);
- assertTrue(mRestoreTask.shouldStageBackupData(NON_SYSTEM_PACKAGE_NAME));
+ assertTrue(mRestoreTask.shouldStageBackupData(SYSTEM_PACKAGE_NAME));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 156cd6e..e5adb80 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -283,7 +283,7 @@
null /* authenticators */);
waitForIdle();
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_NONE),
+ eq(BiometricAuthenticator.TYPE_FINGERPRINT),
eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE),
eq(0 /* vendorCode */));
}
@@ -1117,14 +1117,14 @@
// STRONG-only auth is not available
int authenticators = Authenticators.BIOMETRIC_STRONG;
- assertEquals(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE,
+ assertEquals(BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
invokeCanAuthenticate(mBiometricService, authenticators));
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
authenticators);
waitForIdle();
verify(mReceiver1).onError(
- eq(BiometricAuthenticator.TYPE_NONE),
- eq(BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT),
+ eq(BiometricAuthenticator.TYPE_FINGERPRINT),
+ eq(BiometricPrompt.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED),
eq(0) /* vendorCode */);
// Request for weak auth works
@@ -1154,7 +1154,7 @@
false /* requireConfirmation */,
authenticators);
waitForIdle();
- assertTrue(Utils.isDeviceCredentialAllowed(mBiometricService.mCurrentAuthSession.mBundle));
+ assertTrue(Utils.isCredentialRequested(mBiometricService.mCurrentAuthSession.mBundle));
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
eq(mBiometricService.mCurrentAuthSession.mBundle),
any(IBiometricServiceReceiverInternal.class),
@@ -1162,6 +1162,28 @@
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
eq(TEST_PACKAGE_NAME));
+
+ // Un-downgrading the authenticator allows successful strong auth
+ for (BiometricService.AuthenticatorWrapper wrapper : mBiometricService.mAuthenticators) {
+ if (wrapper.id == testId) {
+ wrapper.updateStrength(Authenticators.BIOMETRIC_STRONG);
+ }
+ }
+
+ resetReceiver();
+ authenticators = Authenticators.BIOMETRIC_STRONG;
+ assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
+ invokeCanAuthenticate(mBiometricService, authenticators));
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */, authenticators);
+ waitForIdle();
+ verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
+ eq(mBiometricService.mCurrentAuthSession.mBundle),
+ any(IBiometricServiceReceiverInternal.class),
+ eq(BiometricAuthenticator.TYPE_FINGERPRINT /* biometricModality */),
+ anyBoolean() /* requireConfirmation */,
+ anyInt() /* userId */,
+ eq(TEST_PACKAGE_NAME));
}
@Test(expected = IllegalStateException.class)
@@ -1245,6 +1267,19 @@
}
@Test
+ public void testAuthentication_normalAppIgnoresDevicePolicy() throws Exception {
+ setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+ when(mDevicePolicyManager
+ .getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */))
+ .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
+ invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */, Authenticators.BIOMETRIC_STRONG);
+ waitForIdle();
+ assertEquals(mBiometricService.mCurrentAuthSession.mState,
+ BiometricService.STATE_AUTH_STARTED);
+ }
+
+ @Test
public void testWorkAuthentication_faceWorksIfNotDisabledByDevicePolicyManager()
throws Exception {
setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
index 312ff2c..df242f3 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
@@ -91,31 +91,31 @@
@Test
public void testIsDeviceCredentialAllowed_withIntegerFlags() {
int authenticators = 0;
- assertFalse(Utils.isDeviceCredentialAllowed(authenticators));
+ assertFalse(Utils.isCredentialRequested(authenticators));
authenticators |= Authenticators.DEVICE_CREDENTIAL;
- assertTrue(Utils.isDeviceCredentialAllowed(authenticators));
+ assertTrue(Utils.isCredentialRequested(authenticators));
authenticators |= Authenticators.BIOMETRIC_WEAK;
- assertTrue(Utils.isDeviceCredentialAllowed(authenticators));
+ assertTrue(Utils.isCredentialRequested(authenticators));
}
@Test
public void testIsDeviceCredentialAllowed_withBundle() {
Bundle bundle = new Bundle();
- assertFalse(Utils.isDeviceCredentialAllowed(bundle));
+ assertFalse(Utils.isCredentialRequested(bundle));
int authenticators = 0;
bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
- assertFalse(Utils.isDeviceCredentialAllowed(bundle));
+ assertFalse(Utils.isCredentialRequested(bundle));
authenticators |= Authenticators.DEVICE_CREDENTIAL;
bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
- assertTrue(Utils.isDeviceCredentialAllowed(bundle));
+ assertTrue(Utils.isCredentialRequested(bundle));
authenticators |= Authenticators.BIOMETRIC_WEAK;
bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
- assertTrue(Utils.isDeviceCredentialAllowed(bundle));
+ assertTrue(Utils.isCredentialRequested(bundle));
}
@Test
@@ -140,14 +140,14 @@
for (int i = 0; i <= 7; i++) {
int authenticators = 1 << i;
bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
- assertTrue(Utils.isBiometricAllowed(bundle));
+ assertTrue(Utils.isBiometricRequested(bundle));
}
// The rest of the bits are not allowed to integrate with the public APIs
for (int i = 8; i < 32; i++) {
int authenticators = 1 << i;
bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators);
- assertFalse(Utils.isBiometricAllowed(bundle));
+ assertFalse(Utils.isBiometricRequested(bundle));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java
index f16cf35..4ba584e 100644
--- a/services/tests/servicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java
@@ -39,6 +39,7 @@
import android.location.GnssMeasurementCorrections;
import android.location.GnssMeasurementsEvent;
import android.location.GnssNavigationMessage;
+import android.location.GnssRequest;
import android.location.GnssSingleSatCorrection;
import android.location.IBatchedLocationCallback;
import android.location.IGnssMeasurementsListener;
@@ -563,7 +564,7 @@
assertThrows(SecurityException.class,
() -> mGnssManagerService.addGnssMeasurementsListener(
- mockGnssMeasurementsListener,
+ new GnssRequest.Builder().build(), mockGnssMeasurementsListener,
"com.android.server", "abcd123", "TestGnssMeasurementsListener"));
mTestGnssMeasurementsProvider.onMeasurementsAvailable(gnssMeasurementsEvent);
@@ -580,8 +581,11 @@
enableLocationPermissions();
- assertThat(mGnssManagerService.addGnssMeasurementsListener(mockGnssMeasurementsListener,
- "com.android.server", "abcd123", "TestGnssMeasurementsListener")).isEqualTo(true);
+ assertThat(mGnssManagerService.addGnssMeasurementsListener(
+ new GnssRequest.Builder().build(),
+ mockGnssMeasurementsListener,
+ "com.android.server", "abcd123",
+ "TestGnssMeasurementsListener")).isEqualTo(true);
mTestGnssMeasurementsProvider.onMeasurementsAvailable(gnssMeasurementsEvent);
verify(mockGnssMeasurementsListener, times(1)).onGnssMeasurementsReceived(
@@ -626,8 +630,10 @@
enableLocationPermissions();
- mGnssManagerService.addGnssMeasurementsListener(mockGnssMeasurementsListener,
- "com.android.server", "abcd123", "TestGnssMeasurementsListener");
+ mGnssManagerService.addGnssMeasurementsListener(new GnssRequest.Builder().build(),
+ mockGnssMeasurementsListener,
+ "com.android.server", "abcd123",
+ "TestGnssMeasurementsListener");
disableLocationPermissions();
@@ -648,8 +654,10 @@
enableLocationPermissions();
- mGnssManagerService.addGnssMeasurementsListener(mockGnssMeasurementsListener,
- "com.android.server", "abcd123", "TestGnssMeasurementsListener");
+ mGnssManagerService.addGnssMeasurementsListener(new GnssRequest.Builder().build(),
+ mockGnssMeasurementsListener,
+ "com.android.server", "abcd123",
+ "TestGnssMeasurementsListener");
disableLocationPermissions();
diff --git a/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java b/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java
index 331ad59..03b5e38 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java
@@ -21,16 +21,24 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import android.annotation.Nullable;
+import android.content.Context;
import android.content.LocusId;
import android.content.pm.ShortcutInfo;
import android.net.Uri;
+import android.os.FileUtils;
+import android.text.format.DateUtils;
import android.util.ArraySet;
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.io.File;
import java.util.Set;
@RunWith(JUnit4.class)
@@ -42,11 +50,34 @@
private static final Uri CONTACT_URI = Uri.parse("tel:+1234567890");
private static final String PHONE_NUMBER = "+1234567890";
+ private static final String SHORTCUT_ID_2 = "ghi";
+ private static final String NOTIFICATION_CHANNEL_ID_2 = "test : ghi";
+ private static final LocusId LOCUS_ID_2 = new LocusId("jkl");
+ private static final Uri CONTACT_URI_2 = Uri.parse("tel:+3234567890");
+ private static final String PHONE_NUMBER_2 = "+3234567890";
+
+ private static final String SHORTCUT_ID_3 = "mno";
+ private static final String NOTIFICATION_CHANNEL_ID_3 = "test : mno";
+ private static final LocusId LOCUS_ID_3 = new LocusId("pqr");
+ private static final Uri CONTACT_URI_3 = Uri.parse("tel:+9234567890");
+ private static final String PHONE_NUMBER_3 = "+9234567890";
+
+ private MockScheduledExecutorService mMockScheduledExecutorService;
+ private TestContactQueryHelper mTestContactQueryHelper;
private ConversationStore mConversationStore;
+ private File mFile;
@Before
public void setUp() {
- mConversationStore = new ConversationStore();
+ Context ctx = InstrumentationRegistry.getContext();
+ mFile = new File(ctx.getCacheDir(), "testdir");
+ mTestContactQueryHelper = new TestContactQueryHelper(ctx);
+ resetConversationStore();
+ }
+
+ @After
+ public void tearDown() {
+ FileUtils.deleteContentsAndDir(mFile);
}
@Test
@@ -153,6 +184,130 @@
mConversationStore.getConversationByNotificationChannelId(NOTIFICATION_CHANNEL_ID));
}
+ @Test
+ public void testDataPersistenceAndRestoration() {
+ // Add conversation infos, causing it to be loaded to disk.
+ ConversationInfo in1 = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI,
+ PHONE_NUMBER, NOTIFICATION_CHANNEL_ID);
+ ConversationInfo in2 = buildConversationInfo(SHORTCUT_ID_2, LOCUS_ID_2, CONTACT_URI_2,
+ PHONE_NUMBER_2, NOTIFICATION_CHANNEL_ID_2);
+ ConversationInfo in3 = buildConversationInfo(SHORTCUT_ID_3, LOCUS_ID_3, CONTACT_URI_3,
+ PHONE_NUMBER_3, NOTIFICATION_CHANNEL_ID_3);
+ mConversationStore.addOrUpdate(in1);
+ mConversationStore.addOrUpdate(in2);
+ mConversationStore.addOrUpdate(in3);
+
+ long futuresExecuted = mMockScheduledExecutorService.fastForwardTime(
+ 3L * DateUtils.MINUTE_IN_MILLIS);
+ assertEquals(1, futuresExecuted);
+
+ mMockScheduledExecutorService.resetTimeElapsedMillis();
+
+ // During restoration, we want to confirm that this conversation was removed.
+ mConversationStore.deleteConversation(SHORTCUT_ID_3);
+ mMockScheduledExecutorService.fastForwardTime(3L * DateUtils.MINUTE_IN_MILLIS);
+
+ mTestContactQueryHelper.setQueryResult(true, true);
+ mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER, PHONE_NUMBER_2);
+
+ resetConversationStore();
+ ConversationInfo out1 = mConversationStore.getConversation(SHORTCUT_ID);
+ ConversationInfo out2 = mConversationStore.getConversation(SHORTCUT_ID_2);
+ ConversationInfo out3 = mConversationStore.getConversation(SHORTCUT_ID_3);
+ mConversationStore.deleteConversation(SHORTCUT_ID);
+ mConversationStore.deleteConversation(SHORTCUT_ID_2);
+ mConversationStore.deleteConversation(SHORTCUT_ID_3);
+ assertEquals(in1, out1);
+ assertEquals(in2, out2);
+ assertNull(out3);
+ }
+
+ @Test
+ public void testDelayedDiskWrites() {
+ ConversationInfo in1 = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI,
+ PHONE_NUMBER, NOTIFICATION_CHANNEL_ID);
+ ConversationInfo in2 = buildConversationInfo(SHORTCUT_ID_2, LOCUS_ID_2, CONTACT_URI_2,
+ PHONE_NUMBER_2, NOTIFICATION_CHANNEL_ID_2);
+ ConversationInfo in3 = buildConversationInfo(SHORTCUT_ID_3, LOCUS_ID_3, CONTACT_URI_3,
+ PHONE_NUMBER_3, NOTIFICATION_CHANNEL_ID_3);
+
+ mConversationStore.addOrUpdate(in1);
+ mMockScheduledExecutorService.fastForwardTime(3L * DateUtils.MINUTE_IN_MILLIS);
+ mMockScheduledExecutorService.resetTimeElapsedMillis();
+
+ // Should not see second conversation on disk because of disk write delay has not been
+ // reached.
+ mConversationStore.addOrUpdate(in2);
+ mMockScheduledExecutorService.fastForwardTime(DateUtils.MINUTE_IN_MILLIS);
+
+ mTestContactQueryHelper.setQueryResult(true);
+ mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER);
+
+ resetConversationStore();
+ ConversationInfo out1 = mConversationStore.getConversation(SHORTCUT_ID);
+ ConversationInfo out2 = mConversationStore.getConversation(SHORTCUT_ID_2);
+ assertEquals(in1, out1);
+ assertNull(out2);
+
+ mConversationStore.addOrUpdate(in2);
+ mMockScheduledExecutorService.fastForwardTime(3L * DateUtils.MINUTE_IN_MILLIS);
+ mMockScheduledExecutorService.resetTimeElapsedMillis();
+
+ mConversationStore.addOrUpdate(in3);
+ mMockScheduledExecutorService.fastForwardTime(3L * DateUtils.MINUTE_IN_MILLIS);
+
+ mTestContactQueryHelper.reset();
+ mTestContactQueryHelper.setQueryResult(true, true, true);
+ mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER, PHONE_NUMBER_2, PHONE_NUMBER_3);
+
+ resetConversationStore();
+ out1 = mConversationStore.getConversation(SHORTCUT_ID);
+ out2 = mConversationStore.getConversation(SHORTCUT_ID_2);
+ ConversationInfo out3 = mConversationStore.getConversation(SHORTCUT_ID_3);
+ assertEquals(in1, out1);
+ assertEquals(in2, out2);
+ assertEquals(in3, out3);
+ }
+
+ @Test
+ public void testMimicDevicePowerOff() {
+
+ // Even without fast forwarding time with our mock ScheduledExecutorService, we should
+ // see the conversations immediately saved to disk.
+ ConversationInfo in1 = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI,
+ PHONE_NUMBER, NOTIFICATION_CHANNEL_ID);
+ ConversationInfo in2 = buildConversationInfo(SHORTCUT_ID_2, LOCUS_ID_2, CONTACT_URI_2,
+ PHONE_NUMBER_2, NOTIFICATION_CHANNEL_ID_2);
+
+ mConversationStore.addOrUpdate(in1);
+ mConversationStore.addOrUpdate(in2);
+ mConversationStore.saveConversationsToDisk();
+
+ // Ensure that futures were cancelled and the immediate flush occurred.
+ assertEquals(0, mMockScheduledExecutorService.getFutures().size());
+
+ // Expect to see 2 executes: loadConversationFromDisk and saveConversationsToDisk.
+ // loadConversationFromDisk gets called each time we call #resetConversationStore().
+ assertEquals(2, mMockScheduledExecutorService.getExecutes().size());
+
+ mTestContactQueryHelper.setQueryResult(true, true);
+ mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER, PHONE_NUMBER_2);
+
+ resetConversationStore();
+ ConversationInfo out1 = mConversationStore.getConversation(SHORTCUT_ID);
+ ConversationInfo out2 = mConversationStore.getConversation(SHORTCUT_ID_2);
+ assertEquals(in1, out1);
+ assertEquals(in2, out2);
+ }
+
+ private void resetConversationStore() {
+ mFile.mkdir();
+ mMockScheduledExecutorService = new MockScheduledExecutorService();
+ mConversationStore = new ConversationStore(mFile, mMockScheduledExecutorService,
+ mTestContactQueryHelper);
+ mConversationStore.loadConversationsFromDisk();
+ }
+
private static ConversationInfo buildConversationInfo(String shortcutId) {
return buildConversationInfo(shortcutId, null, null, null, null);
}
@@ -171,4 +326,54 @@
.setBubbled(true)
.build();
}
+
+ private static class TestContactQueryHelper extends ContactsQueryHelper {
+
+ private int mQueryCalls;
+ private boolean[] mQueryResult;
+
+ private int mPhoneNumberCalls;
+ private String[] mPhoneNumberResult;
+
+ TestContactQueryHelper(Context context) {
+ super(context);
+
+ mQueryCalls = 0;
+ mPhoneNumberCalls = 0;
+ }
+
+ private void setQueryResult(boolean... values) {
+ mQueryResult = values;
+ }
+
+ private void setPhoneNumberResult(String... values) {
+ mPhoneNumberResult = values;
+ }
+
+ private void reset() {
+ mQueryCalls = 0;
+ mQueryResult = null;
+ mPhoneNumberCalls = 0;
+ mPhoneNumberResult = null;
+ }
+
+ @Override
+ boolean query(String contactUri) {
+ if (mQueryResult != null && mQueryCalls < mQueryResult.length) {
+ return mQueryResult[mQueryCalls++];
+ }
+ mQueryCalls++;
+ return false;
+ }
+
+ @Override
+ @Nullable
+ String getPhoneNumber() {
+ if (mPhoneNumberResult != null && mPhoneNumberCalls < mPhoneNumberResult.length) {
+ return mPhoneNumberResult[mPhoneNumberCalls++];
+ }
+ mPhoneNumberCalls++;
+ return null;
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/MockScheduledExecutorService.java b/services/tests/servicestests/src/com/android/server/people/data/MockScheduledExecutorService.java
new file mode 100644
index 0000000..8b8ba12
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/people/data/MockScheduledExecutorService.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.people.data;
+
+import com.android.internal.util.Preconditions;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Mock implementation of ScheduledExecutorService for testing. All commands will run
+ * synchronously. Commands passed to {@link #submit(Runnable)} and {@link #execute(Runnable)} will
+ * run immediately. Commands scheduled via {@link #schedule(Runnable, long, TimeUnit)} will run
+ * after calling {@link #fastForwardTime(long)}.
+ */
+class MockScheduledExecutorService implements ScheduledExecutorService {
+
+ private final List<Runnable> mExecutes = new ArrayList<>();
+ private final List<MockScheduledFuture<?>> mFutures = new ArrayList<>();
+ private long mTimeElapsedMillis = 0;
+
+ /**
+ * Advances fake time, runs all the commands for which the delay has expired.
+ */
+ long fastForwardTime(long millis) {
+ mTimeElapsedMillis += millis;
+ ImmutableList<MockScheduledFuture<?>> futuresCopy = ImmutableList.copyOf(mFutures);
+ mFutures.clear();
+ long totalExecuted = 0;
+ for (MockScheduledFuture<?> future : futuresCopy) {
+ if (future.getDelay() < mTimeElapsedMillis) {
+ future.getCommand().run();
+ mExecutes.add(future.getCommand());
+ totalExecuted += 1;
+ } else {
+ mFutures.add(future);
+ }
+ }
+ return totalExecuted;
+ }
+
+ List<Runnable> getExecutes() {
+ return mExecutes;
+ }
+
+ List<MockScheduledFuture<?>> getFutures() {
+ return mFutures;
+ }
+
+ void resetTimeElapsedMillis() {
+ mTimeElapsedMillis = 0;
+ }
+
+ /**
+ * Fakes a schedule execution of {@link Runnable}. The command will be executed by an explicit
+ * call to {@link #fastForwardTime(long)}.
+ */
+ @Override
+ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+ Preconditions.checkState(unit == TimeUnit.MILLISECONDS);
+ MockScheduledFuture<?> future = new MockScheduledFuture<>(command, delay, unit);
+ mFutures.add(future);
+ return future;
+ }
+
+ @Override
+ public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period,
+ TimeUnit unit) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay,
+ long delay, TimeUnit unit) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void shutdown() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public List<Runnable> shutdownNow() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return false;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> Future<T> submit(Callable<T> task) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> Future<T> submit(Runnable task, T result) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Future<?> submit(Runnable command) {
+ mExecutes.add(command);
+ MockScheduledFuture<?> future = new MockScheduledFuture<>(command, 0,
+ TimeUnit.MILLISECONDS);
+ future.getCommand().run();
+ return future;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout,
+ TimeUnit unit) throws InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+ throws ExecutionException, InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws ExecutionException, InterruptedException, TimeoutException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ mExecutes.add(command);
+ command.run();
+ }
+
+ class MockScheduledFuture<V> implements ScheduledFuture<V> {
+
+ private final Runnable mCommand;
+ private final long mDelay;
+ private boolean mCancelled = false;
+
+ MockScheduledFuture(Runnable command, long delay, TimeUnit timeUnit) {
+ mCommand = command;
+ mDelay = delay;
+ }
+
+ public long getDelay() {
+ return mDelay;
+ }
+
+ public Runnable getCommand() {
+ return mCommand;
+ }
+
+ @Override
+ public long getDelay(TimeUnit unit) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int compareTo(Delayed o) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ mCancelled = true;
+ return mFutures.remove(this);
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return mCancelled;
+ }
+
+ @Override
+ public boolean isDone() {
+ return !mFutures.contains(this);
+ }
+
+ @Override
+ public V get() throws ExecutionException, InterruptedException {
+ return null;
+ }
+
+ @Override
+ public V get(long timeout, TimeUnit unit)
+ throws ExecutionException, InterruptedException, TimeoutException {
+ return null;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java b/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java
index ec4789a..1ddc21e 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java
@@ -20,15 +20,19 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import android.content.Context;
import android.content.LocusId;
import android.content.pm.ShortcutInfo;
import android.net.Uri;
+import androidx.test.InstrumentationRegistry;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.io.File;
import java.util.List;
@RunWith(JUnit4.class)
@@ -52,8 +56,12 @@
@Before
public void setUp() {
+ Context ctx = InstrumentationRegistry.getContext();
+ File testDir = new File(ctx.getCacheDir(), "testdir");
+ testDir.mkdir();
mPackageData = new PackageData(
- PACKAGE_NAME, USER_ID, pkg -> mIsDefaultDialer, pkg -> mIsDefaultSmsApp);
+ PACKAGE_NAME, USER_ID, pkg -> mIsDefaultDialer, pkg -> mIsDefaultSmsApp,
+ new MockScheduledExecutorService(), testDir, new ContactsQueryHelper(ctx));
ConversationInfo conversationInfo = new ConversationInfo.Builder()
.setShortcutId(SHORTCUT_ID)
.setLocusId(LOCUS_ID)
diff --git a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
index e4248a0..b273578 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
@@ -29,8 +29,11 @@
import android.annotation.UserIdInt;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
+import android.content.Context;
import android.content.LocusId;
+import androidx.test.InstrumentationRegistry;
+
import com.android.server.LocalServices;
import org.junit.After;
@@ -41,10 +44,12 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Predicate;
@RunWith(JUnit4.class)
@@ -69,7 +74,13 @@
addLocalServiceMock(UsageStatsManagerInternal.class, mUsageStatsManagerInternal);
- mPackageData = new TestPackageData(PKG_NAME, USER_ID_PRIMARY, pkg -> false, pkg -> false);
+ Context ctx = InstrumentationRegistry.getContext();
+ File testDir = new File(ctx.getCacheDir(), "testdir");
+ ScheduledExecutorService scheduledExecutorService = new MockScheduledExecutorService();
+ ContactsQueryHelper helper = new ContactsQueryHelper(ctx);
+
+ mPackageData = new TestPackageData(PKG_NAME, USER_ID_PRIMARY, pkg -> false, pkg -> false,
+ scheduledExecutorService, testDir, helper);
mPackageData.mConversationStore.mConversationInfo = new ConversationInfo.Builder()
.setShortcutId(SHORTCUT_ID)
.setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
@@ -175,7 +186,7 @@
assertEquals(createInAppConversationEvent(130_000L, 30), events.get(2));
}
- private void addUsageEvents(UsageEvents.Event ... events) {
+ private void addUsageEvents(UsageEvents.Event... events) {
UsageEvents usageEvents = new UsageEvents(Arrays.asList(events), new String[]{});
when(mUsageStatsManagerInternal.queryEventsForUser(anyInt(), anyLong(), anyLong(),
anyBoolean(), anyBoolean())).thenReturn(usageEvents);
@@ -228,6 +239,12 @@
private ConversationInfo mConversationInfo;
+ TestConversationStore(File packageDir,
+ ScheduledExecutorService scheduledExecutorService,
+ ContactsQueryHelper helper) {
+ super(packageDir, scheduledExecutorService, helper);
+ }
+
@Override
@Nullable
ConversationInfo getConversation(@Nullable String shortcutId) {
@@ -237,13 +254,18 @@
private static class TestPackageData extends PackageData {
- private final TestConversationStore mConversationStore = new TestConversationStore();
+ private final TestConversationStore mConversationStore;
private final TestEventStore mEventStore = new TestEventStore();
TestPackageData(@NonNull String packageName, @UserIdInt int userId,
@NonNull Predicate<String> isDefaultDialerPredicate,
- @NonNull Predicate<String> isDefaultSmsAppPredicate) {
- super(packageName, userId, isDefaultDialerPredicate, isDefaultSmsAppPredicate);
+ @NonNull Predicate<String> isDefaultSmsAppPredicate,
+ @NonNull ScheduledExecutorService scheduledExecutorService, @NonNull File rootDir,
+ @NonNull ContactsQueryHelper helper) {
+ super(packageName, userId, isDefaultDialerPredicate, isDefaultSmsAppPredicate,
+ scheduledExecutorService, rootDir, helper);
+ mConversationStore = new TestConversationStore(rootDir, scheduledExecutorService,
+ helper);
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
new file mode 100644
index 0000000..c1a1d5e
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.testing.Assert.assertThrows;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.platform.test.annotations.Presubmit;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Test;
+
+@Presubmit
+public class DisplayAreaProviderTest {
+
+ @Test
+ public void testFromResources_emptyProvider() {
+ Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider("")),
+ Matchers.instanceOf(DisplayAreaPolicy.Default.Provider.class));
+ }
+
+ @Test
+ public void testFromResources_nullProvider() {
+ Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider(null)),
+ Matchers.instanceOf(DisplayAreaPolicy.Default.Provider.class));
+ }
+
+ @Test
+ public void testFromResources_customProvider() {
+ Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider(
+ TestProvider.class.getName())), Matchers.instanceOf(TestProvider.class));
+ }
+
+ @Test
+ public void testFromResources_badProvider_notImplementingProviderInterface() {
+ assertThrows(IllegalStateException.class, () -> {
+ DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider(
+ Object.class.getName()));
+ });
+ }
+
+ @Test
+ public void testFromResources_badProvider_doesntExist() {
+ assertThrows(IllegalStateException.class, () -> {
+ DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider(
+ "com.android.wmtests.nonexistent.Provider"));
+ });
+ }
+
+ private static Resources resourcesWithProvider(String provider) {
+ Resources mock = mock(Resources.class);
+ when(mock.getString(
+ com.android.internal.R.string.config_deviceSpecificDisplayAreaPolicyProvider))
+ .thenReturn(provider);
+ return mock;
+ }
+
+ static class TestProvider implements DisplayAreaPolicy.Provider {
+
+ @Override
+ public DisplayAreaPolicy instantiate(WindowManagerService wmService, DisplayContent content,
+ DisplayArea.Root root, DisplayArea<? extends WindowContainer> imeContainer,
+ DisplayContent.TaskContainers taskContainers) {
+ throw new RuntimeException("test stub");
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index e712255..eae007d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -29,12 +29,13 @@
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.spy;
import android.platform.test.annotations.Presubmit;
import android.util.IntArray;
@@ -122,13 +123,13 @@
// TODO: adjust this test if we pretend to the app that it's still able to control it.
@Test
public void testControlsForDispatch_forceStatusBarVisible() {
- addWindow(TYPE_STATUS_BAR, "topBar").mAttrs.privateFlags |=
+ addWindow(TYPE_STATUS_BAR, "statusBar").mAttrs.privateFlags |=
PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
addWindow(TYPE_NAVIGATION_BAR, "navBar");
final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
- // The app must not control the top bar.
+ // The app must not control the status bar.
assertNotNull(controls);
assertEquals(1, controls.length);
}
@@ -137,6 +138,7 @@
public void testControlsForDispatch_statusBarForceShowNavigation() {
addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade").mAttrs.privateFlags |=
PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+ addWindow(TYPE_STATUS_BAR, "statusBar");
addWindow(TYPE_NAVIGATION_BAR, "navBar");
final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
@@ -169,7 +171,8 @@
.getControllableInsetProvider().getSource().setVisible(false);
final WindowState app = addWindow(TYPE_APPLICATION, "app");
- final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
+ final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy());
+ doNothing().when(policy).startAnimation(anyBoolean(), any());
policy.updateBarControlTarget(app);
policy.showTransient(
IntArray.wrap(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}));
@@ -184,7 +187,7 @@
}
@Test
- public void testShowTransientBars_topCanBeTransient_appGetsTopFakeControl() {
+ public void testShowTransientBars_statusBarCanBeTransient_appGetsStatusBarFakeControl() {
// Adding app window before setting source visibility is to prevent the visibility from
// being cleared by InsetsSourceProvider.updateVisibility.
final WindowState app = addWindow(TYPE_APPLICATION, "app");
@@ -194,14 +197,15 @@
addWindow(TYPE_NAVIGATION_BAR, "navBar")
.getControllableInsetProvider().getSource().setVisible(true);
- final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
+ final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy());
+ doNothing().when(policy).startAnimation(anyBoolean(), any());
policy.updateBarControlTarget(app);
policy.showTransient(
IntArray.wrap(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}));
final InsetsSourceControl[] controls =
mDisplayContent.getInsetsStateController().getControlsForDispatch(app);
- // The app must get the fake control of the top bar, and must get the real control of the
+ // The app must get the fake control of the status bar, and must get the real control of the
// navigation bar.
assertEquals(2, controls.length);
for (int i = controls.length - 1; i >= 0; i--) {
@@ -222,7 +226,8 @@
.getControllableInsetProvider().getSource().setVisible(false);
final WindowState app = addWindow(TYPE_APPLICATION, "app");
- final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
+ final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy());
+ doNothing().when(policy).startAnimation(anyBoolean(), any());
policy.updateBarControlTarget(app);
policy.showTransient(
IntArray.wrap(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR}));
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 39cdd2c..5cf1fbb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -20,7 +20,9 @@
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
@@ -33,14 +35,14 @@
import android.view.InsetsState;
import android.view.test.InsetsModeSession;
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
+
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.SmallTest;
-
@SmallTest
@FlakyTest(detail = "Promote to pre-submit once confirmed stable.")
@Presubmit
@@ -88,6 +90,10 @@
final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime");
+
+ // IME cannot be the IME target.
+ ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
+
getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null);
getController().getSourceProvider(ITYPE_IME).setWindow(ime, null, null);
@@ -98,6 +104,10 @@
public void testImeForDispatch() {
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime");
+
+ // IME cannot be the IME target.
+ ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
+
InsetsSourceProvider statusBarProvider =
getController().getSourceProvider(ITYPE_STATUS_BAR);
statusBarProvider.setWindow(statusBar, null, ((displayFrames, windowState, rect) ->
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
index f7aa3cc..0312df6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -34,14 +34,21 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityManager.StackInfo;
+import android.app.PictureInPictureParams;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
@@ -49,6 +56,8 @@
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap;
+import android.content.pm.ActivityInfo;
+import android.util.Rational;
import android.view.Display;
import android.view.ITaskOrganizer;
import android.view.IWindowContainer;
@@ -384,11 +393,19 @@
RunningTaskInfo info2 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ final int initialRootTaskCount = mWm.mAtmService.mTaskOrganizerController.getRootTasks(
+ mDisplayContent.mDisplayId, null /* activityTypes */).size();
+
final ActivityStack stack = createTaskStackOnDisplay(
WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent);
final ActivityStack stack2 = createTaskStackOnDisplay(
WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mDisplayContent);
+ // Check getRootTasks works
+ List<RunningTaskInfo> roots = mWm.mAtmService.mTaskOrganizerController.getRootTasks(
+ mDisplayContent.mDisplayId, null /* activityTypes */);
+ assertEquals(initialRootTaskCount + 2, roots.size());
+
lastReportedTiles.clear();
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reparent(stack.mRemoteToken, info1.token, true /* onTop */);
@@ -415,11 +432,18 @@
// Check the getChildren call
List<RunningTaskInfo> children =
- mWm.mAtmService.mTaskOrganizerController.getChildTasks(info1.token);
+ mWm.mAtmService.mTaskOrganizerController.getChildTasks(info1.token,
+ null /* activityTypes */);
assertEquals(2, children.size());
- children = mWm.mAtmService.mTaskOrganizerController.getChildTasks(info2.token);
+ children = mWm.mAtmService.mTaskOrganizerController.getChildTasks(info2.token,
+ null /* activityTypes */);
assertEquals(0, children.size());
+ // Check that getRootTasks doesn't include children of tiles
+ roots = mWm.mAtmService.mTaskOrganizerController.getRootTasks(mDisplayContent.mDisplayId,
+ null /* activityTypes */);
+ assertEquals(initialRootTaskCount, roots.size());
+
lastReportedTiles.clear();
wct = new WindowContainerTransaction();
wct.reorder(stack2.mRemoteToken, true /* onTop */);
@@ -483,4 +507,76 @@
verify(transactionListener)
.transactionReady(anyInt(), any());
}
+
+ class StubOrganizer extends ITaskOrganizer.Stub {
+ RunningTaskInfo mInfo;
+
+ @Override
+ public void taskAppeared(RunningTaskInfo info) {
+ mInfo = info;
+ }
+ @Override
+ public void taskVanished(IWindowContainer wc) {
+ }
+ @Override
+ public void transactionReady(int id, SurfaceControl.Transaction t) {
+ }
+ @Override
+ public void onTaskInfoChanged(RunningTaskInfo info) {
+ }
+ };
+
+ private ActivityRecord makePipableActivity() {
+ final ActivityRecord record = createActivityRecord(mDisplayContent,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ record.info.flags |= ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
+ spyOn(record);
+ doReturn(true).when(record).checkEnterPictureInPictureState(any(), anyBoolean());
+ return record;
+ }
+
+ @Test
+ public void testEnterPipParams() {
+ final StubOrganizer o = new StubOrganizer();
+ mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o, WINDOWING_MODE_PINNED);
+ final ActivityRecord record = makePipableActivity();
+
+ final PictureInPictureParams p =
+ new PictureInPictureParams.Builder().setAspectRatio(new Rational(1, 2)).build();
+ assertTrue(mWm.mAtmService.enterPictureInPictureMode(record.token, p));
+ waitUntilHandlersIdle();
+ assertNotNull(o.mInfo);
+ assertNotNull(o.mInfo.pictureInPictureParams);
+ }
+
+ @Test
+ public void testChangePipParams() {
+ class ChangeSavingOrganizer extends StubOrganizer {
+ RunningTaskInfo mChangedInfo;
+ @Override
+ public void onTaskInfoChanged(RunningTaskInfo info) {
+ mChangedInfo = info;
+ }
+ }
+ ChangeSavingOrganizer o = new ChangeSavingOrganizer();
+ mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o, WINDOWING_MODE_PINNED);
+
+ final ActivityRecord record = makePipableActivity();
+ final PictureInPictureParams p =
+ new PictureInPictureParams.Builder().setAspectRatio(new Rational(1, 2)).build();
+ assertTrue(mWm.mAtmService.enterPictureInPictureMode(record.token, p));
+ waitUntilHandlersIdle();
+ assertNotNull(o.mInfo);
+ assertNotNull(o.mInfo.pictureInPictureParams);
+
+ final PictureInPictureParams p2 =
+ new PictureInPictureParams.Builder().setAspectRatio(new Rational(3, 4)).build();
+ mWm.mAtmService.setPictureInPictureParams(record.token, p2);
+ waitUntilHandlersIdle();
+ assertNotNull(o.mChangedInfo);
+ assertNotNull(o.mChangedInfo.pictureInPictureParams);
+ final Rational ratio = o.mChangedInfo.pictureInPictureParams.getAspectRatioRational();
+ assertEquals(3, ratio.getNumerator());
+ assertEquals(4, ratio.getDenominator());
+ }
}
diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
index 553bcff..e97cfaf 100644
--- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
+++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
@@ -123,6 +123,14 @@
return userContext.getContentResolver();
}
+ private static boolean isUpdatedSystemApp(ApplicationInfo ai) {
+ if ((ai.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
+ return true;
+ }
+
+ return false;
+ }
+
/**
* Disable carrier apps until they are privileged
* Must be public b/c framework unit tests can't access package-private methods.
@@ -137,7 +145,7 @@
PackageManager packageManager = context.getPackageManager();
PermissionManager permissionManager =
(PermissionManager) context.getSystemService(Context.PERMISSION_SERVICE);
- List<ApplicationInfo> candidates = getDefaultNotUpdatedCarrierAppCandidatesHelper(
+ List<ApplicationInfo> candidates = getDefaultCarrierAppCandidatesHelper(
userId, systemCarrierAppsDisabledUntilUsed, context);
if (candidates == null || candidates.isEmpty()) {
return;
@@ -176,7 +184,7 @@
if (hasPrivileges) {
// Only update enabled state for the app on /system. Once it has been
// updated we shouldn't touch it.
- if (enabledSetting
+ if (!isUpdatedSystemApp(ai) && enabledSetting
== PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
|| enabledSetting
== PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
@@ -230,7 +238,7 @@
} else { // No carrier privileges
// Only update enabled state for the app on /system. Once it has been
// updated we shouldn't touch it.
- if (enabledSetting
+ if (!isUpdatedSystemApp(ai) && enabledSetting
== PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
&& (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
Log.i(TAG, "Update state(" + packageName
@@ -361,29 +369,6 @@
return apps;
}
- private static List<ApplicationInfo> getDefaultNotUpdatedCarrierAppCandidatesHelper(
- int userId, ArraySet<String> systemCarrierAppsDisabledUntilUsed, Context context) {
- if (systemCarrierAppsDisabledUntilUsed == null) {
- return null;
- }
-
- int size = systemCarrierAppsDisabledUntilUsed.size();
- if (size == 0) {
- return null;
- }
-
- List<ApplicationInfo> apps = new ArrayList<>(size);
- for (int i = 0; i < size; i++) {
- String packageName = systemCarrierAppsDisabledUntilUsed.valueAt(i);
- ApplicationInfo ai =
- getApplicationInfoIfNotUpdatedSystemApp(userId, packageName, context);
- if (ai != null) {
- apps.add(ai);
- }
- }
- return apps;
- }
-
private static Map<String, List<ApplicationInfo>> getDefaultCarrierAssociatedAppsHelper(
int userId, ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed,
Context context) {
@@ -395,11 +380,11 @@
systemCarrierAssociatedAppsDisabledUntilUsed.valueAt(i);
for (int j = 0; j < associatedAppPackages.size(); j++) {
ApplicationInfo ai =
- getApplicationInfoIfNotUpdatedSystemApp(
+ getApplicationInfoIfSystemApp(
userId, associatedAppPackages.get(j), context);
// Only update enabled state for the app on /system. Once it has been updated we
// shouldn't touch it.
- if (ai != null) {
+ if (ai != null && !isUpdatedSystemApp(ai)) {
List<ApplicationInfo> appList = associatedApps.get(carrierAppPackage);
if (appList == null) {
appList = new ArrayList<>();
@@ -413,26 +398,6 @@
}
@Nullable
- private static ApplicationInfo getApplicationInfoIfNotUpdatedSystemApp(
- int userId, String packageName, Context context) {
- try {
- ApplicationInfo ai = context.createContextAsUser(UserHandle.of(userId), 0)
- .getPackageManager()
- .getApplicationInfo(packageName,
- PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
- | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
- | PackageManager.MATCH_SYSTEM_ONLY
- | PackageManager.MATCH_FACTORY_ONLY);
- if (ai != null) {
- return ai;
- }
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Could not reach PackageManager", e);
- }
- return null;
- }
-
- @Nullable
private static ApplicationInfo getApplicationInfoIfSystemApp(
int userId, String packageName, Context context) {
try {
diff --git a/tests/BootImageProfileTest/OWNERS b/tests/BootImageProfileTest/OWNERS
new file mode 100644
index 0000000..657b3f2
--- /dev/null
+++ b/tests/BootImageProfileTest/OWNERS
@@ -0,0 +1,4 @@
+mathieuc@google.com
+calin@google.com
+yawanng@google.com
+sehr@google.com
diff --git a/tests/PlatformCompatGating/Android.bp b/tests/PlatformCompatGating/Android.bp
index 74dfde8..342c47d 100644
--- a/tests/PlatformCompatGating/Android.bp
+++ b/tests/PlatformCompatGating/Android.bp
@@ -18,6 +18,7 @@
name: "PlatformCompatGating",
// Only compile source java files in this apk.
srcs: ["src/**/*.java"],
+ test_suites: ["device-tests"],
static_libs: [
"junit",
"androidx.test.runner",
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index d4e024d..1c330e2 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -272,4 +272,8 @@
boolean isScanThrottleEnabled();
Map getAllMatchingPasspointProfilesForScanResults(in List<ScanResult> scanResult);
+
+ void setAutoWakeupEnabled(boolean enable);
+
+ boolean isAutoWakeupEnabled();
}
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index a720236..0cce23d 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -951,7 +951,7 @@
* Indicate whether the network is trusted or not. Networks are considered trusted
* if the user explicitly allowed this network connection.
* This bit can be used by suggestion network, see
- * {@link WifiNetworkSuggestion.Builder#setUnTrusted(boolean)}
+ * {@link WifiNetworkSuggestion.Builder#setUntrusted(boolean)}
* @hide
*/
public boolean trusted;
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index fb30910c..b6f4490 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -6163,4 +6163,50 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Enable/disable wifi auto wakeup feature.
+ *
+ * <p>
+ * The feature is described in
+ * <a href="Wi-Fi Turn on automatically">
+ * https://source.android.com/devices/tech/connect/wifi-infrastructure
+ * #turn_on_wi-fi_automatically
+ * </a>
+ *
+ * @param enable true to enable, false to disable.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void setAutoWakeupEnabled(boolean enable) {
+ try {
+ mService.setAutoWakeupEnabled(enable);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the persisted Wi-Fi auto wakeup feature state. Defaults to false, unless changed by the
+ * user via Settings.
+ *
+ * <p>
+ * The feature is described in
+ * <a href="Wi-Fi Turn on automatically">
+ * https://source.android.com/devices/tech/connect/wifi-infrastructure
+ * #turn_on_wi-fi_automatically
+ * </a>
+ *
+ * @return true to indicate that wakeup feature is enabled, false to indicate that wakeup
+ * feature is disabled.
+ */
+ @RequiresPermission(ACCESS_WIFI_STATE)
+ public boolean isAutoWakeupEnabled() {
+ try {
+ return mService.isAutoWakeupEnabled();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/wifi/java/android/net/wifi/wificond/NativeScanResult.java b/wifi/java/android/net/wifi/wificond/NativeScanResult.java
index 7cc617d..bd99476 100644
--- a/wifi/java/android/net/wifi/wificond/NativeScanResult.java
+++ b/wifi/java/android/net/wifi/wificond/NativeScanResult.java
@@ -16,14 +16,21 @@
package android.net.wifi.wificond;
+import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.net.MacAddress;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
/**
@@ -33,6 +40,8 @@
*/
@SystemApi
public final class NativeScanResult implements Parcelable {
+ private static final String TAG = "NativeScanResult";
+
/** @hide */
@VisibleForTesting
public byte[] ssid;
@@ -53,7 +62,7 @@
public long tsf;
/** @hide */
@VisibleForTesting
- public int capability;
+ @BssCapabilityBits public int capability;
/** @hide */
@VisibleForTesting
public boolean associated;
@@ -71,14 +80,17 @@
}
/**
- * Returns raw bytes representing the MAC address (BSSID) of the AP represented by this scan
- * result.
+ * Returns the MAC address (BSSID) of the AP represented by this scan result.
*
- * @return a byte array, possibly null or containing the incorrect number of bytes for a MAC
- * address.
+ * @return a MacAddress or null on error.
*/
- @NonNull public byte[] getBssid() {
- return bssid;
+ @Nullable public MacAddress getBssid() {
+ try {
+ return MacAddress.fromBytes(bssid);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Illegal argument " + Arrays.toString(bssid), e);
+ return null;
+ }
}
/**
@@ -127,31 +139,103 @@
return associated;
}
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = {"BSS_CAPABILITY_"},
+ value = {BSS_CAPABILITY_ESS,
+ BSS_CAPABILITY_IBSS,
+ BSS_CAPABILITY_CF_POLLABLE,
+ BSS_CAPABILITY_CF_POLL_REQUEST,
+ BSS_CAPABILITY_PRIVACY,
+ BSS_CAPABILITY_SHORT_PREAMBLE,
+ BSS_CAPABILITY_PBCC,
+ BSS_CAPABILITY_CHANNEL_AGILITY,
+ BSS_CAPABILITY_SPECTRUM_MANAGEMENT,
+ BSS_CAPABILITY_QOS,
+ BSS_CAPABILITY_SHORT_SLOT_TIME,
+ BSS_CAPABILITY_APSD,
+ BSS_CAPABILITY_RADIO_MANAGEMENT,
+ BSS_CAPABILITY_DSSS_OFDM,
+ BSS_CAPABILITY_DELAYED_BLOCK_ACK,
+ BSS_CAPABILITY_IMMEDIATE_BLOCK_ACK
+ })
+ public @interface BssCapabilityBits { }
+
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): ESS.
+ */
+ public static final int BSS_CAPABILITY_ESS = 0x1 << 0;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): IBSS.
+ */
+ public static final int BSS_CAPABILITY_IBSS = 0x1 << 1;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): CF Pollable.
+ */
+ public static final int BSS_CAPABILITY_CF_POLLABLE = 0x1 << 2;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): CF-Poll Request.
+ */
+ public static final int BSS_CAPABILITY_CF_POLL_REQUEST = 0x1 << 3;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Privacy.
+ */
+ public static final int BSS_CAPABILITY_PRIVACY = 0x1 << 4;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Short Preamble.
+ */
+ public static final int BSS_CAPABILITY_SHORT_PREAMBLE = 0x1 << 5;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): PBCC.
+ */
+ public static final int BSS_CAPABILITY_PBCC = 0x1 << 6;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Channel Agility.
+ */
+ public static final int BSS_CAPABILITY_CHANNEL_AGILITY = 0x1 << 7;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Spectrum Management.
+ */
+ public static final int BSS_CAPABILITY_SPECTRUM_MANAGEMENT = 0x1 << 8;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): QoS.
+ */
+ public static final int BSS_CAPABILITY_QOS = 0x1 << 9;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Short Slot Time.
+ */
+ public static final int BSS_CAPABILITY_SHORT_SLOT_TIME = 0x1 << 10;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): APSD.
+ */
+ public static final int BSS_CAPABILITY_APSD = 0x1 << 11;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Radio Management.
+ */
+ public static final int BSS_CAPABILITY_RADIO_MANAGEMENT = 0x1 << 12;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): DSSS-OFDM.
+ */
+ public static final int BSS_CAPABILITY_DSSS_OFDM = 0x1 << 13;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Delayed Block Ack.
+ */
+ public static final int BSS_CAPABILITY_DELAYED_BLOCK_ACK = 0x1 << 14;
+ /**
+ * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Immediate Block Ack.
+ */
+ public static final int BSS_CAPABILITY_IMMEDIATE_BLOCK_ACK = 0x1 << 15;
+
/**
* Returns the capabilities of the AP repseresented by this scan result as advertised in the
* received probe response or beacon.
*
- * This is a bit mask describing the capabilities of a BSS. See IEEE Std 802.11: 9.4.1.4:
- * Bit 0 - ESS
- * Bit 1 - IBSS
- * Bit 2 - CF Pollable
- * Bit 3 - CF-Poll Request
- * Bit 4 - Privacy
- * Bit 5 - Short Preamble
- * Bit 6 - PBCC
- * Bit 7 - Channel Agility
- * Bit 8 - Spectrum Management
- * Bit 9 - QoS
- * Bit 10 - Short Slot Time
- * Bit 11 - APSD
- * Bit 12 - Radio Measurement
- * Bit 13 - DSSS-OFDM
- * Bit 14 - Delayed Block Ack
- * Bit 15 - Immediate Block Ack
+ * This is a bit mask describing the capabilities of a BSS. See IEEE Std 802.11: 9.4.1.4: one
+ * of the {@code BSS_CAPABILITY_*} flags.
*
* @return a bit mask of capabilities.
*/
- @NonNull public int getCapabilities() {
+ @BssCapabilityBits public int getCapabilities() {
return capability;
}
diff --git a/wifi/java/android/net/wifi/wificond/NativeWifiClient.java b/wifi/java/android/net/wifi/wificond/NativeWifiClient.java
index 916c115..9ad2a27 100644
--- a/wifi/java/android/net/wifi/wificond/NativeWifiClient.java
+++ b/wifi/java/android/net/wifi/wificond/NativeWifiClient.java
@@ -17,11 +17,13 @@
package android.net.wifi.wificond;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.net.MacAddress;
import android.os.Parcel;
import android.os.Parcelable;
-import java.util.Arrays;
+import java.util.Objects;
/**
* Structure providing information about clients (STAs) associated with a SoftAp.
@@ -30,16 +32,21 @@
*/
@SystemApi
public final class NativeWifiClient implements Parcelable {
+ private final MacAddress mMacAddress;
+
/**
- * The raw bytes of the MAC address of the client (STA) represented by this object.
+ * The MAC address of the client (STA) represented by this object. The MAC address may be null
+ * in case of an error.
*/
- @NonNull public final byte[] macAddress;
+ @Nullable public MacAddress getMacAddress() {
+ return mMacAddress;
+ }
/**
* Construct a native Wi-Fi client.
*/
- public NativeWifiClient(@NonNull byte[] macAddress) {
- this.macAddress = macAddress;
+ public NativeWifiClient(@Nullable MacAddress macAddress) {
+ this.mMacAddress = macAddress;
}
/** override comparator */
@@ -50,13 +57,13 @@
return false;
}
NativeWifiClient other = (NativeWifiClient) rhs;
- return Arrays.equals(macAddress, other.macAddress);
+ return Objects.equals(mMacAddress, other.mMacAddress);
}
/** override hash code */
@Override
public int hashCode() {
- return Arrays.hashCode(macAddress);
+ return mMacAddress.hashCode();
}
/** implement Parcelable interface */
@@ -71,7 +78,7 @@
*/
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
- out.writeByteArray(macAddress);
+ out.writeByteArray(mMacAddress.toByteArray());
}
/** implement Parcelable interface */
@@ -79,9 +86,11 @@
new Parcelable.Creator<NativeWifiClient>() {
@Override
public NativeWifiClient createFromParcel(Parcel in) {
- byte[] macAddress = in.createByteArray();
- if (macAddress == null) {
- macAddress = new byte[0];
+ MacAddress macAddress;
+ try {
+ macAddress = MacAddress.fromBytes(in.createByteArray());
+ } catch (IllegalArgumentException e) {
+ macAddress = null;
}
return new NativeWifiClient(macAddress);
}
diff --git a/wifi/java/android/net/wifi/wificond/WifiCondManager.java b/wifi/java/android/net/wifi/wificond/WifiCondManager.java
index 7a31a5a..61f18e0 100644
--- a/wifi/java/android/net/wifi/wificond/WifiCondManager.java
+++ b/wifi/java/android/net/wifi/wificond/WifiCondManager.java
@@ -41,19 +41,16 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.stream.Collectors;
/**
- * This class encapsulates the interface the wificond (Wi-Fi Conductor) daemon presents to the
- * Wi-Fi framework. The interface is only for use by the Wi-Fi framework and access is protected
- * by SELinux permissions: only the system server and wpa_supplicant can use WifiCondManager.
+ * This class encapsulates the interface the wificond daemon presents to the Wi-Fi framework. The
+ * interface is only for use by the Wi-Fi framework and access is protected by SELinux permissions.
*
* @hide
*/
@@ -371,7 +368,7 @@
public void onConnectedClientsChanged(NativeWifiClient client, boolean isConnected) {
if (mVerboseLoggingEnabled) {
Log.d(TAG, "onConnectedClientsChanged called with "
- + client.macAddress + " isConnected: " + isConnected);
+ + client.getMacAddress() + " isConnected: " + isConnected);
}
Binder.clearCallingIdentity();
@@ -1046,13 +1043,13 @@
* WifiScanner.WIFI_BAND_5_GHZ
* WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY
* WifiScanner.WIFI_BAND_6_GHZ
- * @return frequencies List of valid frequencies (MHz), or an empty list for error.
+ * @return frequencies vector of valid frequencies (MHz), or an empty array for error.
* @throws IllegalArgumentException if band is not recognized.
*/
- public @NonNull List<Integer> getChannelsMhzForBand(@WifiAnnotations.WifiBandBasic int band) {
+ public @NonNull int[] getChannelsMhzForBand(@WifiAnnotations.WifiBandBasic int band) {
if (mWificond == null) {
Log.e(TAG, "No valid wificond scanner interface handler");
- return Collections.emptyList();
+ return new int[0];
}
int[] result = null;
try {
@@ -1076,9 +1073,9 @@
Log.e(TAG, "Failed to request getChannelsForBand due to remote exception");
}
if (result == null) {
- return Collections.emptyList();
+ result = new int[0];
}
- return Arrays.stream(result).boxed().collect(Collectors.toList());
+ return result;
}
/** Helper function to look up the interface handle using name */
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 847040c..853212a 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -2391,4 +2391,14 @@
assertFalse(mWifiManager.isScanThrottleEnabled());
verify(mWifiService).isScanThrottleEnabled();
}
+
+ @Test
+ public void testAutoWakeup() throws Exception {
+ mWifiManager.setAutoWakeupEnabled(true);
+ verify(mWifiService).setAutoWakeupEnabled(true);
+
+ when(mWifiService.isAutoWakeupEnabled()).thenReturn(false);
+ assertFalse(mWifiManager.isAutoWakeupEnabled());
+ verify(mWifiService).isAutoWakeupEnabled();
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java b/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
index 32105be..b745a34 100644
--- a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
@@ -38,6 +38,7 @@
import android.app.AlarmManager;
import android.app.test.TestAlarmManager;
import android.content.Context;
+import android.net.MacAddress;
import android.net.wifi.ScanResult;
import android.net.wifi.SoftApInfo;
import android.net.wifi.WifiConfiguration;
@@ -65,8 +66,6 @@
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -121,7 +120,8 @@
private static final String TEST_QUOTED_SSID_2 = "\"testSsid2\"";
private static final int[] TEST_FREQUENCIES_1 = {};
private static final int[] TEST_FREQUENCIES_2 = {2500, 5124};
- private static final byte[] TEST_RAW_MAC_BYTES = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05};
+ private static final MacAddress TEST_RAW_MAC_BYTES = MacAddress.fromBytes(
+ new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05});
private static final List<byte[]> SCAN_HIDDEN_NETWORK_SSID_LIST =
new ArrayList<byte[]>() {{
@@ -742,43 +742,11 @@
verify(deathHandler).run();
// The handles should be cleared after death.
- assertEquals(0, mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_5_GHZ).size());
+ assertEquals(0, mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_5_GHZ).length);
verify(mWificond, never()).getAvailable5gNonDFSChannels();
}
/**
- * Verify primitive array to list translation of channel API.
- */
- @Test
- public void testGetChannels() throws Exception {
- int[] resultsEmpty = new int[0];
- int[] resultsSingle = new int[]{100};
- int[] resultsMore = new int[]{100, 200};
-
- List<Integer> emptyList = Collections.emptyList();
- List<Integer> singleList = Arrays.asList(100);
- List<Integer> moreList = Arrays.asList(100, 200);
-
- when(mWificond.getAvailable2gChannels()).thenReturn(null);
- assertEquals(mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_24_GHZ),
- emptyList);
- assertEquals(mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_5_GHZ),
- emptyList);
-
- when(mWificond.getAvailable2gChannels()).thenReturn(resultsEmpty);
- assertEquals(mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_24_GHZ),
- emptyList);
-
- when(mWificond.getAvailable2gChannels()).thenReturn(resultsSingle);
- assertEquals(mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_24_GHZ),
- singleList);
-
- when(mWificond.getAvailable2gChannels()).thenReturn(resultsMore);
- assertEquals(mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_24_GHZ),
- moreList);
- }
-
- /**
* sendMgmtFrame() should fail if a null callback is passed in.
*/
@Test