Merge "Persistent process should have all capabilities."
diff --git a/apex/sdkextensions/Android.bp b/apex/sdkextensions/Android.bp
index 4c5c2b2..25765af 100644
--- a/apex/sdkextensions/Android.bp
+++ b/apex/sdkextensions/Android.bp
@@ -28,7 +28,6 @@
name: "com.android.sdkext-defaults",
java_libs: [ "framework-sdkextensions" ],
prebuilts: [
- "com.android.sdkext.ldconfig",
"derive_sdk.rc",
],
key: "com.android.sdkext.key",
@@ -51,13 +50,6 @@
certificate: "com.android.sdkext",
}
-prebuilt_etc {
- name: "com.android.sdkext.ldconfig",
- src: "ld.config.txt",
- filename: "ld.config.txt",
- installable: false,
-}
-
python_binary_host {
name: "gen_sdkinfo",
srcs: [
diff --git a/apex/sdkextensions/ld.config.txt b/apex/sdkextensions/ld.config.txt
deleted file mode 100644
index dcc69b8..0000000
--- a/apex/sdkextensions/ld.config.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Bionic loader config file for the sdkextensions apex.
-
-dir.sdkextensions = /apex/com.android.sdkext/bin/
-
-[sdkextensions]
-additional.namespaces = platform
-
-namespace.default.isolated = true
-namespace.default.links = platform
-namespace.default.link.platform.allow_all_shared_libs = true
-
-###############################################################################
-# "platform" namespace: used for NDK libraries
-###############################################################################
-namespace.platform.isolated = true
-namespace.platform.search.paths = /system/${LIB}
-namespace.platform.asan.search.paths = /data/asan/system/${LIB}
-
-# /system/lib/libc.so, etc are symlinks to /apex/com.android.lib/lib/bionic/libc.so, etc.
-# Add /apex/... path to the permitted paths because linker uses realpath(3)
-# to check the accessibility of the lib. We could add this to search.paths
-# instead but that makes the resolution of bionic libs be dependent on
-# the order of /system/lib and /apex/... in search.paths. If /apex/...
-# is after /system/lib, then /apex/... is never tried because libc.so
-# is always found in /system/lib but fails to pass the accessibility test
-# because of its realpath. It's better to not depend on the ordering if
-# possible.
-namespace.platform.permitted.paths = /apex/com.android.runtime/${LIB}/bionic
-namespace.platform.asan.permitted.paths = /apex/com.android.runtime/${LIB}/bionic
diff --git a/apex/statsd/framework/java/android/app/StatsManager.java b/apex/statsd/framework/java/android/app/StatsManager.java
index 526d17f..e637187 100644
--- a/apex/statsd/framework/java/android/app/StatsManager.java
+++ b/apex/statsd/framework/java/android/app/StatsManager.java
@@ -159,6 +159,9 @@
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Failed to addConfig in statsmanager");
+ throw new StatsUnavailableException(e.getMessage(), e);
}
}
}
@@ -195,6 +198,9 @@
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Failed to removeConfig in statsmanager");
+ throw new StatsUnavailableException(e.getMessage(), e);
}
}
}
@@ -391,6 +397,9 @@
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Failed to getReports in statsmanager");
+ throw new StatsUnavailableException(e.getMessage(), e);
}
}
}
@@ -428,6 +437,9 @@
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Failed to getStatsMetadata in statsmanager");
+ throw new StatsUnavailableException(e.getMessage(), e);
}
}
}
@@ -469,6 +481,9 @@
+ "registered experiment IDs");
}
throw new StatsUnavailableException("could not connect", e);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Failed to getRegisteredExperimentIds in statsmanager");
+ throw new StatsUnavailableException(e.getMessage(), e);
}
}
}
diff --git a/api/current.txt b/api/current.txt
index 476d0ae..0a7bac5 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2992,7 +2992,7 @@
method public int getNonInteractiveUiTimeoutMillis();
method public android.content.pm.ResolveInfo getResolveInfo();
method public String getSettingsActivityName();
- method @Nullable public android.graphics.drawable.Drawable loadAnimatedImage(@NonNull android.content.pm.PackageManager);
+ method @Nullable public android.graphics.drawable.Drawable loadAnimatedImage(@NonNull android.content.Context);
method public String loadDescription(android.content.pm.PackageManager);
method @Nullable public String loadHtmlDescription(@NonNull android.content.pm.PackageManager);
method public CharSequence loadSummary(android.content.pm.PackageManager);
@@ -26899,21 +26899,27 @@
ctor public MediaRoute2ProviderService();
method @NonNull public final java.util.List<android.media.RoutingSessionInfo> getAllSessionInfo();
method @Nullable public final android.media.RoutingSessionInfo getSessionInfo(@NonNull String);
+ method public final void notifyRequestFailed(long, int);
method public final void notifyRoutes(@NonNull java.util.Collection<android.media.MediaRoute2Info>);
method public final void notifySessionCreated(@NonNull android.media.RoutingSessionInfo, long);
method public final void notifySessionCreationFailed(long);
method public final void notifySessionReleased(@NonNull String);
method public final void notifySessionUpdated(@NonNull android.media.RoutingSessionInfo);
method @CallSuper @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
- method public abstract void onCreateSession(@NonNull String, @NonNull String, long, @Nullable android.os.Bundle);
- method public abstract void onDeselectRoute(@NonNull String, @NonNull String);
+ method public abstract void onCreateSession(long, @NonNull String, @NonNull String, @Nullable android.os.Bundle);
+ method public abstract void onDeselectRoute(long, @NonNull String, @NonNull String);
method public void onDiscoveryPreferenceChanged(@NonNull android.media.RouteDiscoveryPreference);
- method public abstract void onReleaseSession(@NonNull String);
- method public abstract void onSelectRoute(@NonNull String, @NonNull String);
- method public abstract void onSetRouteVolume(@NonNull String, int);
- method public abstract void onSetSessionVolume(@NonNull String, int);
- method public abstract void onTransferToRoute(@NonNull String, @NonNull String);
- field public static final long REQUEST_ID_UNKNOWN = 0L; // 0x0L
+ method public abstract void onReleaseSession(long, @NonNull String);
+ method public abstract void onSelectRoute(long, @NonNull String, @NonNull String);
+ method public abstract void onSetRouteVolume(long, @NonNull String, int);
+ method public abstract void onSetSessionVolume(long, @NonNull String, int);
+ method public abstract void onTransferToRoute(long, @NonNull String, @NonNull String);
+ field public static final int REASON_INVALID_COMMAND = 4; // 0x4
+ field public static final int REASON_NETWORK_ERROR = 2; // 0x2
+ field public static final int REASON_REJECTED = 1; // 0x1
+ field public static final int REASON_ROUTE_NOT_AVAILABLE = 3; // 0x3
+ field public static final int REASON_UNKNOWN_ERROR = 0; // 0x0
+ field public static final long REQUEST_ID_NONE = 0L; // 0x0L
field public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 9663c0c..f4a1d841 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8757,7 +8757,9 @@
public class UserManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void clearSeedAccountData();
- method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle createProfile(@NonNull String, @NonNull String, @Nullable String[]) throws android.os.UserManager.UserOperationException;
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle createProfile(@NonNull String, @NonNull String, @NonNull java.util.Set<java.lang.String>) throws android.os.UserManager.UserOperationException;
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public java.util.List<android.os.UserHandle> getAllProfiles();
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public java.util.List<android.os.UserHandle> getEnabledProfiles();
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getProfileParent(@NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getSeedAccountName();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.PersistableBundle getSeedAccountOptions();
@@ -8765,7 +8767,6 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public long[] getSerialNumbersOfUsers(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public java.util.List<android.os.UserHandle> getUserHandles(boolean);
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public android.graphics.Bitmap getUserIcon();
- method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public java.util.List<android.os.UserHandle> getUserProfiles(boolean);
method @Deprecated @android.os.UserManager.UserRestrictionSource @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public int getUserRestrictionSource(String, android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public java.util.List<android.os.UserManager.EnforcingUser> getUserRestrictionSources(String, android.os.UserHandle);
method @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public int getUserSwitchability();
diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp
index 4899b4a..dcfdfe3 100644
--- a/cmds/statsd/src/storage/StorageManager.cpp
+++ b/cmds/statsd/src/storage/StorageManager.cpp
@@ -75,11 +75,11 @@
(long long)id);
}
-static const char* findTrainInfoFileNameLocked(const string& trainName) {
+static string findTrainInfoFileNameLocked(const string& trainName) {
unique_ptr<DIR, decltype(&closedir)> dir(opendir(TRAIN_INFO_DIR), closedir);
if (dir == NULL) {
VLOG("Path %s does not exist", TRAIN_INFO_DIR);
- return nullptr;
+ return "";
}
dirent* de;
while ((de = readdir(dir.get()))) {
@@ -90,12 +90,12 @@
if (fileNameLength >= trainName.length()) {
if (0 == strncmp(fileName + fileNameLength - trainName.length(), trainName.c_str(),
trainName.length())) {
- return fileName;
+ return string(fileName);
}
}
}
- return nullptr;
+ return "";
}
// Returns array of int64_t which contains timestamp in seconds, uid,
@@ -267,13 +267,13 @@
bool StorageManager::readTrainInfoLocked(const std::string& trainName, InstallTrainInfo& trainInfo) {
trimToFit(TRAIN_INFO_DIR, /*parseTimestampOnly=*/ true);
- const char* fileName = findTrainInfoFileNameLocked(trainName);
- if (fileName == nullptr) {
+ string fileName = findTrainInfoFileNameLocked(trainName);
+ if (fileName.empty()) {
return false;
}
- int fd = open(StringPrintf("%s/%s", TRAIN_INFO_DIR, fileName).c_str(), O_RDONLY | O_CLOEXEC);
+ int fd = open(StringPrintf("%s/%s", TRAIN_INFO_DIR, fileName.c_str()).c_str(), O_RDONLY | O_CLOEXEC);
if (fd == -1) {
- VLOG("Failed to open %s", fileName);
+ VLOG("Failed to open %s", fileName.c_str());
return false;
}
diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
index 9707405..fe270a4 100644
--- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java
+++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
@@ -46,6 +46,10 @@
* @param args The command-line arguments
*/
public static void main(String[] args) {
+ // Initialize the telephony module.
+ // TODO: Do it in zygote and RuntimeInit. b/148897549
+ ActivityThread.initializeMainlineModules();
+
(new Telecom()).run(args);
}
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index c373284..3b0667d 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -803,11 +803,12 @@
* @return The animated image drawable.
*/
@Nullable
- public Drawable loadAnimatedImage(@NonNull PackageManager packageManager) {
+ public Drawable loadAnimatedImage(@NonNull Context context) {
if (mAnimatedImageRes == /* invalid */ 0) {
return null;
}
+ final PackageManager packageManager = context.getPackageManager();
final String packageName = mComponentName.getPackageName();
final ApplicationInfo applicationInfo = mResolveInfo.serviceInfo.applicationInfo;
diff --git a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
index d537ce1..6209679 100644
--- a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
@@ -208,11 +208,12 @@
* @return The animated image drawable.
*/
@Nullable
- public Drawable loadAnimatedImage(@NonNull PackageManager packageManager) {
+ public Drawable loadAnimatedImage(@NonNull Context context) {
if (mAnimatedImageRes == /* invalid */ 0) {
return null;
}
+ final PackageManager packageManager = context.getPackageManager();
final String packageName = mComponentName.getPackageName();
final ApplicationInfo applicationInfo = mActivityInfo.applicationInfo;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 55be082..b219394 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9932,20 +9932,27 @@
}
/**
- * Called by device owner to control the security logging feature.
+ * Called by device owner or a profile owner of an organization-owned managed profile to
+ * control the security logging feature.
*
* <p> Security logs contain various information intended for security auditing purposes.
- * See {@link SecurityEvent} for details.
+ * When security logging is enabled by a profile owner of
+ * an organization-owned managed profile, certain security logs are not visible (for example
+ * personal app launch events) or they will be redacted (for example, details of the physical
+ * volume mount events). Please see {@link SecurityEvent} for details.
*
* <p><strong>Note:</strong> The device owner won't be able to retrieve security logs if there
* are unaffiliated secondary users or profiles on the device, regardless of whether the
* feature is enabled. Logs will be discarded if the internal buffer fills up while waiting for
* all users to become affiliated. Therefore it's recommended that affiliation ids are set for
- * new users as soon as possible after provisioning via {@link #setAffiliationIds}.
+ * new users as soon as possible after provisioning via {@link #setAffiliationIds}. Profile
+ * owner of organization-owned managed profile is not subject to this restriction since all
+ * privacy-sensitive events happening outside the managed profile would have been redacted
+ * already.
*
- * @param admin Which device owner this request is associated with.
+ * @param admin Which device admin this request is associated with.
* @param enabled whether security logging should be enabled or not.
- * @throws SecurityException if {@code admin} is not a device owner.
+ * @throws SecurityException if {@code admin} is not allowed to control security logging.
* @see #setAffiliationIds
* @see #retrieveSecurityLogs
*/
@@ -9959,14 +9966,14 @@
}
/**
- * Return whether security logging is enabled or not by the device owner.
+ * Return whether security logging is enabled or not by the admin.
*
- * <p>Can only be called by the device owner, otherwise a {@link SecurityException} will be
- * thrown.
+ * <p>Can only be called by the device owner or a profile owner of an organization-owned
+ * managed profile, otherwise a {@link SecurityException} will be thrown.
*
- * @param admin Which device owner this request is associated with.
+ * @param admin Which device admin this request is associated with.
* @return {@code true} if security logging is enabled by device owner, {@code false} otherwise.
- * @throws SecurityException if {@code admin} is not a device owner.
+ * @throws SecurityException if {@code admin} is not allowed to control security logging.
*/
public boolean isSecurityLoggingEnabled(@Nullable ComponentName admin) {
throwIfParentInstance("isSecurityLoggingEnabled");
@@ -9978,20 +9985,21 @@
}
/**
- * Called by device owner to retrieve all new security logging entries since the last call to
- * this API after device boots.
+ * Called by device owner or profile owner of an organization-owned managed profile to retrieve
+ * all new security logging entries since the last call to this API after device boots.
*
* <p> Access to the logs is rate limited and it will only return new logs after the device
* owner has been notified via {@link DeviceAdminReceiver#onSecurityLogsAvailable}.
*
- * <p>If there is any other user or profile on the device, it must be affiliated with the
- * device. Otherwise a {@link SecurityException} will be thrown. See {@link #isAffiliatedUser}.
+ * <p> When called by a device owner, if there is any other user or profile on the device,
+ * it must be affiliated with the device. Otherwise a {@link SecurityException} will be thrown.
+ * See {@link #isAffiliatedUser}.
*
- * @param admin Which device owner this request is associated with.
+ * @param admin Which device admin this request is associated with.
* @return the new batch of security logs which is a list of {@link SecurityEvent},
* or {@code null} if rate limitation is exceeded or if logging is currently disabled.
- * @throws SecurityException if {@code admin} is not a device owner, or there is at least one
- * profile or secondary user that is not affiliated with the device.
+ * @throws SecurityException if {@code admin} is not allowed to access security logging,
+ * or there is at least one profile or secondary user that is not affiliated with the device.
* @see #isAffiliatedUser
* @see DeviceAdminReceiver#onSecurityLogsAvailable
*/
@@ -10124,21 +10132,23 @@
}
/**
- * Called by device owners to retrieve device logs from before the device's last reboot.
+ * Called by device owner or profile owner of an organization-owned managed profile to retrieve
+ * device logs from before the device's last reboot.
* <p>
* <strong> This API is not supported on all devices. Calling this API on unsupported devices
* will result in {@code null} being returned. The device logs are retrieved from a RAM region
* which is not guaranteed to be corruption-free during power cycles, as a result be cautious
* about data corruption when parsing. </strong>
*
- * <p>If there is any other user or profile on the device, it must be affiliated with the
- * device. Otherwise a {@link SecurityException} will be thrown. See {@link #isAffiliatedUser}.
+ * <p> When called by a device owner, if there is any other user or profile on the device,
+ * it must be affiliated with the device. Otherwise a {@link SecurityException} will be thrown.
+ * See {@link #isAffiliatedUser}.
*
- * @param admin Which device owner this request is associated with.
+ * @param admin Which device admin this request is associated with.
* @return Device logs from before the latest reboot of the system, or {@code null} if this API
* is not supported on the device.
- * @throws SecurityException if {@code admin} is not a device owner, or there is at least one
- * profile or secondary user that is not affiliated with the device.
+ * @throws SecurityException if {@code admin} is not allowed to access security logging, or
+ * there is at least one profile or secondary user that is not affiliated with the device.
* @see #isAffiliatedUser
* @see #retrieveSecurityLogs
*/
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index 91cf120..fb7f573 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -23,11 +23,13 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemProperties;
+import android.os.UserHandle;
import android.util.EventLog.Event;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
@@ -104,7 +106,8 @@
/**
* Indicates that a shell command was issued over ADB via {@code adb shell <command>}
* The log entry contains a {@code String} payload containing the shell command, accessible
- * via {@link SecurityEvent#getData()}.
+ * via {@link SecurityEvent#getData()}. If security logging is enabled on organization-owned
+ * managed profile devices, the shell command will be redacted to an empty string.
*/
public static final int TAG_ADB_SHELL_CMD = SecurityLogTags.SECURITY_ADB_SHELL_COMMAND;
@@ -133,6 +136,8 @@
* <li> [3] app pid ({@code Integer})
* <li> [4] seinfo tag ({@code String})
* <li> [5] SHA-256 hash of the base APK in hexadecimal ({@code String})
+ * If security logging is enabled on organization-owned managed profile devices, only events
+ * happening inside the managed profile will be visible.
*/
public static final int TAG_APP_PROCESS_START = SecurityLogTags.SECURITY_APP_PROCESS_START;
@@ -205,7 +210,8 @@
* following information about the event, encapsulated in an {@link Object} array and
* accessible via {@link SecurityEvent#getData()}:
* <li> [0] mount point ({@code String})
- * <li> [1] volume label ({@code String}).
+ * <li> [1] volume label ({@code String}). Redacted to empty string on organization-owned
+ * managed profile devices.
*/
public static final int TAG_MEDIA_MOUNT = SecurityLogTags.SECURITY_MEDIA_MOUNTED;
@@ -214,7 +220,8 @@
* following information about the event, encapsulated in an {@link Object} array and
* accessible via {@link SecurityEvent#getData()}:
* <li> [0] mount point ({@code String})
- * <li> [1] volume label ({@code String}).
+ * <li> [1] volume label ({@code String}). Redacted to empty string on organization-owned
+ * managed profile devices.
*/
public static final int TAG_MEDIA_UNMOUNT = SecurityLogTags.SECURITY_MEDIA_UNMOUNTED;
@@ -340,6 +347,9 @@
* <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded)
* <li> [1] alias of the key ({@code String})
* <li> [2] requesting process uid ({@code Integer}).
+ *
+ * If security logging is enabled on organization-owned managed profile devices, only events
+ * happening inside the managed profile will be visible.
*/
public static final int TAG_KEY_GENERATED =
SecurityLogTags.SECURITY_KEY_GENERATED;
@@ -351,6 +361,9 @@
* <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded)
* <li> [1] alias of the key ({@code String})
* <li> [2] requesting process uid ({@code Integer}).
+ *
+ * If security logging is enabled on organization-owned managed profile devices, only events
+ * happening inside the managed profile will be visible.
*/
public static final int TAG_KEY_IMPORT = SecurityLogTags.SECURITY_KEY_IMPORTED;
@@ -361,6 +374,9 @@
* <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded)
* <li> [1] alias of the key ({@code String})
* <li> [2] requesting process uid ({@code Integer}).
+ *
+ * If security logging is enabled on organization-owned managed profile devices, only events
+ * happening inside the managed profile will be visible.
*/
public static final int TAG_KEY_DESTRUCTION = SecurityLogTags.SECURITY_KEY_DESTROYED;
@@ -370,6 +386,11 @@
* {@link Object} array and accessible via {@link SecurityEvent#getData()}:
* <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded)
* <li> [1] subject of the certificate ({@code String}).
+ * <li> [2] which user the certificate is installed for ({@code Integer}), only available from
+ * version {@link android.os.Build.VERSION_CODES#R}.
+ *
+ * If security logging is enabled on organization-owned managed profile devices, only events
+ * happening inside the managed profile will be visible.
*/
public static final int TAG_CERT_AUTHORITY_INSTALLED =
SecurityLogTags.SECURITY_CERT_AUTHORITY_INSTALLED;
@@ -380,6 +401,11 @@
* {@link Object} array and accessible via {@link SecurityEvent#getData()}:
* <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded)
* <li> [1] subject of the certificate ({@code String}).
+ * <li> [2] which user the certificate is removed from ({@code Integer}), only available from
+ * version {@link android.os.Build.VERSION_CODES#R}.
+ *
+ * If security logging is enabled on organization-owned managed profile devices, only events
+ * happening inside the managed profile will be visible.
*/
public static final int TAG_CERT_AUTHORITY_REMOVED =
SecurityLogTags.SECURITY_CERT_AUTHORITY_REMOVED;
@@ -422,6 +448,9 @@
* {@link SecurityEvent#getData()}:
* <li> [0] alias of the key ({@code String})
* <li> [1] owner application uid ({@code Integer}).
+ *
+ * If security logging is enabled on organization-owned managed profile devices, only events
+ * happening inside the managed profile will be visible.
*/
public static final int TAG_KEY_INTEGRITY_VIOLATION =
SecurityLogTags.SECURITY_KEY_INTEGRITY_VIOLATION;
@@ -535,6 +564,16 @@
return mEvent.getData();
}
+ /** @hide */
+ public int getIntegerData(int index) {
+ return (Integer) ((Object[]) mEvent.getData())[index];
+ }
+
+ /** @hide */
+ public String getStringData(int index) {
+ return (String) ((Object[]) mEvent.getData())[index];
+ }
+
/**
* @hide
*/
@@ -554,7 +593,7 @@
* Returns severity level for the event.
*/
public @SecurityLogLevel int getLogLevel() {
- switch (mEvent.getTag()) {
+ switch (getTag()) {
case TAG_ADB_SHELL_INTERACTIVE:
case TAG_ADB_SHELL_CMD:
case TAG_SYNC_RECV_FILE:
@@ -608,6 +647,75 @@
return array.length >= 1 && array[0] instanceof Integer && (Integer) array[0] != 0;
}
+ /**
+ * Returns a copy of the security event suitable to be consumed by the provided user.
+ * This method will either return the original event itself if the event does not contain
+ * any sensitive data; or a copy of itself but with sensitive information redacted; or
+ * {@code null} if the entire event should not be accessed by the given user.
+ *
+ * @param accessingUser which user this security event is to be accessed, must be a
+ * concrete user id.
+ * @hide
+ */
+ public SecurityEvent redact(int accessingUser) {
+ // Which user the event is associated with, for the purpose of log redaction.
+ final int userId;
+ switch (getTag()) {
+ case SecurityLog.TAG_ADB_SHELL_CMD:
+ return new SecurityEvent(getId(), mEvent.withNewData("").getBytes());
+ case SecurityLog.TAG_MEDIA_MOUNT:
+ case SecurityLog.TAG_MEDIA_UNMOUNT:
+ // Partial redaction
+ String mountPoint;
+ try {
+ mountPoint = getStringData(0);
+ } catch (Exception e) {
+ return null;
+ }
+ return new SecurityEvent(getId(),
+ mEvent.withNewData(new Object[] {mountPoint, ""}).getBytes());
+ case SecurityLog.TAG_APP_PROCESS_START:
+ try {
+ userId = UserHandle.getUserId(getIntegerData(2));
+ } catch (Exception e) {
+ return null;
+ }
+ break;
+ case SecurityLog.TAG_CERT_AUTHORITY_INSTALLED:
+ case SecurityLog.TAG_CERT_AUTHORITY_REMOVED:
+ try {
+ userId = getIntegerData(2);
+ } catch (Exception e) {
+ return null;
+ }
+ break;
+ case SecurityLog.TAG_KEY_GENERATED:
+ case SecurityLog.TAG_KEY_IMPORT:
+ case SecurityLog.TAG_KEY_DESTRUCTION:
+ try {
+ userId = UserHandle.getUserId(getIntegerData(2));
+ } catch (Exception e) {
+ return null;
+ }
+ break;
+ case SecurityLog.TAG_KEY_INTEGRITY_VIOLATION:
+ try {
+ userId = UserHandle.getUserId(getIntegerData(1));
+ } catch (Exception e) {
+ return null;
+ }
+ break;
+ default:
+ userId = UserHandle.USER_NULL;
+ }
+ // If the event is not user-specific, or matches the accessing user, return it
+ // unmodified, else redact by returning null
+ if (userId == UserHandle.USER_NULL || accessingUser == userId) {
+ return this;
+ } else {
+ return null;
+ }
+ }
@Override
public int describeContents() {
@@ -657,6 +765,30 @@
return other != null && mEvent.equals(other.mEvent);
}
}
+
+ /**
+ * Redacts events in-place according to which user will consume the events.
+ *
+ * @param accessingUser which user will consume the redacted events, or UserHandle.USER_ALL if
+ * redaction should be skipped.
+ * @hide
+ */
+ public static void redactEvents(ArrayList<SecurityEvent> logList, int accessingUser) {
+ if (accessingUser == UserHandle.USER_ALL) return;
+ int end = 0;
+ for (int i = 0; i < logList.size(); i++) {
+ SecurityEvent event = logList.get(i);
+ event = event.redact(accessingUser);
+ if (event != null) {
+ logList.set(end, event);
+ end++;
+ }
+ }
+ for (int i = logList.size() - 1; i >= end; i--) {
+ logList.remove(i);
+ }
+ }
+
/**
* Retrieve all security logs and return immediately.
* @hide
diff --git a/core/java/android/app/admin/SecurityLogTags.logtags b/core/java/android/app/admin/SecurityLogTags.logtags
index 4e67fe2..100fd4c 100644
--- a/core/java/android/app/admin/SecurityLogTags.logtags
+++ b/core/java/android/app/admin/SecurityLogTags.logtags
@@ -33,8 +33,8 @@
210026 security_key_destroyed (success|1),(key_id|3),(uid|1)
210027 security_user_restriction_added (package|3),(admin_user|1),(restriction|3)
210028 security_user_restriction_removed (package|3),(admin_user|1),(restriction|3)
-210029 security_cert_authority_installed (success|1),(subject|3)
-210030 security_cert_authority_removed (success|1),(subject|3)
+210029 security_cert_authority_installed (success|1),(subject|3),(target_user|1)
+210030 security_cert_authority_removed (success|1),(subject|3),(target_user|1)
210031 security_crypto_self_test_completed (success|1)
210032 security_key_integrity_violation (key_id|3),(uid|1)
210033 security_cert_validation_failure (reason|3)
diff --git a/core/java/android/app/trust/IStrongAuthTracker.aidl b/core/java/android/app/trust/IStrongAuthTracker.aidl
index 36c71bf..6d54396 100644
--- a/core/java/android/app/trust/IStrongAuthTracker.aidl
+++ b/core/java/android/app/trust/IStrongAuthTracker.aidl
@@ -23,4 +23,5 @@
*/
oneway interface IStrongAuthTracker {
void onStrongAuthRequiredChanged(int strongAuthRequired, int userId);
+ void onIsNonStrongBiometricAllowedChanged(boolean allowed, int userId);
}
\ No newline at end of file
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index b5f4f80..8a89840 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -95,9 +95,9 @@
void registerShortcutChangeCallback(String callingPackage, long changedSince,
String packageName, in List shortcutIds, in List<LocusId> locusIds,
- in ComponentName componentName, int flags, in IShortcutChangeCallback callback,
- int callbackId);
- void unregisterShortcutChangeCallback(String callingPackage, int callbackId);
+ in ComponentName componentName, int flags, in IShortcutChangeCallback callback);
+ void unregisterShortcutChangeCallback(String callingPackage,
+ in IShortcutChangeCallback callback);
void cacheShortcuts(String callingPackage, String packageName, in List<String> shortcutIds,
in UserHandle user);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 5aa0208..86242fd 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -17,6 +17,7 @@
package android.content.pm;
import static android.Manifest.permission;
+
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -161,7 +162,7 @@
private final List<CallbackMessageHandler> mCallbacks = new ArrayList<>();
private final List<SessionCallbackDelegate> mDelegates = new ArrayList<>();
- private final Map<Integer, Pair<Executor, ShortcutChangeCallback>>
+ private final Map<ShortcutChangeCallback, Pair<Executor, IShortcutChangeCallback>>
mShortcutChangeCallbacks = new HashMap<>();
/**
@@ -549,8 +550,8 @@
android.content.pm.IShortcutChangeCallback.Stub {
private final WeakReference<Pair<Executor, ShortcutChangeCallback>> mRemoteReferences;
- ShortcutChangeCallbackProxy(Pair<Executor, ShortcutChangeCallback> remoteReferences) {
- mRemoteReferences = new WeakReference<>(remoteReferences);
+ ShortcutChangeCallbackProxy(Executor executor, ShortcutChangeCallback callback) {
+ mRemoteReferences = new WeakReference<>(new Pair<>(executor, callback));
}
@Override
@@ -1753,14 +1754,12 @@
Objects.requireNonNull(executor, "Executor cannot be null");
synchronized (mShortcutChangeCallbacks) {
- final int callbackId = callback.hashCode();
- final Pair<Executor, ShortcutChangeCallback> state = new Pair<>(executor, callback);
- mShortcutChangeCallbacks.put(callbackId, state);
+ IShortcutChangeCallback proxy = new ShortcutChangeCallbackProxy(executor, callback);
+ mShortcutChangeCallbacks.put(callback, new Pair<>(executor, proxy));
try {
mService.registerShortcutChangeCallback(mContext.getPackageName(),
query.mChangedSince, query.mPackage, query.mShortcutIds, query.mLocusIds,
- query.mActivity, query.mQueryFlags, new ShortcutChangeCallbackProxy(state),
- callbackId);
+ query.mActivity, query.mQueryFlags, proxy);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1779,12 +1778,10 @@
Objects.requireNonNull(callback, "Callback cannot be null");
synchronized (mShortcutChangeCallbacks) {
- final int callbackId = callback.hashCode();
- if (mShortcutChangeCallbacks.containsKey(callbackId)) {
- mShortcutChangeCallbacks.remove(callbackId);
+ if (mShortcutChangeCallbacks.containsKey(callback)) {
+ IShortcutChangeCallback proxy = mShortcutChangeCallbacks.remove(callback).second;
try {
- mService.unregisterShortcutChangeCallback(mContext.getPackageName(),
- callbackId);
+ mService.unregisterShortcutChangeCallback(mContext.getPackageName(), proxy);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index 3a93421..a50ce92 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -65,6 +65,9 @@
public abstract void addListener(@NonNull ShortcutChangeListener listener);
+ public abstract void addShortcutChangeCallback(
+ @NonNull LauncherApps.ShortcutChangeCallback callback);
+
public abstract int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage,
@NonNull String packageName, @NonNull String shortcutId, int userId);
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 3a0660d..9b9889e 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -97,8 +97,10 @@
}
@Override // binder call
- public void onAuthenticationSucceeded(long deviceId, Face face, int userId) {
- mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, 0, face).sendToTarget();
+ public void onAuthenticationSucceeded(long deviceId, Face face, int userId,
+ boolean isStrongBiometric) {
+ mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, isStrongBiometric ? 1 : 0,
+ face).sendToTarget();
}
@Override // binder call
@@ -814,6 +816,7 @@
private Face mFace;
private CryptoObject mCryptoObject;
private int mUserId;
+ private boolean mIsStrongBiometric;
/**
* Authentication result
@@ -822,10 +825,12 @@
* @param face the recognized face data, if allowed.
* @hide
*/
- public AuthenticationResult(CryptoObject crypto, Face face, int userId) {
+ public AuthenticationResult(CryptoObject crypto, Face face, int userId,
+ boolean isStrongBiometric) {
mCryptoObject = crypto;
mFace = face;
mUserId = userId;
+ mIsStrongBiometric = isStrongBiometric;
}
/**
@@ -857,6 +862,16 @@
public int getUserId() {
return mUserId;
}
+
+ /**
+ * Check whether the strength of the face modality associated with this operation is strong
+ * (i.e. not weak or convenience).
+ *
+ * @hide
+ */
+ public boolean isStrongBiometric() {
+ return mIsStrongBiometric;
+ }
}
/**
@@ -1056,7 +1071,8 @@
msg.arg2 /* vendorCode */);
break;
case MSG_AUTHENTICATION_SUCCEEDED:
- sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */);
+ sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */,
+ msg.arg2 == 1 /* isStrongBiometric */);
break;
case MSG_AUTHENTICATION_FAILED:
sendAuthenticatedFailed();
@@ -1133,10 +1149,10 @@
}
}
- private void sendAuthenticatedSucceeded(Face face, int userId) {
+ private void sendAuthenticatedSucceeded(Face face, int userId, boolean isStrongBiometric) {
if (mAuthenticationCallback != null) {
final AuthenticationResult result =
- new AuthenticationResult(mCryptoObject, face, userId);
+ new AuthenticationResult(mCryptoObject, face, userId, isStrongBiometric);
mAuthenticationCallback.onAuthenticationSucceeded(result);
}
}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 8ba2473..6318274 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -110,4 +110,7 @@
void getFeature(int userId, int feature, IFaceServiceReceiver receiver, String opPackageName);
void userActivity();
+
+ // Initialize the OEM configured biometric strength
+ void initConfiguredStrength(int strength);
}
diff --git a/core/java/android/hardware/face/IFaceServiceReceiver.aidl b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
index 10f9c43..7582308 100644
--- a/core/java/android/hardware/face/IFaceServiceReceiver.aidl
+++ b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
@@ -24,7 +24,8 @@
oneway interface IFaceServiceReceiver {
void onEnrollResult(long deviceId, int faceId, int remaining);
void onAcquired(long deviceId, int acquiredInfo, int vendorCode);
- void onAuthenticationSucceeded(long deviceId, in Face face, int userId);
+ void onAuthenticationSucceeded(long deviceId, in Face face, int userId,
+ boolean isStrongBiometric);
void onAuthenticationFailed(long deviceId);
void onError(long deviceId, int error, int vendorCode);
void onRemoved(long deviceId, int faceId, int remaining);
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index f301a5c..dc8ea5e 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -176,6 +176,7 @@
private Fingerprint mFingerprint;
private CryptoObject mCryptoObject;
private int mUserId;
+ private boolean mIsStrongBiometric;
/**
* Authentication result
@@ -184,10 +185,12 @@
* @param fingerprint the recognized fingerprint data, if allowed.
* @hide
*/
- public AuthenticationResult(CryptoObject crypto, Fingerprint fingerprint, int userId) {
+ public AuthenticationResult(CryptoObject crypto, Fingerprint fingerprint, int userId,
+ boolean isStrongBiometric) {
mCryptoObject = crypto;
mFingerprint = fingerprint;
mUserId = userId;
+ mIsStrongBiometric = isStrongBiometric;
}
/**
@@ -211,6 +214,15 @@
* @hide
*/
public int getUserId() { return mUserId; }
+
+ /**
+ * Check whether the strength of the fingerprint modality associated with this operation is
+ * strong (i.e. not weak or convenience).
+ * @hide
+ */
+ public boolean isStrongBiometric() {
+ return mIsStrongBiometric;
+ }
};
/**
@@ -833,7 +845,8 @@
msg.arg2 /* vendorCode */);
break;
case MSG_AUTHENTICATION_SUCCEEDED:
- sendAuthenticatedSucceeded((Fingerprint) msg.obj, msg.arg1 /* userId */);
+ sendAuthenticatedSucceeded((Fingerprint) msg.obj, msg.arg1 /* userId */,
+ msg.arg2 == 1 /* isStrongBiometric */);
break;
case MSG_AUTHENTICATION_FAILED:
sendAuthenticatedFailed();
@@ -890,10 +903,10 @@
}
}
- private void sendAuthenticatedSucceeded(Fingerprint fp, int userId) {
+ private void sendAuthenticatedSucceeded(Fingerprint fp, int userId, boolean isStrongBiometric) {
if (mAuthenticationCallback != null) {
final AuthenticationResult result =
- new AuthenticationResult(mCryptoObject, fp, userId);
+ new AuthenticationResult(mCryptoObject, fp, userId, isStrongBiometric);
mAuthenticationCallback.onAuthenticationSucceeded(result);
}
}
@@ -1078,8 +1091,10 @@
}
@Override // binder call
- public void onAuthenticationSucceeded(long deviceId, Fingerprint fp, int userId) {
- mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, 0, fp).sendToTarget();
+ public void onAuthenticationSucceeded(long deviceId, Fingerprint fp, int userId,
+ boolean isStrongBiometric) {
+ mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, isStrongBiometric ? 1 : 0,
+ fp).sendToTarget();
}
@Override // binder call
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index f2ffd08d..0285448 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -113,4 +113,7 @@
// Removes a callback set by addClientActiveCallback
void removeClientActiveCallback(IFingerprintClientActiveCallback callback);
+
+ // Initialize the OEM configured biometric strength
+ void initConfiguredStrength(int strength);
}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
index cf1c94e..4412cee 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
@@ -24,7 +24,8 @@
oneway interface IFingerprintServiceReceiver {
void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining);
void onAcquired(long deviceId, int acquiredInfo, int vendorCode);
- void onAuthenticationSucceeded(long deviceId, in Fingerprint fp, int userId);
+ void onAuthenticationSucceeded(long deviceId, in Fingerprint fp, int userId,
+ boolean isStrongBiometric);
void onAuthenticationFailed(long deviceId);
void onError(long deviceId, int error, int vendorCode);
void onRemoved(long deviceId, int fingerId, int groupId, int remaining);
diff --git a/core/java/android/hardware/iris/IIrisService.aidl b/core/java/android/hardware/iris/IIrisService.aidl
index 8cf3c13..5ef0a0c 100644
--- a/core/java/android/hardware/iris/IIrisService.aidl
+++ b/core/java/android/hardware/iris/IIrisService.aidl
@@ -21,4 +21,6 @@
* @hide
*/
interface IIrisService {
-}
\ No newline at end of file
+ // Initialize the OEM configured biometric strength
+ void initConfiguredStrength(int strength);
+}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 84fd580..0f2060a 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -63,6 +63,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
/**
* Manages users and user details on a multi-user system. There are two major categories of
@@ -2717,10 +2718,11 @@
Manifest.permission.CREATE_USERS})
@UserHandleAware
public @Nullable UserHandle createProfile(@NonNull String name, @NonNull String userType,
- @Nullable String[] disallowedPackages) throws UserOperationException {
+ @NonNull Set<String> disallowedPackages) throws UserOperationException {
try {
return mService.createProfileForUserWithThrow(name, userType, 0,
- mUserId, disallowedPackages).getUserHandle();
+ mUserId, disallowedPackages.toArray(
+ new String[disallowedPackages.size()])).getUserHandle();
} catch (ServiceSpecificException e) {
return returnNullOrThrowUserOperationException(e,
mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R);
@@ -3354,19 +3356,46 @@
}
/**
- * Returns a list of ids for profiles associated with the context user including the user
- * itself.
+ * Returns a list of ids for enabled profiles associated with the context user including the
+ * user itself.
*
- * @param enabledOnly whether to return only {@link UserInfo#isEnabled() enabled} profiles
* @return A non-empty list of UserHandles associated with the calling user.
- *
* @hide
*/
@SystemApi
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.CREATE_USERS}, conditional = true)
@UserHandleAware
- public @NonNull List<UserHandle> getUserProfiles(boolean enabledOnly) {
+ public @NonNull List<UserHandle> getEnabledProfiles() {
+ return getProfiles(true);
+ }
+
+ /**
+ * Returns a list of ids for all profiles associated with the context user including the user
+ * itself.
+ *
+ * @return A non-empty list of UserHandles associated with the calling user.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS}, conditional = true)
+ @UserHandleAware
+ public @NonNull List<UserHandle> getAllProfiles() {
+ return getProfiles(false);
+ }
+
+ /**
+ * Returns a list of ids for profiles associated with the context user including the user
+ * itself.
+ *
+ * @param enabledOnly whether to return only {@link UserInfo#isEnabled() enabled} profiles
+ * @return A non-empty list of UserHandles associated with the calling user.
+ */
+ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS}, conditional = true)
+ @UserHandleAware
+ private @NonNull List<UserHandle> getProfiles(boolean enabledOnly) {
final int[] userIds = getProfileIds(mUserId, enabledOnly);
final List<UserHandle> result = new ArrayList<>(userIds.length);
for (int userId : userIds) {
diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java
index c9dc05b..ead4e46 100644
--- a/core/java/android/util/EventLog.java
+++ b/core/java/android/util/EventLog.java
@@ -16,6 +16,8 @@
package android.util;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
@@ -62,7 +64,7 @@
private Exception mLastWtf;
// Layout of event log entry received from Android logger.
- // see system/core/include/log/log.h
+ // see system/core/liblog/include/log/log_read.h
private static final int LENGTH_OFFSET = 0;
private static final int HEADER_SIZE_OFFSET = 2;
private static final int PROCESS_OFFSET = 4;
@@ -73,7 +75,7 @@
// Layout for event log v1 format, v2 and v3 use HEADER_SIZE_OFFSET
private static final int V1_PAYLOAD_START = 20;
- private static final int DATA_OFFSET = 4;
+ private static final int TAG_LENGTH = 4;
// Value types
private static final byte INT_TYPE = 0;
@@ -121,26 +123,26 @@
/** @return the type tag code of the entry */
public int getTag() {
- int offset = mBuffer.getShort(HEADER_SIZE_OFFSET);
- if (offset == 0) {
- offset = V1_PAYLOAD_START;
- }
- return mBuffer.getInt(offset);
+ return mBuffer.getInt(getHeaderSize());
}
+ private int getHeaderSize() {
+ int length = mBuffer.getShort(HEADER_SIZE_OFFSET);
+ if (length != 0) {
+ return length;
+ }
+ return V1_PAYLOAD_START;
+ }
/** @return one of Integer, Long, Float, String, null, or Object[] of same. */
public synchronized Object getData() {
try {
- int offset = mBuffer.getShort(HEADER_SIZE_OFFSET);
- if (offset == 0) {
- offset = V1_PAYLOAD_START;
- }
+ int offset = getHeaderSize();
mBuffer.limit(offset + mBuffer.getShort(LENGTH_OFFSET));
- if ((offset + DATA_OFFSET) >= mBuffer.limit()) {
+ if ((offset + TAG_LENGTH) >= mBuffer.limit()) {
// no payload
return null;
}
- mBuffer.position(offset + DATA_OFFSET); // Just after the tag.
+ mBuffer.position(offset + TAG_LENGTH); // Just after the tag.
return decodeObject();
} catch (IllegalArgumentException e) {
Log.wtf(TAG, "Illegal entry payload: tag=" + getTag(), e);
@@ -153,6 +155,28 @@
}
}
+ /**
+ * Construct a new EventLog object from the current object, copying all log metadata
+ * but replacing the actual payload with the content provided.
+ * @hide
+ */
+ public Event withNewData(@Nullable Object object) {
+ byte[] payload = encodeObject(object);
+ if (payload.length > 65535 - TAG_LENGTH) {
+ throw new IllegalArgumentException("Payload too long");
+ }
+ int headerLength = getHeaderSize();
+ byte[] newBytes = new byte[headerLength + TAG_LENGTH + payload.length];
+ // Copy header (including the 4 bytes of tag integer at the beginning of payload)
+ System.arraycopy(mBuffer.array(), 0, newBytes, 0, headerLength + TAG_LENGTH);
+ // Fill in encoded objects
+ System.arraycopy(payload, 0, newBytes, headerLength + TAG_LENGTH, payload.length);
+ Event result = new Event(newBytes);
+ // Patch payload length in header
+ result.mBuffer.putShort(LENGTH_OFFSET, (short) (payload.length + TAG_LENGTH));
+ return result;
+ }
+
/** @return the loggable item at the current position in mBuffer. */
private Object decodeObject() {
byte type = mBuffer.get();
@@ -190,6 +214,66 @@
}
}
+ private static @NonNull byte[] encodeObject(@Nullable Object object) {
+ if (object == null) {
+ return new byte[0];
+ }
+ if (object instanceof Integer) {
+ return ByteBuffer.allocate(1 + 4)
+ .order(ByteOrder.nativeOrder())
+ .put(INT_TYPE)
+ .putInt((Integer) object)
+ .array();
+ } else if (object instanceof Long) {
+ return ByteBuffer.allocate(1 + 8)
+ .order(ByteOrder.nativeOrder())
+ .put(LONG_TYPE)
+ .putLong((Long) object)
+ .array();
+ } else if (object instanceof Float) {
+ return ByteBuffer.allocate(1 + 4)
+ .order(ByteOrder.nativeOrder())
+ .put(FLOAT_TYPE)
+ .putFloat((Float) object)
+ .array();
+ } else if (object instanceof String) {
+ String string = (String) object;
+ byte[] bytes;
+ try {
+ bytes = string.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ bytes = new byte[0];
+ }
+ return ByteBuffer.allocate(1 + 4 + bytes.length)
+ .order(ByteOrder.nativeOrder())
+ .put(STRING_TYPE)
+ .putInt(bytes.length)
+ .put(bytes)
+ .array();
+ } else if (object instanceof Object[]) {
+ Object[] objects = (Object[]) object;
+ if (objects.length > 255) {
+ throw new IllegalArgumentException("Object array too long");
+ }
+ byte[][] bytes = new byte[objects.length][];
+ int totalLength = 0;
+ for (int i = 0; i < objects.length; i++) {
+ bytes[i] = encodeObject(objects[i]);
+ totalLength += bytes[i].length;
+ }
+ ByteBuffer buffer = ByteBuffer.allocate(1 + 1 + totalLength)
+ .order(ByteOrder.nativeOrder())
+ .put(LIST_TYPE)
+ .put((byte) objects.length);
+ for (int i = 0; i < objects.length; i++) {
+ buffer.put(bytes[i]);
+ }
+ return buffer.array();
+ } else {
+ throw new IllegalArgumentException("Unknown object type " + object);
+ }
+ }
+
/** @hide */
public static Event fromBytes(byte[] data) {
return new Event(data);
diff --git a/core/java/com/android/internal/app/ResolverViewPager.java b/core/java/com/android/internal/app/ResolverViewPager.java
index 84fed9c..8239018 100644
--- a/core/java/com/android/internal/app/ResolverViewPager.java
+++ b/core/java/com/android/internal/app/ResolverViewPager.java
@@ -24,10 +24,10 @@
import com.android.internal.widget.ViewPager;
/**
- * A {@link ViewPager} which wraps around its first child's height and has swiping disabled.
+ * A {@link ViewPager} which wraps around its first child's height.
* <p>Normally {@link ViewPager} instances expand their height to cover all remaining space in
* the layout.
- * <p>This class is used for the intent resolver and share sheet's tabbed view.
+ * <p>This class is used for the intent resolver's tabbed view.
*/
public class ResolverViewPager extends ViewPager {
@@ -70,14 +70,4 @@
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- return false;
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- return false;
- }
}
diff --git a/core/java/com/android/internal/content/om/OverlayConfig.java b/core/java/com/android/internal/content/om/OverlayConfig.java
index ffa347c..72f16e4 100644
--- a/core/java/com/android/internal/content/om/OverlayConfig.java
+++ b/core/java/com/android/internal/content/om/OverlayConfig.java
@@ -186,13 +186,6 @@
*/
@NonNull
public static OverlayConfig getZygoteInstance() {
- if (Process.myUid() != Process.ROOT_UID) {
- // Scan the overlays in the zygote process to generate configuration settings for
- // overlays on the system image. Do not cache this instance so OverlayConfig will not
- // be present in applications by default.
- throw new IllegalStateException("Can only be invoked in the root process");
- }
-
Trace.traceBegin(Trace.TRACE_TAG_RRO, "OverlayConfig#getZygoteInstance");
try {
return new OverlayConfig(null /* rootDirectory */, OverlayScanner::new,
@@ -209,13 +202,12 @@
*/
@NonNull
public static OverlayConfig initializeSystemInstance(PackageProvider packageProvider) {
- if (Process.myUid() != Process.SYSTEM_UID) {
- throw new IllegalStateException("Can only be invoked in the system process");
- }
-
Trace.traceBegin(Trace.TRACE_TAG_RRO, "OverlayConfig#initializeSystemInstance");
- sInstance = new OverlayConfig(null, null, packageProvider);
- Trace.traceEnd(Trace.TRACE_TAG_RRO);
+ try {
+ sInstance = new OverlayConfig(null, null, packageProvider);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_RRO);
+ }
return sInstance;
}
@@ -373,10 +365,6 @@
*/
@NonNull
public String[] createImmutableFrameworkIdmapsInZygote() {
- if (Process.myUid() != Process.ROOT_UID) {
- throw new IllegalStateException("This method can only be called from the root process");
- }
-
final String targetPath = "/system/framework/framework-res.apk";
final ArrayList<String> idmapPaths = new ArrayList<>();
final ArrayList<IdmapInvocation> idmapInvocations =
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index e24e982..e35fda1 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -57,6 +57,8 @@
void registerStrongAuthTracker(in IStrongAuthTracker tracker);
void unregisterStrongAuthTracker(in IStrongAuthTracker tracker);
void requireStrongAuth(int strongAuthReason, int userId);
+ void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId);
+ void scheduleNonStrongBiometricIdleTimeout(int userId);
void systemReady();
void userPresent(int userId);
int getStrongAuthForUser(int userId);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 864429c..d9b2902 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -49,6 +49,7 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
+import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.SparseLongArray;
@@ -1382,6 +1383,22 @@
}
}
+ public void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId) {
+ try {
+ getLockSettings().reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not report successful biometric unlock", e);
+ }
+ }
+
+ public void scheduleNonStrongBiometricIdleTimeout(int userId) {
+ try {
+ getLockSettings().scheduleNonStrongBiometricIdleTimeout(userId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not schedule non-strong biometric idle timeout", e);
+ }
+ }
+
/**
* @see StrongAuthTracker#getStrongAuthForUser
*/
@@ -1557,7 +1574,8 @@
SOME_AUTH_REQUIRED_AFTER_USER_REQUEST,
STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
STRONG_AUTH_REQUIRED_AFTER_TIMEOUT,
- STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN})
+ STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
+ STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT})
@Retention(RetentionPolicy.SOURCE)
public @interface StrongAuthFlags {}
@@ -1604,6 +1622,12 @@
public static final int STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE = 0x40;
/**
+ * Strong authentication is required because it hasn't been used for a time after a
+ * non-strong biometric (i.e. weak or convenience biometric) is used to unlock device.
+ */
+ public static final int STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT = 0x80;
+
+ /**
* Strong auth flags that do not prevent biometric methods from being accepted as auth.
* If any other flags are set, biometric authentication is disabled.
*/
@@ -1614,6 +1638,10 @@
private final H mHandler;
private final int mDefaultStrongAuthFlags;
+ private final SparseBooleanArray mIsNonStrongBiometricAllowedForUser =
+ new SparseBooleanArray();
+ private final boolean mDefaultIsNonStrongBiometricAllowed = true;
+
public StrongAuthTracker(Context context) {
this(context, Looper.myLooper());
}
@@ -1657,8 +1685,21 @@
* @return true if unlocking with a biometric method alone is allowed for {@code userId}
* by the current strong authentication requirements.
*/
- public boolean isBiometricAllowedForUser(int userId) {
- return (getStrongAuthForUser(userId) & ~ALLOWING_BIOMETRIC) == 0;
+ public boolean isBiometricAllowedForUser(boolean isStrongBiometric, int userId) {
+ boolean allowed = ((getStrongAuthForUser(userId) & ~ALLOWING_BIOMETRIC) == 0);
+ if (!isStrongBiometric) {
+ allowed &= isNonStrongBiometricAllowedAfterIdleTimeout(userId);
+ }
+ return allowed;
+ }
+
+ /**
+ * @return true if unlocking with a non-strong (i.e. weak or convenience) biometric method
+ * alone is allowed for {@code userId}, otherwise returns false.
+ */
+ public boolean isNonStrongBiometricAllowedAfterIdleTimeout(int userId) {
+ return mIsNonStrongBiometricAllowedForUser.get(userId,
+ mDefaultIsNonStrongBiometricAllowed);
}
/**
@@ -1667,6 +1708,12 @@
public void onStrongAuthRequiredChanged(int userId) {
}
+ /**
+ * Called when whether non-strong biometric is allowed for {@code userId} changed.
+ */
+ public void onIsNonStrongBiometricAllowedChanged(int userId) {
+ }
+
protected void handleStrongAuthRequiredChanged(@StrongAuthFlags int strongAuthFlags,
int userId) {
int oldValue = getStrongAuthForUser(userId);
@@ -1680,6 +1727,18 @@
}
}
+ protected void handleIsNonStrongBiometricAllowedChanged(boolean allowed,
+ int userId) {
+ boolean oldValue = isNonStrongBiometricAllowedAfterIdleTimeout(userId);
+ if (allowed != oldValue) {
+ if (allowed == mDefaultIsNonStrongBiometricAllowed) {
+ mIsNonStrongBiometricAllowedForUser.delete(userId);
+ } else {
+ mIsNonStrongBiometricAllowedForUser.put(userId, allowed);
+ }
+ onIsNonStrongBiometricAllowedChanged(userId);
+ }
+ }
protected final IStrongAuthTracker.Stub mStub = new IStrongAuthTracker.Stub() {
@Override
@@ -1688,10 +1747,17 @@
mHandler.obtainMessage(H.MSG_ON_STRONG_AUTH_REQUIRED_CHANGED,
strongAuthFlags, userId).sendToTarget();
}
+
+ @Override
+ public void onIsNonStrongBiometricAllowedChanged(boolean allowed, int userId) {
+ mHandler.obtainMessage(H.MSG_ON_IS_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED,
+ allowed ? 1 : 0, userId).sendToTarget();
+ }
};
private class H extends Handler {
static final int MSG_ON_STRONG_AUTH_REQUIRED_CHANGED = 1;
+ static final int MSG_ON_IS_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED = 2;
public H(Looper looper) {
super(looper);
@@ -1703,6 +1769,10 @@
case MSG_ON_STRONG_AUTH_REQUIRED_CHANGED:
handleStrongAuthRequiredChanged(msg.arg1, msg.arg2);
break;
+ case MSG_ON_IS_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED:
+ handleIsNonStrongBiometricAllowedChanged(msg.arg1 == 1 /* allowed */,
+ msg.arg2);
+ break;
}
}
}
diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml
index c0de693..5676049 100644
--- a/core/res/res/layout/chooser_grid.xml
+++ b/core/res/res/layout/chooser_grid.xml
@@ -90,7 +90,7 @@
android:id="@android:id/tabcontent"
android:layout_width="match_parent"
android:layout_height="wrap_content">
- <com.android.internal.app.ResolverViewPager
+ <com.android.internal.widget.ViewPager
android:id="@+id/profile_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
diff --git a/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java b/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
index 9f0af60..f0e70a5 100644
--- a/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
+++ b/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
@@ -92,7 +92,7 @@
@Test
public void testLoadAnimatedImage() {
assertNotNull("Can't find animated image",
- mShortcutInfo.loadAnimatedImage(mPackageManager));
+ mShortcutInfo.loadAnimatedImage(mTargetContext));
}
@Test
diff --git a/core/tests/coretests/src/android/util/EventLogTest.java b/core/tests/coretests/src/android/util/EventLogTest.java
new file mode 100644
index 0000000..94e72c4
--- /dev/null
+++ b/core/tests/coretests/src/android/util/EventLogTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.util;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.util.EventLog.Event;
+
+import junit.framework.AssertionFailedError;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Unit tests for {@link android.util.EventLog} */
+public class EventLogTest {
+
+ @Test
+ public void testWithNewData() throws Throwable {
+ Event event = createEvent(() -> {
+ EventLog.writeEvent(314, 123);
+ }, 314);
+
+ assertTrue(event.withNewData(12345678L).getData().equals(12345678L));
+ assertTrue(event.withNewData(2.718f).getData().equals(2.718f));
+ assertTrue(event.withNewData("test string").getData().equals("test string"));
+
+ Object[] objects = ((Object[]) event.withNewData(
+ new Object[] {111, 2.22f, 333L, "444"}).getData());
+ assertEquals(4, objects.length);
+ assertTrue(objects[0].equals(111));
+ assertTrue(objects[1].equals(2.22f));
+ assertTrue(objects[2].equals(333L));
+ assertTrue(objects[3].equals("444"));
+ }
+
+ /**
+ * Creates an Event object. Only the native code has the serialization and deserialization logic
+ * so need to actually emit a real log in order to generate the object.
+ */
+ private Event createEvent(Runnable generator, int expectedTag) throws Exception {
+ Long markerData = System.currentTimeMillis();
+ EventLog.writeEvent(expectedTag, markerData);
+ generator.run();
+
+ List<Event> events = new ArrayList<>();
+ // Give the message some time to show up in the log
+ Thread.sleep(20);
+ EventLog.readEvents(new int[] {expectedTag}, events);
+ for (int i = 0; i < events.size() - 1; i++) {
+ if (markerData.equals(events.get(i).getData())) {
+ return events.get(i + 1);
+ }
+ }
+ throw new AssertionFailedError("Unable to locate marker event");
+ }
+}
diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java
index c12159c..5858e39 100644
--- a/graphics/java/android/graphics/Outline.java
+++ b/graphics/java/android/graphics/Outline.java
@@ -280,7 +280,10 @@
* {@link android.os.Build.VERSION_CODES#Q}, it is no longer required to be
* convex.
*
- * @deprecated The path is no longer required to be convex. Use {@link #setPath} instead.
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#Q}, the restriction
+ * that the path must be convex is removed. However, the API is misnamed until
+ * {@link android.os.Build.VERSION_CODES#R}, when {@link #setPath} is
+ * introduced. Use {@link #setPath} instead.
*/
@Deprecated
public void setConvexPath(@NonNull Path convexPath) {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 0b825f6..383202b 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1654,6 +1654,12 @@
* @hide
* Interface to be notified of changes in the preferred audio device set for a given audio
* strategy.
+ * <p>Note that this listener will only be invoked whenever
+ * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)} or
+ * {@link #removePreferredDeviceForStrategy(AudioProductStrategy)} causes a change in
+ * preferred device. It will not be invoked directly after registration with
+ * {@link #addOnPreferredDeviceForStrategyChangedListener(Executor, OnPreferredDeviceForStrategyChangedListener)}
+ * to indicate which strategies had preferred devices at the time of registration.</p>
* @see #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)
* @see #removePreferredDeviceForStrategy(AudioProductStrategy)
* @see #getPreferredDeviceForStrategy(AudioProductStrategy)
diff --git a/media/java/android/media/IMediaRoute2ProviderService.aidl b/media/java/android/media/IMediaRoute2ProviderService.aidl
index cd0def3..6cd2547 100644
--- a/media/java/android/media/IMediaRoute2ProviderService.aidl
+++ b/media/java/android/media/IMediaRoute2ProviderService.aidl
@@ -29,13 +29,13 @@
// MediaRoute2ProviderService#MediaRoute2ProviderServiceStub for readability.
void setCallback(IMediaRoute2ProviderServiceCallback callback);
void updateDiscoveryPreference(in RouteDiscoveryPreference discoveryPreference);
- void setRouteVolume(String routeId, int volume);
+ void setRouteVolume(String routeId, int volume, long requestId);
void requestCreateSession(String packageName, String routeId, long requestId,
in @nullable Bundle sessionHints);
- void selectRoute(String sessionId, String routeId);
- void deselectRoute(String sessionId, String routeId);
- void transferToRoute(String sessionId, String routeId);
- void setSessionVolume(String sessionId, int volume);
- void releaseSession(String sessionId);
+ void selectRoute(String sessionId, String routeId, long requestId);
+ void deselectRoute(String sessionId, String routeId, long requestId);
+ void transferToRoute(String sessionId, String routeId, long requestId);
+ void setSessionVolume(String sessionId, int volume, long requestId);
+ void releaseSession(String sessionId, long requestId);
}
diff --git a/media/java/android/media/IMediaRoute2ProviderServiceCallback.aidl b/media/java/android/media/IMediaRoute2ProviderServiceCallback.aidl
index e35b0c4..ab42d75 100644
--- a/media/java/android/media/IMediaRoute2ProviderServiceCallback.aidl
+++ b/media/java/android/media/IMediaRoute2ProviderServiceCallback.aidl
@@ -31,4 +31,5 @@
void notifySessionCreationFailed(long requestId);
void notifySessionUpdated(in RoutingSessionInfo sessionInfo);
void notifySessionReleased(in RoutingSessionInfo sessionInfo);
+ void notifyRequestFailed(long requestId, int reason);
}
diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl
index ffad659..a2f9ee9 100644
--- a/media/java/android/media/IMediaRouter2Manager.aidl
+++ b/media/java/android/media/IMediaRouter2Manager.aidl
@@ -30,4 +30,5 @@
void notifyRoutesAdded(in List<MediaRoute2Info> routes);
void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
void notifyRoutesChanged(in List<MediaRoute2Info> routes);
+ void notifyRequestFailed(int requestId, int reason);
}
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index cbec323..c7cb07d 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -71,16 +71,17 @@
void registerManager(IMediaRouter2Manager manager, String packageName);
void unregisterManager(IMediaRouter2Manager manager);
void setRouteVolumeWithManager(IMediaRouter2Manager manager, in MediaRoute2Info route,
- int volume);
+ int volume, int requestId);
void requestCreateSessionWithManager(IMediaRouter2Manager manager, String packageName,
in @nullable MediaRoute2Info route, int requestId);
void selectRouteWithManager(IMediaRouter2Manager manager, String sessionId,
- in MediaRoute2Info route);
+ in MediaRoute2Info route, int requestId);
void deselectRouteWithManager(IMediaRouter2Manager manager, String sessionId,
- in MediaRoute2Info route);
+ in MediaRoute2Info route, int requestId);
void transferToRouteWithManager(IMediaRouter2Manager manager, String sessionId,
- in MediaRoute2Info route);
- void setSessionVolumeWithManager(IMediaRouter2Manager manager, String sessionId, int volume);
- void releaseSessionWithManager(IMediaRouter2Manager manager, String sessionId);
+ in MediaRoute2Info route, int requestId);
+ void setSessionVolumeWithManager(IMediaRouter2Manager manager, String sessionId, int volume,
+ int requestId);
+ void releaseSessionWithManager(IMediaRouter2Manager manager, String sessionId, int requestId);
}
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 38233fd..aa0eda1 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -19,6 +19,7 @@
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.annotation.CallSuper;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
@@ -37,6 +38,8 @@
import com.android.internal.annotations.GuardedBy;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -51,7 +54,7 @@
* Media apps which use {@link MediaRouter2} can request to play their media on the routes.
* </p><p>
* When {@link MediaRouter2 media router} wants to play media on a route,
- * {@link #onCreateSession(String, String, long, Bundle)} will be called to handle the request.
+ * {@link #onCreateSession(long, String, String, Bundle)} will be called to handle the request.
* A session can be considered as a group of currently selected routes for each connection.
* Create and manage the sessions by yourself, and notify the {@link RoutingSessionInfo
* session infos} when there are any changes.
@@ -61,6 +64,8 @@
* a {@link MediaRouter2 media router} by an application. See
* {@link #onDiscoveryPreferenceChanged(RouteDiscoveryPreference)} for the details.
* </p>
+ * Use {@link #notifyRequestFailed(long, int)} to notify the failure with previously received
+ * request ID.
*/
public abstract class MediaRoute2ProviderService extends Service {
private static final String TAG = "MR2ProviderService";
@@ -79,7 +84,53 @@
*
* @see #notifySessionCreated(RoutingSessionInfo, long)
*/
- public static final long REQUEST_ID_UNKNOWN = 0;
+ public static final long REQUEST_ID_NONE = 0;
+
+ /**
+ * The request has failed due to unknown reason.
+ *
+ * @see #notifyRequestFailed(long, int)
+ */
+ public static final int REASON_UNKNOWN_ERROR = 0;
+
+ /**
+ * The request has failed since this service rejected the request.
+ *
+ * @see #notifyRequestFailed(long, int)
+ */
+ public static final int REASON_REJECTED = 1;
+
+ /**
+ * The request has failed due to a network error.
+ *
+ * @see #notifyRequestFailed(long, int)
+ */
+ public static final int REASON_NETWORK_ERROR = 2;
+
+ /**
+ * The request has failed since the requested route is no longer available.
+ *
+ * @see #notifyRequestFailed(long, int)
+ */
+ public static final int REASON_ROUTE_NOT_AVAILABLE = 3;
+
+ /**
+ * The request has failed since the request is not valid. For example, selecting a route
+ * which is not selectable.
+ *
+ * @see #notifyRequestFailed(long, int)
+ */
+ public static final int REASON_INVALID_COMMAND = 4;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = "REASON_", value = {
+ REASON_UNKNOWN_ERROR, REASON_REJECTED, REASON_NETWORK_ERROR, REASON_ROUTE_NOT_AVAILABLE,
+ REASON_INVALID_COMMAND
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Reason {}
private final Handler mHandler;
private final Object mSessionLock = new Object();
@@ -116,20 +167,23 @@
/**
* Called when a volume setting is requested on a route of the provider
*
+ * @param requestId the id of this request
* @param routeId the id of the route
* @param volume the target volume
- * @see MediaRoute2Info#getVolumeMax()
+ * @see MediaRoute2Info.Builder#setVolume(int)
*/
- public abstract void onSetRouteVolume(@NonNull String routeId, int volume);
+ public abstract void onSetRouteVolume(long requestId, @NonNull String routeId, int volume);
/**
* Called when {@link MediaRouter2.RoutingController#setVolume(int)} is called on
* a routing session of the provider
*
+ * @param requestId the id of this request
* @param sessionId the id of the routing session
* @param volume the target volume
+ * @see RoutingSessionInfo.Builder#setVolume(int)
*/
- public abstract void onSetSessionVolume(@NonNull String sessionId, int volume);
+ public abstract void onSetSessionVolume(long requestId, @NonNull String sessionId, int volume);
/**
* Gets information of the session with the given id.
@@ -161,14 +215,15 @@
/**
* Notifies clients of that the session is created and ready for use.
* <p>
- * If this session is created without any creation request, use {@link #REQUEST_ID_UNKNOWN}
+ * If this session is created without any creation request, use {@link #REQUEST_ID_NONE}
* as the request ID.
*
* @param sessionInfo information of the new session.
* The {@link RoutingSessionInfo#getId() id} of the session must be unique.
* @param requestId id of the previous request to create this session provided in
- * {@link #onCreateSession(String, String, long, Bundle)}
- * @see #onCreateSession(String, String, long, Bundle)
+ * {@link #onCreateSession(long, String, String, Bundle)}. Can be
+ * {@link #REQUEST_ID_NONE} if this session is created without any request.
+ * @see #onCreateSession(long, String, String, Bundle)
* @see #getSessionInfo(String)
*/
public final void notifySessionCreated(@NonNull RoutingSessionInfo sessionInfo,
@@ -201,8 +256,8 @@
* Notifies clients of that the session could not be created.
*
* @param requestId id of the previous request to create the session provided in
- * {@link #onCreateSession(String, String, long, Bundle)}.
- * @see #onCreateSession(String, String, long, Bundle)
+ * {@link #onCreateSession(long, String, String, Bundle)}.
+ * @see #onCreateSession(long, String, String, Bundle)
*/
public final void notifySessionCreationFailed(long requestId) {
if (mRemoteCallback == null) {
@@ -246,7 +301,7 @@
* Notifies that the session is released.
*
* @param sessionId id of the released session.
- * @see #onReleaseSession(String)
+ * @see #onReleaseSession(long, String)
*/
public final void notifySessionReleased(@NonNull String sessionId) {
if (TextUtils.isEmpty(sessionId)) {
@@ -273,6 +328,29 @@
}
/**
+ * Notifies to the client that the request has failed.
+ *
+ * @param requestId the ID of the previous request
+ * @param reason the reason why the request has failed
+ *
+ * @see #REASON_UNKNOWN_ERROR
+ * @see #REASON_REJECTED
+ * @see #REASON_NETWORK_ERROR
+ * @see #REASON_ROUTE_NOT_AVAILABLE
+ * @see #REASON_INVALID_COMMAND
+ */
+ public final void notifyRequestFailed(long requestId, @Reason int reason) {
+ if (mRemoteCallback == null) {
+ return;
+ }
+ try {
+ mRemoteCallback.notifyRequestFailed(requestId, reason);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Failed to notify that the request has failed.");
+ }
+ }
+
+ /**
* Called when the service receives a request to create a session.
* <p>
* You should create and maintain your own session and notifies the client of
@@ -288,9 +366,9 @@
* If you can't create the session or want to reject the request, call
* {@link #notifySessionCreationFailed(long)} with the given {@code requestId}.
*
+ * @param requestId the id of this request
* @param packageName the package name of the application that selected the route
* @param routeId the id of the route initially being connected
- * @param requestId the id of this session creation request
* @param sessionHints an optional bundle of app-specific arguments sent by
* {@link MediaRouter2}, or null if none. The contents of this bundle
* may affect the result of session creation.
@@ -299,8 +377,8 @@
* @see RoutingSessionInfo.Builder#addSelectedRoute(String)
* @see RoutingSessionInfo.Builder#setControlHints(Bundle)
*/
- public abstract void onCreateSession(@NonNull String packageName, @NonNull String routeId,
- long requestId, @Nullable Bundle sessionHints);
+ public abstract void onCreateSession(long requestId, @NonNull String packageName,
+ @NonNull String routeId, @Nullable Bundle sessionHints);
/**
* Called when the session should be released. A client of the session or system can request
@@ -312,44 +390,48 @@
* Note: Calling {@link #notifySessionReleased(String)} will <em>NOT</em> trigger
* this method to be called.
*
+ * @param requestId the id of this request
* @param sessionId id of the session being released.
* @see #notifySessionReleased(String)
* @see #getSessionInfo(String)
*/
- public abstract void onReleaseSession(@NonNull String sessionId);
+ public abstract void onReleaseSession(long requestId, @NonNull String sessionId);
- //TODO: make a way to reject the request
/**
* Called when a client requests selecting a route for the session.
* After the route is selected, call {@link #notifySessionUpdated(RoutingSessionInfo)}
* to update session info.
*
+ * @param requestId the id of this request
* @param sessionId id of the session
* @param routeId id of the route
*/
- public abstract void onSelectRoute(@NonNull String sessionId, @NonNull String routeId);
+ public abstract void onSelectRoute(long requestId, @NonNull String sessionId,
+ @NonNull String routeId);
- //TODO: make a way to reject the request
/**
* Called when a client requests deselecting a route from the session.
* After the route is deselected, call {@link #notifySessionUpdated(RoutingSessionInfo)}
* to update session info.
*
+ * @param requestId the id of this request
* @param sessionId id of the session
* @param routeId id of the route
*/
- public abstract void onDeselectRoute(@NonNull String sessionId, @NonNull String routeId);
+ public abstract void onDeselectRoute(long requestId, @NonNull String sessionId,
+ @NonNull String routeId);
- //TODO: make a way to reject the request
/**
* Called when a client requests transferring a session to a route.
* After the transfer is finished, call {@link #notifySessionUpdated(RoutingSessionInfo)}
* to update session info.
*
+ * @param requestId the id of this request
* @param sessionId id of the session
* @param routeId id of the route
*/
- public abstract void onTransferToRoute(@NonNull String sessionId, @NonNull String routeId);
+ public abstract void onTransferToRoute(long requestId, @NonNull String sessionId,
+ @NonNull String routeId);
/**
* Called when the {@link RouteDiscoveryPreference discovery preference} has changed.
@@ -439,12 +521,12 @@
}
@Override
- public void setRouteVolume(String routeId, int volume) {
+ public void setRouteVolume(String routeId, int volume, long requestId) {
if (!checkCallerisSystem()) {
return;
}
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetRouteVolume,
- MediaRoute2ProviderService.this, routeId, volume));
+ MediaRoute2ProviderService.this, requestId, routeId, volume));
}
@Override
@@ -454,12 +536,12 @@
return;
}
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession,
- MediaRoute2ProviderService.this, packageName, routeId, requestId,
+ MediaRoute2ProviderService.this, requestId, packageName, routeId,
requestCreateSession));
}
@Override
- public void selectRoute(@NonNull String sessionId, String routeId) {
+ public void selectRoute(String sessionId, String routeId, long requestId) {
if (!checkCallerisSystem()) {
return;
}
@@ -468,11 +550,11 @@
return;
}
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute,
- MediaRoute2ProviderService.this, sessionId, routeId));
+ MediaRoute2ProviderService.this, requestId, sessionId, routeId));
}
@Override
- public void deselectRoute(@NonNull String sessionId, String routeId) {
+ public void deselectRoute(String sessionId, String routeId, long requestId) {
if (!checkCallerisSystem()) {
return;
}
@@ -481,11 +563,11 @@
return;
}
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onDeselectRoute,
- MediaRoute2ProviderService.this, sessionId, routeId));
+ MediaRoute2ProviderService.this, requestId, sessionId, routeId));
}
@Override
- public void transferToRoute(@NonNull String sessionId, String routeId) {
+ public void transferToRoute(String sessionId, String routeId, long requestId) {
if (!checkCallerisSystem()) {
return;
}
@@ -494,20 +576,20 @@
return;
}
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onTransferToRoute,
- MediaRoute2ProviderService.this, sessionId, routeId));
+ MediaRoute2ProviderService.this, requestId, sessionId, routeId));
}
@Override
- public void setSessionVolume(String sessionId, int volume) {
+ public void setSessionVolume(String sessionId, int volume, long requestId) {
if (!checkCallerisSystem()) {
return;
}
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetSessionVolume,
- MediaRoute2ProviderService.this, sessionId, volume));
+ MediaRoute2ProviderService.this, requestId, sessionId, volume));
}
@Override
- public void releaseSession(@NonNull String sessionId) {
+ public void releaseSession(String sessionId, long requestId) {
if (!checkCallerisSystem()) {
return;
}
@@ -516,7 +598,7 @@
return;
}
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onReleaseSession,
- MediaRoute2ProviderService.this, sessionId));
+ MediaRoute2ProviderService.this, requestId, sessionId));
}
}
}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 5942a3d..079fee8 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -817,7 +817,7 @@
* @return An optional bundle of app-specific arguments to send to the provider,
* or null if none. The contents of this bundle may affect the result of
* controller creation.
- * @see MediaRoute2ProviderService#onCreateSession(String, String, long, Bundle)
+ * @see MediaRoute2ProviderService#onCreateSession(long, String, String, Bundle)
*/
@Nullable
Bundle onGetControllerHints(@NonNull MediaRoute2Info route);
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 636ee92..ff2c863 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -337,7 +337,8 @@
}
if (client != null) {
try {
- mMediaRouterService.setRouteVolumeWithManager(client, route, volume);
+ int requestId = mNextRequestId.getAndIncrement();
+ mMediaRouterService.setRouteVolumeWithManager(client, route, volume, requestId);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to send control request.", ex);
}
@@ -368,8 +369,9 @@
}
if (client != null) {
try {
+ int requestId = mNextRequestId.getAndIncrement();
mMediaRouterService.setSessionVolumeWithManager(
- client, sessionInfo.getId(), volume);
+ client, sessionInfo.getId(), volume, requestId);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to send control request.", ex);
}
@@ -443,6 +445,12 @@
}
}
+ void notifyRequestFailed(int reason) {
+ for (CallbackRecord record : mCallbackRecords) {
+ record.mExecutor.execute(() -> record.mCallback.onRequestFailed(reason));
+ }
+ }
+
void updatePreferredFeatures(String packageName, List<String> preferredFeatures) {
List<String> prevFeatures = mPreferredFeaturesMap.put(packageName, preferredFeatures);
if ((prevFeatures == null && preferredFeatures.size() == 0)
@@ -593,7 +601,9 @@
}
if (client != null) {
try {
- mMediaRouterService.selectRouteWithManager(mClient, getSessionId(), route);
+ int requestId = mNextRequestId.getAndIncrement();
+ mMediaRouterService.selectRouteWithManager(
+ mClient, getSessionId(), route, requestId);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to select route for session.", ex);
}
@@ -635,7 +645,9 @@
}
if (client != null) {
try {
- mMediaRouterService.deselectRouteWithManager(mClient, getSessionId(), route);
+ int requestId = mNextRequestId.getAndIncrement();
+ mMediaRouterService.deselectRouteWithManager(
+ mClient, getSessionId(), route, requestId);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to remove route from session.", ex);
}
@@ -678,7 +690,9 @@
}
if (client != null) {
try {
- mMediaRouterService.transferToRouteWithManager(mClient, getSessionId(), route);
+ int requestId = mNextRequestId.getAndIncrement();
+ mMediaRouterService.transferToRouteWithManager(
+ mClient, getSessionId(), route, requestId);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to transfer to route for session.", ex);
}
@@ -696,7 +710,9 @@
}
if (client != null) {
try {
- mMediaRouterService.releaseSessionWithManager(mClient, getSessionId());
+ int requestId = mNextRequestId.getAndIncrement();
+ mMediaRouterService.releaseSessionWithManager(
+ mClient, getSessionId(), requestId);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to notify of controller release", ex);
}
@@ -783,6 +799,17 @@
public void onPreferredFeaturesChanged(@NonNull String packageName,
@NonNull List<String> preferredFeatures) {}
+ /**
+ * Called when a previous request has failed.
+ *
+ * @param reason the reason that the request has failed. Can be one of followings:
+ * {@link MediaRoute2ProviderService#REASON_UNKNOWN_ERROR},
+ * {@link MediaRoute2ProviderService#REASON_REJECTED},
+ * {@link MediaRoute2ProviderService#REASON_NETWORK_ERROR},
+ * {@link MediaRoute2ProviderService#REASON_ROUTE_NOT_AVAILABLE},
+ * {@link MediaRoute2ProviderService#REASON_INVALID_COMMAND},
+ */
+ public void onRequestFailed(int reason) {}
}
final class CallbackRecord {
@@ -826,6 +853,13 @@
}
@Override
+ public void notifyRequestFailed(int requestId, int reason) {
+ // Note: requestId is not used.
+ mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifyRequestFailed,
+ MediaRouter2Manager.this, reason));
+ }
+
+ @Override
public void notifyPreferredFeaturesChanged(String packageName, List<String> features) {
mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updatePreferredFeatures,
MediaRouter2Manager.this, packageName, features));
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index e80562b..bcff6a1 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -19,6 +19,8 @@
import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
+import static android.media.MediaRoute2ProviderService.REASON_REJECTED;
+import static android.media.MediaRoute2ProviderService.REQUEST_ID_NONE;
import static com.android.mediaroutertest.SampleMediaRoute2ProviderService.FEATURE_SAMPLE;
import static com.android.mediaroutertest.SampleMediaRoute2ProviderService.FEATURE_SPECIAL;
@@ -32,6 +34,7 @@
import static com.android.mediaroutertest.SampleMediaRoute2ProviderService.VOLUME_MAX;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -55,7 +58,6 @@
import org.junit.runner.RunWith;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -69,6 +71,7 @@
@SmallTest
public class MediaRouter2ManagerTest {
private static final String TAG = "MediaRouter2ManagerTest";
+ private static final int WAIT_TIME_MS = 2000;
private static final int TIMEOUT_MS = 5000;
private Context mContext;
@@ -111,6 +114,11 @@
releaseAllSessions();
// unregister callbacks
clearCallbacks();
+
+ SampleMediaRoute2ProviderService instance = SampleMediaRoute2ProviderService.getInstance();
+ if (instance != null) {
+ instance.setProxy(null);
+ }
}
@Test
@@ -296,7 +304,7 @@
String selectedSystemRouteId =
MediaRouter2Utils.getOriginalId(
mManager.getActiveSessions().get(0).getSelectedRoutes().get(0));
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(Collections.emptyList());
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
MediaRoute2Info volRoute = routes.get(selectedSystemRouteId);
assertNotNull(volRoute);
@@ -398,6 +406,53 @@
}
}
+ /**
+ * Tests that {@link android.media.MediaRoute2ProviderService#notifyRequestFailed(long, int)}
+ * should invoke the callback only when the right requestId is used.
+ */
+ @Test
+ public void testOnRequestFailedCalledForProperRequestId() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
+ MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
+
+ SampleMediaRoute2ProviderService instance = SampleMediaRoute2ProviderService.getInstance();
+ assertNotNull(instance);
+
+ final List<Long> requestIds = new ArrayList<>();
+ final CountDownLatch onSetRouteVolumeLatch = new CountDownLatch(1);
+ instance.setProxy(new SampleMediaRoute2ProviderService.Proxy() {
+ @Override
+ public void onSetRouteVolume(String routeId, int volume, long requestId) {
+ requestIds.add(requestId);
+ onSetRouteVolumeLatch.countDown();
+ }
+ });
+
+ addManagerCallback(new MediaRouter2Manager.Callback() {});
+ mManager.setRouteVolume(volRoute, 0);
+ assertTrue(onSetRouteVolumeLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertFalse(requestIds.isEmpty());
+
+ final int failureReason = REASON_REJECTED;
+ final CountDownLatch onRequestFailedLatch = new CountDownLatch(1);
+ addManagerCallback(new MediaRouter2Manager.Callback() {
+ @Override
+ public void onRequestFailed(int reason) {
+ if (reason == failureReason) {
+ onRequestFailedLatch.countDown();
+ }
+ }
+ });
+
+ final long invalidRequestId = REQUEST_ID_NONE;
+ instance.notifyRequestFailed(invalidRequestId, failureReason);
+ assertFalse(onRequestFailedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+
+ final long validRequestId = requestIds.get(0);
+ instance.notifyRequestFailed(validRequestId, failureReason);
+ assertTrue(onRequestFailedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+
@Test
public void testVolumeHandling() throws Exception {
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
@@ -429,10 +484,11 @@
}
@Override
- public void onControlCategoriesChanged(String packageName,
+ public void onPreferredFeaturesChanged(String packageName,
List<String> preferredFeatures) {
if (TextUtils.equals(mPackageName, packageName)
- && preferredFeatures.equals(routeFeatures)) {
+ && preferredFeatures.size() == routeFeatures.size()
+ && preferredFeatures.containsAll(routeFeatures)) {
featuresLatch.countDown();
}
}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java
index 3faefdb..9071c85 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java
@@ -75,6 +75,7 @@
@GuardedBy("sLock")
private static SampleMediaRoute2ProviderService sInstance;
+ private Proxy mProxy;
private void initializeRoutes() {
MediaRoute2Info route1 = new MediaRoute2Info.Builder(ROUTE_ID1, ROUTE_NAME1)
@@ -179,7 +180,13 @@
}
@Override
- public void onSetRouteVolume(String routeId, int volume) {
+ public void onSetRouteVolume(long requestId, String routeId, int volume) {
+ Proxy proxy = mProxy;
+ if (proxy != null) {
+ proxy.onSetRouteVolume(routeId, volume, requestId);
+ return;
+ }
+
MediaRoute2Info route = mRoutes.get(routeId);
if (route == null) {
return;
@@ -192,7 +199,7 @@
}
@Override
- public void onSetSessionVolume(String sessionId, int volume) {
+ public void onSetSessionVolume(long requestId, String sessionId, int volume) {
RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
if (sessionInfo == null) {
return;
@@ -205,7 +212,7 @@
}
@Override
- public void onCreateSession(String packageName, String routeId, long requestId,
+ public void onCreateSession(long requestId, String packageName, String routeId,
@Nullable Bundle sessionHints) {
MediaRoute2Info route = mRoutes.get(routeId);
if (route == null || TextUtils.equals(ROUTE_ID3_SESSION_CREATION_FAILED, routeId)) {
@@ -238,7 +245,7 @@
}
@Override
- public void onReleaseSession(String sessionId) {
+ public void onReleaseSession(long requestId, String sessionId) {
RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
if (sessionInfo == null) {
return;
@@ -258,7 +265,7 @@
}
@Override
- public void onSelectRoute(String sessionId, String routeId) {
+ public void onSelectRoute(long requestId, String sessionId, String routeId) {
RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
MediaRoute2Info route = mRoutes.get(routeId);
if (route == null || sessionInfo == null) {
@@ -280,7 +287,7 @@
}
@Override
- public void onDeselectRoute(String sessionId, String routeId) {
+ public void onDeselectRoute(long requestId, String sessionId, String routeId) {
RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
MediaRoute2Info route = mRoutes.get(routeId);
@@ -308,7 +315,7 @@
}
@Override
- public void onTransferToRoute(String sessionId, String routeId) {
+ public void onTransferToRoute(long requestId, String sessionId, String routeId) {
RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
MediaRoute2Info route = mRoutes.get(routeId);
@@ -347,10 +354,18 @@
}
String sessionId = mRouteIdToSessionId.get(routeId);
- onDeselectRoute(sessionId, routeId);
+ onDeselectRoute(REQUEST_ID_NONE, sessionId, routeId);
}
void publishRoutes() {
notifyRoutes(mRoutes.values());
}
+
+ public void setProxy(@Nullable Proxy proxy) {
+ mProxy = proxy;
+ }
+
+ public static class Proxy {
+ public void onSetRouteVolume(String routeId, int volume, long requestId) {}
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index 09d4d5f..20b1e0d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -57,6 +57,12 @@
int PROMPT_REASON_PREPARE_FOR_UPDATE = 6;
/**
+ * Primary auth is required because the user uses weak/convenience biometrics and hasn't used
+ * primary auth since a while
+ */
+ int PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT = 7;
+
+ /**
* Interface back to keyguard to tell it when security
* @param callback
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 9ba3860..52ea300 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -80,6 +80,7 @@
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyManager;
import android.util.Log;
+import android.util.SparseArray;
import android.util.SparseBooleanArray;
import com.android.internal.annotations.VisibleForTesting;
@@ -91,6 +92,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -108,6 +110,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.TimeZone;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -264,6 +267,7 @@
// If the user long pressed the lock icon, disabling face auth for the current session.
private boolean mLockIconPressed;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private final Executor mBackgroundExecutor;
/**
* Short delay before restarting biometric authentication after a successful try
@@ -320,12 +324,22 @@
}
};
+ private class BiometricAuthenticated {
+ private final boolean mAuthenticated;
+ private final boolean mIsStrongBiometric;
+
+ BiometricAuthenticated(boolean authenticated, boolean isStrongBiometric) {
+ this.mAuthenticated = authenticated;
+ this.mIsStrongBiometric = isStrongBiometric;
+ }
+ }
+
private SparseBooleanArray mUserIsUnlocked = new SparseBooleanArray();
private SparseBooleanArray mUserHasTrust = new SparseBooleanArray();
private SparseBooleanArray mUserTrustIsManaged = new SparseBooleanArray();
private SparseBooleanArray mUserTrustIsUsuallyManaged = new SparseBooleanArray();
- private SparseBooleanArray mUserFingerprintAuthenticated = new SparseBooleanArray();
- private SparseBooleanArray mUserFaceAuthenticated = new SparseBooleanArray();
+ private SparseArray<BiometricAuthenticated> mUserFingerprintAuthenticated = new SparseArray<>();
+ private SparseArray<BiometricAuthenticated> mUserFaceAuthenticated = new SparseArray<>();
private SparseBooleanArray mUserFaceUnlockRunning = new SparseBooleanArray();
private Map<Integer, Intent> mSecondaryLockscreenRequirement = new HashMap<Integer, Intent>();
@@ -523,10 +537,11 @@
}
@VisibleForTesting
- protected void onFingerprintAuthenticated(int userId) {
+ protected void onFingerprintAuthenticated(int userId, boolean isStrongBiometric) {
Assert.isMainThread();
Trace.beginSection("KeyGuardUpdateMonitor#onFingerPrintAuthenticated");
- mUserFingerprintAuthenticated.put(userId, true);
+ mUserFingerprintAuthenticated.put(userId,
+ new BiometricAuthenticated(true, isStrongBiometric));
// Update/refresh trust state only if user can skip bouncer
if (getUserCanSkipBouncer(userId)) {
mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FINGERPRINT);
@@ -536,7 +551,8 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricAuthenticated(userId, BiometricSourceType.FINGERPRINT);
+ cb.onBiometricAuthenticated(userId, BiometricSourceType.FINGERPRINT,
+ isStrongBiometric);
}
}
@@ -546,9 +562,21 @@
// Only authenticate fingerprint once when assistant is visible
mAssistantVisible = false;
+ // Report unlock with strong or non-strong biometric
+ reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
+
Trace.endSection();
}
+ private void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId) {
+ mBackgroundExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mLockPatternUtils.reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
+ }
+ });
+ }
+
private void handleFingerprintAuthFailed() {
Assert.isMainThread();
for (int i = 0; i < mCallbacks.size(); i++) {
@@ -574,7 +602,7 @@
}
}
- private void handleFingerprintAuthenticated(int authUserId) {
+ private void handleFingerprintAuthenticated(int authUserId, boolean isStrongBiometric) {
Trace.beginSection("KeyGuardUpdateMonitor#handlerFingerPrintAuthenticated");
try {
final int userId;
@@ -592,7 +620,7 @@
Log.d(TAG, "Fingerprint disabled by DPM for userId: " + userId);
return;
}
- onFingerprintAuthenticated(userId);
+ onFingerprintAuthenticated(userId, isStrongBiometric);
} finally {
setFingerprintRunningState(BIOMETRIC_STATE_STOPPED);
}
@@ -683,10 +711,11 @@
}
@VisibleForTesting
- protected void onFaceAuthenticated(int userId) {
+ protected void onFaceAuthenticated(int userId, boolean isStrongBiometric) {
Trace.beginSection("KeyGuardUpdateMonitor#onFaceAuthenticated");
Assert.isMainThread();
- mUserFaceAuthenticated.put(userId, true);
+ mUserFaceAuthenticated.put(userId,
+ new BiometricAuthenticated(true, isStrongBiometric));
// Update/refresh trust state only if user can skip bouncer
if (getUserCanSkipBouncer(userId)) {
mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FACE);
@@ -697,7 +726,8 @@
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
cb.onBiometricAuthenticated(userId,
- BiometricSourceType.FACE);
+ BiometricSourceType.FACE,
+ isStrongBiometric);
}
}
@@ -707,6 +737,9 @@
// Only authenticate face once when assistant is visible
mAssistantVisible = false;
+ // Report unlock with strong or non-strong biometric
+ reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
+
Trace.endSection();
}
@@ -737,7 +770,7 @@
}
}
- private void handleFaceAuthenticated(int authUserId) {
+ private void handleFaceAuthenticated(int authUserId, boolean isStrongBiometric) {
Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated");
try {
if (mGoingToSleep) {
@@ -760,7 +793,7 @@
return;
}
if (DEBUG_FACE) Log.d(TAG, "Face auth succeeded for user " + userId);
- onFaceAuthenticated(userId);
+ onFaceAuthenticated(userId, isStrongBiometric);
} finally {
setFaceRunningState(BIOMETRIC_STATE_STOPPED);
}
@@ -914,9 +947,13 @@
* Returns whether the user is unlocked with biometrics.
*/
public boolean getUserUnlockedWithBiometric(int userId) {
- boolean fingerprintOrFace = mUserFingerprintAuthenticated.get(userId)
- || mUserFaceAuthenticated.get(userId);
- return fingerprintOrFace && isUnlockingWithBiometricAllowed();
+ BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
+ BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
+ boolean fingerprintAllowed = fingerprint != null && fingerprint.mAuthenticated
+ && isUnlockingWithBiometricAllowed(fingerprint.mIsStrongBiometric);
+ boolean faceAllowed = face != null && face.mAuthenticated
+ && isUnlockingWithBiometricAllowed(face.mIsStrongBiometric);
+ return fingerprintAllowed || faceAllowed;
}
public boolean getUserTrustIsManaged(int userId) {
@@ -970,8 +1007,8 @@
return mUserTrustIsUsuallyManaged.get(userId);
}
- public boolean isUnlockingWithBiometricAllowed() {
- return mStrongAuthTracker.isUnlockingWithBiometricAllowed();
+ public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
+ return mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric);
}
public boolean isUserInLockdown(int userId) {
@@ -1169,7 +1206,7 @@
@Override
public void onAuthenticationSucceeded(AuthenticationResult result) {
Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
- handleFingerprintAuthenticated(result.getUserId());
+ handleFingerprintAuthenticated(result.getUserId(), result.isStrongBiometric());
Trace.endSection();
}
@@ -1201,7 +1238,7 @@
@Override
public void onAuthenticationSucceeded(FaceManager.AuthenticationResult result) {
Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
- handleFaceAuthenticated(result.getUserId());
+ handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric());
Trace.endSection();
}
@@ -1305,9 +1342,9 @@
mStrongAuthRequiredChangedCallback = strongAuthRequiredChangedCallback;
}
- public boolean isUnlockingWithBiometricAllowed() {
+ public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
int userId = getCurrentUser();
- return isBiometricAllowedForUser(userId);
+ return isBiometricAllowedForUser(isStrongBiometric, userId);
}
public boolean hasUserAuthenticatedSinceBoot() {
@@ -1438,12 +1475,14 @@
Context context,
@Main Looper mainLooper,
BroadcastDispatcher broadcastDispatcher,
- DumpController dumpController) {
+ DumpController dumpController,
+ @Background Executor backgroundExecutor) {
mContext = context;
mSubscriptionManager = SubscriptionManager.from(context);
mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged);
dumpController.registerDumpable(this);
+ mBackgroundExecutor = backgroundExecutor;
mHandler = new Handler(mainLooper) {
@Override
@@ -1753,14 +1792,16 @@
}
private boolean shouldListenForFingerprintAssistant() {
+ BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(getCurrentUser());
return mAssistantVisible && mKeyguardOccluded
- && !mUserFingerprintAuthenticated.get(getCurrentUser(), false)
+ && !(fingerprint != null && fingerprint.mAuthenticated)
&& !mUserHasTrust.get(getCurrentUser(), false);
}
private boolean shouldListenForFaceAssistant() {
+ BiometricAuthenticated face = mUserFaceAuthenticated.get(getCurrentUser());
return mAssistantVisible && mKeyguardOccluded
- && !mUserFaceAuthenticated.get(getCurrentUser(), false)
+ && !(face != null && face.mAuthenticated)
&& !mUserHasTrust.get(getCurrentUser(), false);
}
@@ -1817,7 +1858,7 @@
public void onLockIconPressed() {
mLockIconPressed = true;
final int userId = getCurrentUser();
- mUserFaceAuthenticated.put(userId, false);
+ mUserFaceAuthenticated.put(userId, null);
updateFaceListeningState();
mStrongAuthTracker.onStrongAuthRequiredChanged(userId);
}
@@ -2691,9 +2732,11 @@
if (mFpm != null && mFpm.isHardwareDetected()) {
final int userId = ActivityManager.getCurrentUser();
final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
+ BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
pw.println(" Fingerprint state (user=" + userId + ")");
- pw.println(" allowed=" + isUnlockingWithBiometricAllowed());
- pw.println(" auth'd=" + mUserFingerprintAuthenticated.get(userId));
+ pw.println(" allowed=" + fingerprint != null
+ && isUnlockingWithBiometricAllowed(fingerprint.mIsStrongBiometric));
+ pw.println(" auth'd=" + fingerprint != null && fingerprint.mAuthenticated);
pw.println(" authSinceBoot="
+ getStrongAuthTracker().hasUserAuthenticatedSinceBoot());
pw.println(" disabled(DPM)=" + isFingerprintDisabled(userId));
@@ -2706,9 +2749,11 @@
if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
final int userId = ActivityManager.getCurrentUser();
final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
+ BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
pw.println(" Face authentication state (user=" + userId + ")");
- pw.println(" allowed=" + isUnlockingWithBiometricAllowed());
- pw.println(" auth'd=" + mUserFaceAuthenticated.get(userId));
+ pw.println(" allowed=" + face != null
+ && isUnlockingWithBiometricAllowed(face.mIsStrongBiometric));
+ pw.println(" auth'd=" + face != null && face.mAuthenticated);
pw.println(" authSinceBoot="
+ getStrongAuthTracker().hasUserAuthenticatedSinceBoot());
pw.println(" disabled(DPM)=" + isFaceDisabled(userId));
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 49f72a9..12e0ecd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -239,7 +239,8 @@
* @param userId the user id for which the biometric sample was authenticated
* @param biometricSourceType
*/
- public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) { }
+ public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
+ boolean isStrongBiometric) { }
/**
* Called when biometric authentication provides help string (e.g. "Try again")
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index 1a47dac..dc0cb03 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -96,6 +96,7 @@
private void fakeWakeAndUnlock() {
mBiometricUnlockController.onBiometricAcquired(BiometricSourceType.FINGERPRINT);
mBiometricUnlockController.onBiometricAuthenticated(
- KeyguardUpdateMonitor.getCurrentUser(), BiometricSourceType.FINGERPRINT);
+ KeyguardUpdateMonitor.getCurrentUser(), BiometricSourceType.FINGERPRINT,
+ true /* isStrongBiometric */);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
index 6a64c83..f719cc6 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
@@ -130,7 +130,8 @@
new KeyguardUpdateMonitorCallback() {
@Override
public void onBiometricAuthenticated(int userId,
- BiometricSourceType biometricSourceType) {
+ BiometricSourceType biometricSourceType,
+ boolean isStrongBiometric) {
if (userId == KeyguardUpdateMonitor.getCurrentUser()
&& biometricSourceType == BiometricSourceType.FACE) {
mJustUnlockedWithFace = true;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
index 2f3e336..a084ae6 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
@@ -78,7 +78,8 @@
new KeyguardUpdateMonitorCallback() {
@Override
public void onBiometricAuthenticated(int userId,
- BiometricSourceType biometricSourceType) {
+ BiometricSourceType biometricSourceType,
+ boolean isStrongBiometric) {
if (userId == KeyguardUpdateMonitor.getCurrentUser()
&& biometricSourceType == BiometricSourceType.FACE) {
mJustUnlockedWithFace = true;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 374153c..2e6c955 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -22,6 +22,7 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
import static com.android.systemui.DejankUtils.whitelistIpcs;
@@ -538,7 +539,8 @@
}
@Override
- public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
+ public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
+ boolean isStrongBiometric) {
if (mLockPatternUtils.isSecure(userId)) {
mLockPatternUtils.getDevicePolicyManager().reportSuccessfulBiometricAttempt(
userId);
@@ -675,6 +677,9 @@
return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT;
} else if (any && (strongAuth & STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE) != 0) {
return KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
+ } else if (any && (strongAuth
+ & STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT) != 0) {
+ return KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT;
}
return KeyguardSecurityView.PROMPT_REASON_NONE;
}
@@ -1842,6 +1847,13 @@
mShowKeyguardWakeLock.release();
}
mKeyguardDisplayManager.show();
+
+ // schedule 4hr idle timeout after which non-strong biometrics (i.e. weak or convenience
+ // biometric) can't be used to unlock device until unlocking with strong biometric or
+ // primary auth (i.e. PIN/pattern/password)
+ mLockPatternUtils.scheduleNonStrongBiometricIdleTimeout(
+ KeyguardUpdateMonitor.getCurrentUser());
+
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 9e1e347..f06cd54 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -497,9 +497,9 @@
flashOutAnimator.addUpdateListener(animation ->
mScreenshotFlash.setAlpha((float) animation.getAnimatedValue()));
- final PointF startPos = new PointF((float) bounds.left, (float) bounds.top);
- final PointF finalPos = new PointF(mScreenshotOffsetXPx,
- mDisplayMetrics.heightPixels - mScreenshotOffsetYPx - height * cornerScale);
+ final PointF startPos = new PointF(bounds.centerX(), bounds.centerY());
+ final PointF finalPos = new PointF(mScreenshotOffsetXPx + width * cornerScale / 2f,
+ mDisplayMetrics.heightPixels - mScreenshotOffsetYPx - height * cornerScale / 2f);
ValueAnimator toCorner = ValueAnimator.ofFloat(0, 1);
toCorner.setDuration(SCREENSHOT_TO_CORNER_Y_DURATION_MS);
@@ -517,11 +517,13 @@
}
if (t < xPositionPct) {
- mScreenshotView.setX(MathUtils.lerp(
- startPos.x, finalPos.x, mFastOutSlowIn.getInterpolation(t / xPositionPct)));
+ float xCenter = MathUtils.lerp(startPos.x, finalPos.x,
+ mFastOutSlowIn.getInterpolation(t / xPositionPct));
+ mScreenshotView.setX(xCenter - width * mScreenshotView.getScaleX() / 2f);
}
- mScreenshotView.setY(MathUtils.lerp(
- startPos.y, finalPos.y, mFastOutSlowIn.getInterpolation(t)));
+ float yCenter = MathUtils.lerp(startPos.y, finalPos.y,
+ mFastOutSlowIn.getInterpolation(t));
+ mScreenshotView.setY(yCenter - height * mScreenshotView.getScaleY() / 2f);
});
toCorner.addListener(new AnimatorListenerAdapter() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 7de70f5..2571521 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -281,7 +281,8 @@
.putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
.putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
mSmartActionsEnabled)
- .setAction(Intent.ACTION_SEND),
+ .setAction(Intent.ACTION_SEND)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
@@ -310,7 +311,8 @@
.putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
.putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
mSmartActionsEnabled)
- .setAction(Intent.ACTION_EDIT),
+ .setAction(Intent.ACTION_EDIT)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
Icon.createWithResource(r, R.drawable.ic_screenshot_edit),
@@ -324,7 +326,8 @@
.putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString())
.putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
.putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
- mSmartActionsEnabled),
+ mSmartActionsEnabled)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
Icon.createWithResource(r, R.drawable.ic_screenshot_delete),
@@ -361,9 +364,9 @@
String actionType = extras.getString(
ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
- Intent intent = new Intent(context,
- GlobalScreenshot.SmartActionsReceiver.class).putExtra(
- GlobalScreenshot.EXTRA_ACTION_INTENT, action.actionIntent);
+ Intent intent = new Intent(context, GlobalScreenshot.SmartActionsReceiver.class)
+ .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, action.actionIntent)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
mRandom.nextInt(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
index ab69d47..4bb8621 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
@@ -90,7 +90,7 @@
}
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
- IndentingPrintWriter(pw, " ").use {
+ IndentingPrintWriter(pw, " ").let {
it.println("BlurUtils:")
it.increaseIndent()
it.println("minBlurRadius: $minBlurRadius")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 7d3d406..4f8e6cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -645,7 +645,13 @@
@Override
public void onBiometricHelp(int msgId, String helpString,
BiometricSourceType biometricSourceType) {
- if (!mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed()) {
+ // TODO(b/141025588): refactor to reduce repetition of code/comments
+ // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
+ // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
+ // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
+ // check of whether non-strong biometric is allowed
+ if (!mKeyguardUpdateMonitor
+ .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)) {
return;
}
boolean showSwipeToUnlock =
@@ -705,13 +711,21 @@
private boolean shouldSuppressFingerprintError(int msgId,
KeyguardUpdateMonitor updateMonitor) {
- return ((!updateMonitor.isUnlockingWithBiometricAllowed()
+ // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
+ // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
+ // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
+ // check of whether non-strong biometric is allowed
+ return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
&& msgId != FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
|| msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED);
}
private boolean shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor) {
- return ((!updateMonitor.isUnlockingWithBiometricAllowed()
+ // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
+ // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
+ // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
+ // check of whether non-strong biometric is allowed
+ return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
&& msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT)
|| msgId == FaceManager.FACE_ERROR_CANCELED);
}
@@ -745,8 +759,9 @@
}
@Override
- public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
- super.onBiometricAuthenticated(userId, biometricSourceType);
+ public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
+ boolean isStrongBiometric) {
+ super.onBiometricAuthenticated(userId, biometricSourceType, isStrongBiometric);
mHandler.sendEmptyMessage(MSG_HIDE_TRANSIENT);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 691e1c4..1dde5c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -150,14 +150,26 @@
private KeyguardViewMediator mKeyguardViewMediator;
private ScrimController mScrimController;
private StatusBar mStatusBar;
- private int mPendingAuthenticatedUserId = -1;
- private BiometricSourceType mPendingAuthenticatedBioSourceType = null;
+ private PendingAuthenticated mPendingAuthenticated = null;
private boolean mPendingShowBouncer;
private boolean mHasScreenTurnedOnSinceAuthenticating;
private boolean mFadedAwayAfterWakeAndUnlock;
private final MetricsLogger mMetricsLogger;
+ private static final class PendingAuthenticated {
+ public final int userId;
+ public final BiometricSourceType biometricSourceType;
+ public final boolean isStrongBiometric;
+
+ PendingAuthenticated(int userId, BiometricSourceType biometricSourceType,
+ boolean isStrongBiometric) {
+ this.userId = userId;
+ this.biometricSourceType = biometricSourceType;
+ this.isStrongBiometric = isStrongBiometric;
+ }
+ }
+
@Inject
public BiometricUnlockController(Context context, DozeScrimController dozeScrimController,
KeyguardViewMediator keyguardViewMediator, ScrimController scrimController,
@@ -251,28 +263,30 @@
}
@Override
- public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
+ public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
+ boolean isStrongBiometric) {
Trace.beginSection("BiometricUnlockController#onBiometricAuthenticated");
if (mUpdateMonitor.isGoingToSleep()) {
- mPendingAuthenticatedUserId = userId;
- mPendingAuthenticatedBioSourceType = biometricSourceType;
+ mPendingAuthenticated = new PendingAuthenticated(userId, biometricSourceType,
+ isStrongBiometric);
Trace.endSection();
return;
}
mMetricsLogger.write(new LogMaker(MetricsEvent.BIOMETRIC_AUTH)
.setType(MetricsEvent.TYPE_SUCCESS).setSubtype(toSubtype(biometricSourceType)));
boolean unlockAllowed = mKeyguardBypassController.onBiometricAuthenticated(
- biometricSourceType);
+ biometricSourceType, isStrongBiometric);
if (unlockAllowed) {
mKeyguardViewMediator.userActivity();
- startWakeAndUnlock(biometricSourceType);
+ startWakeAndUnlock(biometricSourceType, isStrongBiometric);
} else {
Log.d(TAG, "onBiometricAuthenticated aborted by bypass controller");
}
}
- public void startWakeAndUnlock(BiometricSourceType biometricSourceType) {
- startWakeAndUnlock(calculateMode(biometricSourceType));
+ public void startWakeAndUnlock(BiometricSourceType biometricSourceType,
+ boolean isStrongBiometric) {
+ startWakeAndUnlock(calculateMode(biometricSourceType, isStrongBiometric));
}
public void startWakeAndUnlock(@WakeAndUnlockMode int mode) {
@@ -373,45 +387,46 @@
public void onStartedGoingToSleep(int why) {
resetMode();
mFadedAwayAfterWakeAndUnlock = false;
- mPendingAuthenticatedUserId = -1;
- mPendingAuthenticatedBioSourceType = null;
+ mPendingAuthenticated = null;
}
@Override
public void onFinishedGoingToSleep(int why) {
Trace.beginSection("BiometricUnlockController#onFinishedGoingToSleep");
- BiometricSourceType pendingType = mPendingAuthenticatedBioSourceType;
- int pendingUserId = mPendingAuthenticatedUserId;
- if (pendingUserId != -1 && pendingType != null) {
+ if (mPendingAuthenticated != null) {
// Post this to make sure it's executed after the device is fully locked.
- mHandler.post(() -> onBiometricAuthenticated(pendingUserId, pendingType));
+ mHandler.post(() -> onBiometricAuthenticated(mPendingAuthenticated.userId,
+ mPendingAuthenticated.biometricSourceType,
+ mPendingAuthenticated.isStrongBiometric));
+ mPendingAuthenticated = null;
}
- mPendingAuthenticatedUserId = -1;
- mPendingAuthenticatedBioSourceType = null;
Trace.endSection();
}
public boolean hasPendingAuthentication() {
- return mPendingAuthenticatedUserId != -1
- && mUpdateMonitor.isUnlockingWithBiometricAllowed()
- && mPendingAuthenticatedUserId == KeyguardUpdateMonitor.getCurrentUser();
+ return mPendingAuthenticated != null
+ && mUpdateMonitor
+ .isUnlockingWithBiometricAllowed(mPendingAuthenticated.isStrongBiometric)
+ && mPendingAuthenticated.userId == KeyguardUpdateMonitor.getCurrentUser();
}
public int getMode() {
return mMode;
}
- private @WakeAndUnlockMode int calculateMode(BiometricSourceType biometricSourceType) {
+ private @WakeAndUnlockMode int calculateMode(BiometricSourceType biometricSourceType,
+ boolean isStrongBiometric) {
if (biometricSourceType == BiometricSourceType.FACE
|| biometricSourceType == BiometricSourceType.IRIS) {
- return calculateModeForPassiveAuth();
+ return calculateModeForPassiveAuth(isStrongBiometric);
} else {
- return calculateModeForFingerprint();
+ return calculateModeForFingerprint(isStrongBiometric);
}
}
- private @WakeAndUnlockMode int calculateModeForFingerprint() {
- boolean unlockingAllowed = mUpdateMonitor.isUnlockingWithBiometricAllowed();
+ private @WakeAndUnlockMode int calculateModeForFingerprint(boolean isStrongBiometric) {
+ boolean unlockingAllowed =
+ mUpdateMonitor.isUnlockingWithBiometricAllowed(isStrongBiometric);
boolean deviceDreaming = mUpdateMonitor.isDreaming();
if (!mUpdateMonitor.isDeviceInteractive()) {
@@ -440,8 +455,9 @@
return MODE_NONE;
}
- private @WakeAndUnlockMode int calculateModeForPassiveAuth() {
- boolean unlockingAllowed = mUpdateMonitor.isUnlockingWithBiometricAllowed();
+ private @WakeAndUnlockMode int calculateModeForPassiveAuth(boolean isStrongBiometric) {
+ boolean unlockingAllowed =
+ mUpdateMonitor.isUnlockingWithBiometricAllowed(isStrongBiometric);
boolean deviceDreaming = mUpdateMonitor.isDreaming();
boolean bypass = mKeyguardBypassController.getBypassEnabled();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index b4d0d47..03918e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -38,11 +38,20 @@
private val mKeyguardStateController: KeyguardStateController
private val statusBarStateController: StatusBarStateController
private var hasFaceFeature: Boolean
+ private var pendingUnlock: PendingUnlock? = null
/**
+ * Pending unlock info:
+ *
* The pending unlock type which is set if the bypass was blocked when it happened.
+ *
+ * Whether the pending unlock type is strong biometric or non-strong biometric
+ * (i.e. weak or convenience).
*/
- private var pendingUnlockType: BiometricSourceType? = null
+ private data class PendingUnlock(
+ val pendingUnlockType: BiometricSourceType,
+ val isStrongBiometric: Boolean
+ )
lateinit var unlockController: BiometricUnlockController
var isPulseExpanding = false
@@ -86,7 +95,7 @@
statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
override fun onStateChanged(newState: Int) {
if (newState != StatusBarState.KEYGUARD) {
- pendingUnlockType = null
+ pendingUnlock = null
}
}
})
@@ -101,7 +110,7 @@
lockscreenUserManager.addUserChangedListener(
object : NotificationLockscreenUserManager.UserChangedListener {
override fun onUserChanged(userId: Int) {
- pendingUnlockType = null
+ pendingUnlock = null
}
})
}
@@ -111,11 +120,14 @@
*
* @return false if we can not wake and unlock right now
*/
- fun onBiometricAuthenticated(biometricSourceType: BiometricSourceType): Boolean {
+ fun onBiometricAuthenticated(
+ biometricSourceType: BiometricSourceType,
+ isStrongBiometric: Boolean
+ ): Boolean {
if (bypassEnabled) {
val can = canBypass()
if (!can && (isPulseExpanding || qSExpanded)) {
- pendingUnlockType = biometricSourceType
+ pendingUnlock = PendingUnlock(biometricSourceType, isStrongBiometric)
}
return can
}
@@ -123,10 +135,12 @@
}
fun maybePerformPendingUnlock() {
- if (pendingUnlockType != null) {
- if (onBiometricAuthenticated(pendingUnlockType!!)) {
- unlockController.startWakeAndUnlock(pendingUnlockType)
- pendingUnlockType = null
+ if (pendingUnlock != null) {
+ if (onBiometricAuthenticated(pendingUnlock!!.pendingUnlockType,
+ pendingUnlock!!.isStrongBiometric)) {
+ unlockController.startWakeAndUnlock(pendingUnlock!!.pendingUnlockType,
+ pendingUnlock!!.isStrongBiometric)
+ pendingUnlock = null
}
}
}
@@ -162,12 +176,17 @@
}
fun onStartedGoingToSleep() {
- pendingUnlockType = null
+ pendingUnlock = null
}
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
pw.println("KeyguardBypassController:")
- pw.println(" pendingUnlockType: $pendingUnlockType")
+ if (pendingUnlock != null) {
+ pw.println(" mPendingUnlock.pendingUnlockType: ${pendingUnlock!!.pendingUnlockType}")
+ pw.println(" mPendingUnlock.isStrongBiometric: ${pendingUnlock!!.isStrongBiometric}")
+ } else {
+ pw.println(" mPendingUnlock: $pendingUnlock")
+ }
pw.println(" bypassEnabled: $bypassEnabled")
pw.println(" canBypass: ${canBypass()}")
pw.println(" bouncerShowing: $bouncerShowing")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 60589843..61cef68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -387,7 +387,12 @@
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
boolean fingerprintRunning = mKeyguardUpdateMonitor.isFingerprintDetectionRunning();
- boolean unlockingAllowed = mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed();
+ // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
+ // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
+ // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
+ // check of whether non-strong biometric is allowed
+ boolean unlockingAllowed = mKeyguardUpdateMonitor
+ .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */);
if (fingerprintRunning && unlockingAllowed) {
AccessibilityNodeInfo.AccessibilityAction unlock
= new AccessibilityNodeInfo.AccessibilityAction(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index fb7976f..c61d7bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -202,8 +202,10 @@
@Override
public void onBiometricAuthenticated(int userId,
- BiometricSourceType biometricSourceType) {
- if (mFirstBypassAttempt && mUpdateMonitor.isUnlockingWithBiometricAllowed()) {
+ BiometricSourceType biometricSourceType,
+ boolean isStrongBiometric) {
+ if (mFirstBypassAttempt
+ && mUpdateMonitor.isUnlockingWithBiometricAllowed(isStrongBiometric)) {
mDelayShowingKeyguardStatusBar = true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 0ab08a8..a7f60d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -293,13 +293,12 @@
}
@Override
- public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
+ public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
+ boolean isStrongBiometric) {
Trace.beginSection("KeyguardUpdateMonitorCallback#onBiometricAuthenticated");
- if (!mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed()) {
- Trace.endSection();
- return;
+ if (mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(isStrongBiometric)) {
+ update(false /* updateAlways */);
}
- update(false /* updateAlways */);
Trace.endSection();
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index befe3e1..6a093963 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -22,6 +22,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -75,6 +76,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
@SmallTest
@@ -117,6 +119,8 @@
private SubscriptionManager mSubscriptionManager;
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
+ @Mock
+ private Executor mBackgroundExecutor;
private TestableLooper mTestableLooper;
private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -137,7 +141,9 @@
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true);
when(mUserManager.isPrimaryUser()).thenReturn(true);
- when(mStrongAuthTracker.isUnlockingWithBiometricAllowed()).thenReturn(true);
+ when(mStrongAuthTracker
+ .isUnlockingWithBiometricAllowed(anyBoolean() /* isStrongBiometric */))
+ .thenReturn(true);
context.addMockSystemService(TrustManager.class, mTrustManager);
context.addMockSystemService(FingerprintManager.class, mFingerprintManager);
context.addMockSystemService(BiometricManager.class, mBiometricManager);
@@ -450,7 +456,10 @@
@Test
public void testOnFaceAuthenticated_skipsFaceWhenAuthenticated() {
- mKeyguardUpdateMonitor.onFaceAuthenticated(KeyguardUpdateMonitor.getCurrentUser());
+ // test whether face will be skipped if authenticated, so the value of isStrongBiometric
+ // doesn't matter here
+ mKeyguardUpdateMonitor.onFaceAuthenticated(KeyguardUpdateMonitor.getCurrentUser(),
+ true /* isStrongBiometric */);
mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(true);
mTestableLooper.processAllMessages();
@@ -460,18 +469,36 @@
@Test
public void testGetUserCanSkipBouncer_whenFace() {
int user = KeyguardUpdateMonitor.getCurrentUser();
- mKeyguardUpdateMonitor.onFaceAuthenticated(user);
+ mKeyguardUpdateMonitor.onFaceAuthenticated(user, true /* isStrongBiometric */);
assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
}
@Test
+ public void testGetUserCanSkipBouncer_whenFace_nonStrongAndDisallowed() {
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isStrongBiometric */))
+ .thenReturn(false);
+ int user = KeyguardUpdateMonitor.getCurrentUser();
+ mKeyguardUpdateMonitor.onFaceAuthenticated(user, false /* isStrongBiometric */);
+ assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse();
+ }
+
+ @Test
public void testGetUserCanSkipBouncer_whenFingerprint() {
int user = KeyguardUpdateMonitor.getCurrentUser();
- mKeyguardUpdateMonitor.onFingerprintAuthenticated(user);
+ mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, true /* isStrongBiometric */);
assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
}
@Test
+ public void testGetUserCanSkipBouncer_whenFingerprint_nonStrongAndDisallowed() {
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isStrongBiometric */))
+ .thenReturn(false);
+ int user = KeyguardUpdateMonitor.getCurrentUser();
+ mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, false /* isStrongBiometric */);
+ assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse();
+ }
+
+ @Test
public void testGetUserCanSkipBouncer_whenTrust() {
int user = KeyguardUpdateMonitor.getCurrentUser();
mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, user, 0 /* flags */);
@@ -585,7 +612,7 @@
protected TestableKeyguardUpdateMonitor(Context context) {
super(context,
TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
- mBroadcastDispatcher, mDumpController);
+ mBroadcastDispatcher, mDumpController, mBackgroundExecutor);
mStrongAuthTracker = KeyguardUpdateMonitorTest.this.mStrongAuthTracker;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 1d4b4be..581d795 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -21,6 +21,7 @@
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 static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
@@ -124,7 +125,7 @@
mContext.addMockSystemService(Context.TRUST_SERVICE, mock(TrustManager.class));
mContext.addMockSystemService(Context.FINGERPRINT_SERVICE, mock(FingerprintManager.class));
- when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+ when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
when(mKeyguardUpdateMonitor.isScreenOn()).thenReturn(true);
when(mKeyguardUpdateMonitor.isUserUnlocked(anyInt())).thenReturn(true);
when(mIndicationArea.findViewById(R.id.keyguard_indication_text)).thenReturn(mTextView);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 769b774..813923d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -97,7 +97,8 @@
when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
- when(mKeyguardBypassController.onBiometricAuthenticated(any())).thenReturn(true);
+ when(mKeyguardBypassController.onBiometricAuthenticated(any(), anyBoolean()))
+ .thenReturn(true);
when(mKeyguardBypassController.canPlaySubtleWindowAnimations()).thenReturn(true);
mContext.addMockSystemService(PowerManager.class, mPowerManager);
mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager);
@@ -112,11 +113,28 @@
@Test
public void onBiometricAuthenticated_whenFingerprintAndBiometricsDisallowed_showBouncer() {
+ when(mUpdateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */))
+ .thenReturn(false);
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
- BiometricSourceType.FINGERPRINT);
+ BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
verify(mStatusBarKeyguardViewManager).showBouncer(eq(false));
verify(mShadeController).animateCollapsePanels(anyInt(), anyBoolean(), anyBoolean(),
anyFloat());
+ assertThat(mBiometricUnlockController.getMode())
+ .isEqualTo(BiometricUnlockController.MODE_SHOW_BOUNCER);
+ }
+
+ @Test
+ public void onBiometricAuthenticated_whenFingerprint_nonStrongBioDisallowed_showBouncer() {
+ when(mUpdateMonitor.isUnlockingWithBiometricAllowed(false /* isStrongBiometric */))
+ .thenReturn(false);
+ mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
+ BiometricSourceType.FINGERPRINT, false /* isStrongBiometric */);
+ verify(mStatusBarKeyguardViewManager).showBouncer(eq(false));
+ verify(mShadeController).animateCollapsePanels(anyInt(), anyBoolean(), anyBoolean(),
+ anyFloat());
+ assertThat(mBiometricUnlockController.getMode())
+ .isEqualTo(BiometricUnlockController.MODE_SHOW_BOUNCER);
}
@Test
@@ -124,44 +142,60 @@
reset(mUpdateMonitor);
reset(mStatusBarKeyguardViewManager);
when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
- when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+ when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
when(mDozeScrimController.isPulsing()).thenReturn(true);
+ // the value of isStrongBiometric doesn't matter here since we only care about the returned
+ // value of isUnlockingWithBiometricAllowed()
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
- BiometricSourceType.FINGERPRINT);
+ BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
verify(mKeyguardViewMediator).onWakeAndUnlocking();
+ assertThat(mBiometricUnlockController.getMode())
+ .isEqualTo(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING);
}
@Test
public void onBiometricAuthenticated_whenFingerprint_dismissKeyguard() {
- when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+ when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+ // the value of isStrongBiometric doesn't matter here since we only care about the returned
+ // value of isUnlockingWithBiometricAllowed()
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
- BiometricSourceType.FINGERPRINT);
+ BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
verify(mShadeController).animateCollapsePanels(anyInt(), anyBoolean(), anyBoolean(),
anyFloat());
+ assertThat(mBiometricUnlockController.getMode())
+ .isEqualTo(BiometricUnlockController.MODE_UNLOCK_COLLAPSING);
}
@Test
public void onBiometricAuthenticated_whenFingerprintOnBouncer_dismissBouncer() {
- when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+ when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+ // the value of isStrongBiometric doesn't matter here since we only care about the returned
+ // value of isUnlockingWithBiometricAllowed()
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
- BiometricSourceType.FINGERPRINT);
+ BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
+ assertThat(mBiometricUnlockController.getMode())
+ .isEqualTo(BiometricUnlockController.MODE_DISMISS_BOUNCER);
}
@Test
public void onBiometricAuthenticated_whenFace_dontDismissKeyguard() {
- when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+ when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+ // the value of isStrongBiometric doesn't matter here since we only care about the returned
+ // value of isUnlockingWithBiometricAllowed()
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
- BiometricSourceType.FACE);
+ BiometricSourceType.FACE, true /* isStrongBiometric */);
verify(mShadeController, never()).animateCollapsePanels(anyInt(), anyBoolean(),
anyBoolean(), anyFloat());
verify(mStatusBarKeyguardViewManager, never()).notifyKeyguardAuthenticated(anyBoolean());
+ assertThat(mBiometricUnlockController.getMode())
+ .isEqualTo(BiometricUnlockController.MODE_NONE);
}
@Test
@@ -169,13 +203,17 @@
when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
- when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+ when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+ // the value of isStrongBiometric doesn't matter here since we only care about the returned
+ // value of isUnlockingWithBiometricAllowed()
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
- BiometricSourceType.FACE);
+ BiometricSourceType.FACE, true /* isStrongBiometric */);
verify(mShadeController, never()).animateCollapsePanels(anyInt(), anyBoolean(),
anyBoolean(), anyFloat());
verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
+ assertThat(mBiometricUnlockController.getMode())
+ .isEqualTo(BiometricUnlockController.MODE_UNLOCK_FADING);
}
@Test
@@ -184,9 +222,11 @@
when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
- when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(false);
+ when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+ // the value of isStrongBiometric doesn't matter here since we only care about the returned
+ // value of isUnlockingWithBiometricAllowed()
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
- BiometricSourceType.FACE);
+ BiometricSourceType.FACE, true /* isStrongBiometric */);
// Wake up before showing the bouncer
verify(mStatusBarKeyguardViewManager, never()).showBouncer(eq(false));
@@ -202,9 +242,11 @@
reset(mUpdateMonitor);
mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
- when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(false);
+ when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+ // the value of isStrongBiometric doesn't matter here since we only care about the returned
+ // value of isUnlockingWithBiometricAllowed()
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
- BiometricSourceType.FACE);
+ BiometricSourceType.FACE, true /* isStrongBiometric */);
verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
verify(mShadeController, never()).animateCollapsePanels(anyInt(), anyBoolean(),
@@ -215,23 +257,30 @@
@Test
public void onBiometricAuthenticated_whenFaceOnBouncer_dismissBouncer() {
- when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+ when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+ // the value of isStrongBiometric doesn't matter here since we only care about the returned
+ // value of isUnlockingWithBiometricAllowed()
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
- BiometricSourceType.FACE);
+ BiometricSourceType.FACE, true /* isStrongBiometric */);
verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
+ assertThat(mBiometricUnlockController.getMode())
+ .isEqualTo(BiometricUnlockController.MODE_DISMISS_BOUNCER);
}
@Test
public void onBiometricAuthenticated_whenBypassOnBouncer_dismissBouncer() {
reset(mKeyguardBypassController);
- when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+ when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
- when(mKeyguardBypassController.onBiometricAuthenticated(any())).thenReturn(true);
+ when(mKeyguardBypassController.onBiometricAuthenticated(any(), anyBoolean()))
+ .thenReturn(true);
when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+ // the value of isStrongBiometric doesn't matter here since we only care about the returned
+ // value of isUnlockingWithBiometricAllowed()
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
- BiometricSourceType.FACE);
+ BiometricSourceType.FACE, true /* isStrongBiometric */);
verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
assertThat(mBiometricUnlockController.getMode())
@@ -240,11 +289,13 @@
@Test
public void onBiometricAuthenticated_whenBypassOnBouncer_respectsCanPlaySubtleAnim() {
- when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+ when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+ // the value of isStrongBiometric doesn't matter here since we only care about the returned
+ // value of isUnlockingWithBiometricAllowed()
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
- BiometricSourceType.FACE);
+ BiometricSourceType.FACE, true /* isStrongBiometric */);
verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
assertThat(mBiometricUnlockController.getMode())
@@ -255,13 +306,17 @@
public void onBiometricAuthenticated_whenFaceAndPulsing_dontDismissKeyguard() {
reset(mUpdateMonitor);
reset(mStatusBarKeyguardViewManager);
- when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+ when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
when(mDozeScrimController.isPulsing()).thenReturn(true);
+ // the value of isStrongBiometric doesn't matter here since we only care about the returned
+ // value of isUnlockingWithBiometricAllowed()
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
- BiometricSourceType.FACE);
+ BiometricSourceType.FACE, true /* isStrongBiometric */);
verify(mShadeController, never()).animateCollapsePanels(anyInt(), anyBoolean(),
anyBoolean(), anyFloat());
+ assertThat(mBiometricUnlockController.getMode())
+ .isEqualTo(BiometricUnlockController.MODE_ONLY_WAKE);
}
@Test
@@ -270,8 +325,10 @@
mBiometricUnlockController.onFinishedGoingToSleep(-1);
verify(mHandler, never()).post(any());
+ // the value of isStrongBiometric doesn't matter here since we only care about the returned
+ // value of isUnlockingWithBiometricAllowed()
mBiometricUnlockController.onBiometricAuthenticated(1 /* userId */,
- BiometricSourceType.FACE);
+ BiometricSourceType.FACE, true /* isStrongBiometric */);
mBiometricUnlockController.onFinishedGoingToSleep(-1);
verify(mHandler).post(any());
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index f544517..317ce4c 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1158,6 +1158,18 @@
} catch (RemoteException e) {
Slog.e(TAG, "Error requesting to hide fill UI", e);
}
+ try {
+ final InlineSuggestionSession.ImeResponse imeResponse =
+ mInlineSuggestionSession.waitAndGetImeResponse();
+ if (imeResponse == null) {
+ Log.w(TAG, "Session input method callback is not set yet");
+ return;
+ }
+ imeResponse.getCallback().onInlineSuggestionsResponse(
+ new InlineSuggestionsResponse(Collections.EMPTY_LIST));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException hiding inline suggestions");
+ }
}
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
index a8886fc..17cdf61 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
@@ -82,12 +82,17 @@
if (sDebug) Slog.d(TAG, "createInlineSuggestionsResponse called");
final BiConsumer<Dataset, Integer> onClickFactory;
if (response.getAuthentication() != null) {
- onClickFactory = (dataset, datasetIndex) -> client.authenticate(response.getRequestId(),
- datasetIndex, response.getAuthentication(), response.getClientState(),
- /* authenticateInline= */ true);
+ onClickFactory = (dataset, datasetIndex) -> {
+ client.requestHideFillUi(autofillId);
+ client.authenticate(response.getRequestId(),
+ datasetIndex, response.getAuthentication(), response.getClientState(),
+ /* authenticateInline= */ true);
+ };
} else {
- onClickFactory = (dataset, datasetIndex) ->
- client.fill(response.getRequestId(), datasetIndex, dataset);
+ onClickFactory = (dataset, datasetIndex) -> {
+ client.requestHideFillUi(autofillId);
+ client.fill(response.getRequestId(), datasetIndex, dataset);
+ };
}
final List<Dataset> datasetList = response.getDatasets();
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 5d8a0f6..4670d58 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -401,10 +401,10 @@
pw.print(prefix); pw.print("hasStartedWhitelistingBgActivityStarts=");
pw.println(mHasStartedWhitelistingBgActivityStarts);
}
- if (mAllowWhileInUsePermissionInFgs) {
- pw.print(prefix); pw.print("allowWhileInUsePermissionInFgs=");
- pw.println(mAllowWhileInUsePermissionInFgs);
- }
+ pw.print(prefix); pw.print("allowWhileInUsePermissionInFgs=");
+ pw.println(mAllowWhileInUsePermissionInFgs);
+ pw.print(prefix); pw.print("recentCallingPackage=");
+ pw.println(mRecentCallingPackage);
if (delayed) {
pw.print(prefix); pw.print("delayed="); pw.println(delayed);
}
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 204f072..8ed221d 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -311,6 +311,7 @@
}
authenticator = new FingerprintAuthenticator(fingerprintService);
+ fingerprintService.initConfiguredStrength(config.mStrength);
break;
case TYPE_FACE:
@@ -322,6 +323,7 @@
}
authenticator = new FaceAuthenticator(faceService);
+ faceService.initConfiguredStrength(config.mStrength);
break;
case TYPE_IRIS:
@@ -333,6 +335,7 @@
}
authenticator = new IrisAuthenticator(irisService);
+ irisService.initConfiguredStrength(config.mStrength);
break;
default:
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 0e70994..74c70df 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -31,6 +31,7 @@
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricNativeHandle;
import android.hardware.biometrics.IBiometricService;
@@ -106,6 +107,7 @@
private PerformanceStats mPerformanceStats;
protected int mCurrentUserId = UserHandle.USER_NULL;
protected long mHalDeviceId;
+ private int mOEMStrength; // Tracks the OEM configured biometric modality strength
// Tracks if the current authentication makes use of CryptoObjects.
protected boolean mIsCrypto;
// Normal authentications are tracked by mPerformanceMap.
@@ -681,6 +683,20 @@
statsModality(), BiometricsProtoEnums.ISSUE_HAL_DEATH);
}
+ protected void initConfiguredStrengthInternal(int strength) {
+ if (DEBUG) {
+ Slog.d(getTag(), "initConfiguredStrengthInternal(" + strength + ")");
+ }
+ mOEMStrength = strength;
+ }
+
+ protected boolean isStrongBiometric() {
+ // TODO(b/141025588): need to calculate actual strength when downgrading tiers
+ final int biometricBits = mOEMStrength
+ & BiometricManager.Authenticators.BIOMETRIC_MIN_STRENGTH;
+ return biometricBits == BiometricManager.Authenticators.BIOMETRIC_STRONG;
+ }
+
protected ClientMonitor getCurrentClient() {
return mCurrentClient;
}
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index 31c3d4d..a87a455 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -740,6 +740,12 @@
}
return 0;
}
+
+ @Override // Binder call
+ public void initConfiguredStrength(int strength) {
+ checkPermission(USE_BIOMETRIC_INTERNAL);
+ initConfiguredStrengthInternal(strength);
+ }
}
/**
@@ -809,7 +815,7 @@
if (mFaceServiceReceiver != null) {
if (biometric == null || biometric instanceof Face) {
mFaceServiceReceiver.onAuthenticationSucceeded(deviceId, (Face) biometric,
- userId);
+ userId, isStrongBiometric());
} else {
Slog.e(TAG, "onAuthenticationSucceeded received non-face biometric");
}
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
index 0a61988..83aa9cf5 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -21,6 +21,7 @@
import static android.Manifest.permission.MANAGE_FINGERPRINT;
import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
import static android.Manifest.permission.USE_BIOMETRIC;
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.Manifest.permission.USE_FINGERPRINT;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
@@ -462,6 +463,12 @@
checkPermission(MANAGE_FINGERPRINT);
mClientActiveCallbacks.remove(callback);
}
+
+ @Override // Binder call
+ public void initConfiguredStrength(int strength) {
+ checkPermission(USE_BIOMETRIC_INTERNAL);
+ initConfiguredStrengthInternal(strength);
+ }
}
/**
@@ -526,8 +533,8 @@
throws RemoteException {
if (mFingerprintServiceReceiver != null) {
if (biometric == null || biometric instanceof Fingerprint) {
- mFingerprintServiceReceiver
- .onAuthenticationSucceeded(deviceId, (Fingerprint) biometric, userId);
+ mFingerprintServiceReceiver.onAuthenticationSucceeded(deviceId,
+ (Fingerprint) biometric, userId, isStrongBiometric());
} else {
Slog.e(TAG, "onAuthenticationSucceeded received non-fingerprint biometric");
}
diff --git a/services/core/java/com/android/server/biometrics/iris/IrisService.java b/services/core/java/com/android/server/biometrics/iris/IrisService.java
index 2817315..903ae6b 100644
--- a/services/core/java/com/android/server/biometrics/iris/IrisService.java
+++ b/services/core/java/com/android/server/biometrics/iris/IrisService.java
@@ -16,9 +16,12 @@
package com.android.server.biometrics.iris;
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.iris.IIrisService;
import com.android.server.biometrics.AuthenticationClient;
import com.android.server.biometrics.BiometricServiceBase;
@@ -42,6 +45,17 @@
private static final String TAG = "IrisService";
/**
+ * Receives the incoming binder calls from IrisManager.
+ */
+ private final class IrisServiceWrapper extends IIrisService.Stub {
+ @Override // Binder call
+ public void initConfiguredStrength(int strength) {
+ checkPermission(USE_BIOMETRIC_INTERNAL);
+ initConfiguredStrengthInternal(strength);
+ }
+ }
+
+ /**
* Initializes the system service.
* <p>
* Subclasses must define a single argument constructor that accepts the context
@@ -57,6 +71,7 @@
@Override
public void onStart() {
super.onStart();
+ publishBinderService(Context.IRIS_SERVICE, new IrisServiceWrapper());
}
@Override
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 1f4048f..15dfab9 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -17,6 +17,7 @@
package com.android.server.locksettings;
import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE;
+import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.READ_CONTACTS;
import static android.content.Context.KEYGUARD_SERVICE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -178,6 +179,7 @@
public class LockSettingsService extends ILockSettings.Stub {
private static final String TAG = "LockSettingsService";
private static final String PERMISSION = ACCESS_KEYGUARD_SECURE_STORAGE;
+ private static final String BIOMETRIC_PERMISSION = MANAGE_BIOMETRIC;
private static final boolean DEBUG = false;
private static final int PROFILE_KEY_IV_SIZE = 12;
@@ -1050,6 +1052,10 @@
}
}
+ private final void checkBiometricPermission() {
+ mContext.enforceCallingOrSelfPermission(BIOMETRIC_PERMISSION, "LockSettingsBiometric");
+ }
+
@Override
public boolean hasSecureLockScreen() {
return mHasSecureLockScreen;
@@ -2304,6 +2310,18 @@
}
@Override
+ public void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId) {
+ checkBiometricPermission();
+ mStrongAuth.reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
+ }
+
+ @Override
+ public void scheduleNonStrongBiometricIdleTimeout(int userId) {
+ checkBiometricPermission();
+ mStrongAuth.scheduleNonStrongBiometricIdleTimeout(userId);
+ }
+
+ @Override
public void userPresent(int userId) {
checkWritePermission(userId);
mStrongAuth.reportUnlock(userId);
@@ -3191,6 +3209,12 @@
mStorage.dump(pw);
pw.println();
pw.decreaseIndent();
+
+ pw.println("StrongAuth:");
+ pw.increaseIndent();
+ mStrongAuth.dump(pw);
+ pw.println();
+ pw.decreaseIndent();
}
/**
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
index 91cf53e..fbee6f4 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
@@ -17,6 +17,7 @@
package com.android.server.locksettings;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
import android.app.AlarmManager;
@@ -32,8 +33,10 @@
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Slog;
+import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker;
/**
@@ -42,6 +45,7 @@
public class LockSettingsStrongAuth {
private static final String TAG = "LockSettings";
+ private static final boolean DEBUG = false;
private static final int MSG_REQUIRE_STRONG_AUTH = 1;
private static final int MSG_REGISTER_TRACKER = 2;
@@ -49,15 +53,40 @@
private static final int MSG_REMOVE_USER = 4;
private static final int MSG_SCHEDULE_STRONG_AUTH_TIMEOUT = 5;
private static final int MSG_NO_LONGER_REQUIRE_STRONG_AUTH = 6;
+ private static final int MSG_SCHEDULE_NON_STRONG_BIOMETRIC_TIMEOUT = 7;
+ private static final int MSG_STRONG_BIOMETRIC_UNLOCK = 8;
+ private static final int MSG_SCHEDULE_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT = 9;
private static final String STRONG_AUTH_TIMEOUT_ALARM_TAG =
"LockSettingsStrongAuth.timeoutForUser";
+ private static final String NON_STRONG_BIOMETRIC_TIMEOUT_ALARM_TAG =
+ "LockSettingsPrimaryAuth.nonStrongBiometricTimeoutForUser";
+ private static final String NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_ALARM_TAG =
+ "LockSettingsPrimaryAuth.nonStrongBiometricIdleTimeoutForUser";
+
+ /**
+ * Default and maximum timeout in milliseconds after which unlocking with weak auth times out,
+ * i.e. the user has to use a strong authentication method like password, PIN or pattern.
+ */
+ public static final long DEFAULT_NON_STRONG_BIOMETRIC_TIMEOUT_MS = 24 * 60 * 60 * 1000; // 24h
+ public static final long DEFAULT_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_MS =
+ 4 * 60 * 60 * 1000; // 4h
private final RemoteCallbackList<IStrongAuthTracker> mTrackers = new RemoteCallbackList<>();
private final SparseIntArray mStrongAuthForUser = new SparseIntArray();
+ private final SparseBooleanArray mIsNonStrongBiometricAllowedForUser = new SparseBooleanArray();
private final ArrayMap<Integer, StrongAuthTimeoutAlarmListener>
mStrongAuthTimeoutAlarmListenerForUser = new ArrayMap<>();
+ // Track non-strong biometric timeout
+ private final ArrayMap<Integer, NonStrongBiometricTimeoutAlarmListener>
+ mNonStrongBiometricTimeoutAlarmListener = new ArrayMap<>();
+ // Track non-strong biometric idle timeout
+ private final ArrayMap<Integer, NonStrongBiometricIdleTimeoutAlarmListener>
+ mNonStrongBiometricIdleTimeoutAlarmListener = new ArrayMap<>();
+
private final int mDefaultStrongAuthFlags;
+ private final boolean mDefaultIsNonStrongBiometricAllowed = true;
+
private final Context mContext;
private AlarmManager mAlarmManager;
@@ -80,6 +109,17 @@
Slog.e(TAG, "Exception while adding StrongAuthTracker.", e);
}
}
+
+ for (int i = 0; i < mIsNonStrongBiometricAllowedForUser.size(); i++) {
+ int key = mIsNonStrongBiometricAllowedForUser.keyAt(i);
+ boolean value = mIsNonStrongBiometricAllowedForUser.valueAt(i);
+ try {
+ tracker.onIsNonStrongBiometricAllowedChanged(value, key);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception while adding StrongAuthTracker: "
+ + "IsNonStrongBiometricAllowedChanged.", e);
+ }
+ }
}
private void handleRemoveStrongAuthTracker(IStrongAuthTracker tracker) {
@@ -134,6 +174,13 @@
mStrongAuthForUser.removeAt(index);
notifyStrongAuthTrackers(mDefaultStrongAuthFlags, userId);
}
+
+ index = mIsNonStrongBiometricAllowedForUser.indexOfKey(userId);
+ if (index >= 0) {
+ mIsNonStrongBiometricAllowedForUser.removeAt(index);
+ notifyStrongAuthTrackersForIsNonStrongBiometricAllowed(
+ mDefaultIsNonStrongBiometricAllowed, userId);
+ }
}
private void handleScheduleStrongAuthTimeout(int userId) {
@@ -151,6 +198,125 @@
// schedule a new alarm listener for the user
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, when, STRONG_AUTH_TIMEOUT_ALARM_TAG,
alarm, mHandler);
+
+ // cancel current non-strong biometric alarm listener for the user (if there was one)
+ cancelNonStrongBiometricAlarmListener(userId);
+ // cancel current non-strong biometric idle alarm listener for the user (if there was one)
+ cancelNonStrongBiometricIdleAlarmListener(userId);
+ // re-allow unlock with non-strong biometrics
+ setIsNonStrongBiometricAllowed(true, userId);
+ }
+
+ private void handleScheduleNonStrongBiometricTimeout(int userId) {
+ if (DEBUG) Slog.d(TAG, "handleScheduleNonStrongBiometricTimeout for userId=" + userId);
+ long when = SystemClock.elapsedRealtime() + DEFAULT_NON_STRONG_BIOMETRIC_TIMEOUT_MS;
+ NonStrongBiometricTimeoutAlarmListener alarm = mNonStrongBiometricTimeoutAlarmListener
+ .get(userId);
+ if (alarm != null) {
+ // Unlock with non-strong biometric will not affect the existing non-strong biometric
+ // timeout alarm
+ if (DEBUG) {
+ Slog.d(TAG, "There is an existing alarm for non-strong biometric"
+ + " fallback timeout, so do not re-schedule");
+ }
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "Schedule a new alarm for non-strong biometric fallback timeout");
+ }
+ alarm = new NonStrongBiometricTimeoutAlarmListener(userId);
+ mNonStrongBiometricTimeoutAlarmListener.put(userId, alarm);
+ // schedule a new alarm listener for the user
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, when,
+ NON_STRONG_BIOMETRIC_TIMEOUT_ALARM_TAG, alarm, mHandler);
+ }
+
+ // cancel current non-strong biometric idle alarm listener for the user (if there was one)
+ cancelNonStrongBiometricIdleAlarmListener(userId);
+ }
+
+ private void handleStrongBiometricUnlock(int userId) {
+ if (DEBUG) Slog.d(TAG, "handleStrongBiometricUnlock for userId=" + userId);
+ // cancel current non-strong biometric alarm listener for the user (if there was one)
+ cancelNonStrongBiometricAlarmListener(userId);
+ // cancel current non-strong biometric idle alarm listener for the user (if there was one)
+ cancelNonStrongBiometricIdleAlarmListener(userId);
+ // re-allow unlock with non-strong biometrics
+ setIsNonStrongBiometricAllowed(true, userId);
+ }
+
+ private void cancelNonStrongBiometricAlarmListener(int userId) {
+ if (DEBUG) Slog.d(TAG, "cancelNonStrongBiometricAlarmListener for userId=" + userId);
+ NonStrongBiometricTimeoutAlarmListener alarm = mNonStrongBiometricTimeoutAlarmListener
+ .get(userId);
+ if (alarm != null) {
+ if (DEBUG) Slog.d(TAG, "Cancel alarm for non-strong biometric fallback timeout");
+ mAlarmManager.cancel(alarm);
+ // need to remove the alarm when cancelled by primary auth or strong biometric
+ mNonStrongBiometricTimeoutAlarmListener.remove(userId);
+ }
+ }
+
+ private void cancelNonStrongBiometricIdleAlarmListener(int userId) {
+ if (DEBUG) Slog.d(TAG, "cancelNonStrongBiometricIdleAlarmListener for userId=" + userId);
+ // cancel idle alarm listener by any unlocks (i.e. primary auth, strong biometric,
+ // non-strong biometric)
+ NonStrongBiometricIdleTimeoutAlarmListener alarm =
+ mNonStrongBiometricIdleTimeoutAlarmListener.get(userId);
+ if (alarm != null) {
+ if (DEBUG) Slog.d(TAG, "Cancel alarm for non-strong biometric idle timeout");
+ mAlarmManager.cancel(alarm);
+ }
+ }
+
+ private void setIsNonStrongBiometricAllowed(boolean allowed, int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "setIsNonStrongBiometricAllowed for allowed=" + allowed
+ + ", userId=" + userId);
+ }
+ if (userId == UserHandle.USER_ALL) {
+ for (int i = 0; i < mIsNonStrongBiometricAllowedForUser.size(); i++) {
+ int key = mIsNonStrongBiometricAllowedForUser.keyAt(i);
+ setIsNonStrongBiometricAllowedOneUser(allowed, key);
+ }
+ } else {
+ setIsNonStrongBiometricAllowedOneUser(allowed, userId);
+ }
+ }
+
+ private void setIsNonStrongBiometricAllowedOneUser(boolean allowed, int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "setIsNonStrongBiometricAllowedOneUser for allowed=" + allowed
+ + ", userId=" + userId);
+ }
+ boolean oldValue = mIsNonStrongBiometricAllowedForUser.get(userId,
+ mDefaultIsNonStrongBiometricAllowed);
+ if (allowed != oldValue) {
+ if (DEBUG) {
+ Slog.d(TAG, "mIsNonStrongBiometricAllowedForUser value changed:"
+ + " oldValue=" + oldValue + ", allowed=" + allowed);
+ }
+ mIsNonStrongBiometricAllowedForUser.put(userId, allowed);
+ notifyStrongAuthTrackersForIsNonStrongBiometricAllowed(allowed, userId);
+ }
+ }
+
+ private void handleScheduleNonStrongBiometricIdleTimeout(int userId) {
+ if (DEBUG) Slog.d(TAG, "handleScheduleNonStrongBiometricIdleTimeout for userId=" + userId);
+ long when = SystemClock.elapsedRealtime() + DEFAULT_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_MS;
+ // cancel current alarm listener for the user (if there was one)
+ NonStrongBiometricIdleTimeoutAlarmListener alarm =
+ mNonStrongBiometricIdleTimeoutAlarmListener.get(userId);
+ if (alarm != null) {
+ if (DEBUG) Slog.d(TAG, "Cancel existing alarm for non-strong biometric idle timeout");
+ mAlarmManager.cancel(alarm);
+ } else {
+ alarm = new NonStrongBiometricIdleTimeoutAlarmListener(userId);
+ mNonStrongBiometricIdleTimeoutAlarmListener.put(userId, alarm);
+ }
+ // schedule a new alarm listener for the user
+ if (DEBUG) Slog.d(TAG, "Schedule a new alarm for non-strong biometric idle timeout");
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, when,
+ NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_ALARM_TAG, alarm, mHandler);
}
private void notifyStrongAuthTrackers(int strongAuthReason, int userId) {
@@ -170,6 +336,29 @@
}
}
+ private void notifyStrongAuthTrackersForIsNonStrongBiometricAllowed(boolean allowed,
+ int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyStrongAuthTrackersForIsNonStrongBiometricAllowed"
+ + " for allowed=" + allowed + ", userId=" + userId);
+ }
+ int i = mTrackers.beginBroadcast();
+ try {
+ while (i > 0) {
+ i--;
+ try {
+ mTrackers.getBroadcastItem(i).onIsNonStrongBiometricAllowedChanged(
+ allowed, userId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception while notifying StrongAuthTracker: "
+ + "IsNonStrongBiometricAllowedChanged.", e);
+ }
+ }
+ } finally {
+ mTrackers.finishBroadcast();
+ }
+ }
+
public void registerStrongAuthTracker(IStrongAuthTracker tracker) {
mHandler.obtainMessage(MSG_REGISTER_TRACKER, tracker).sendToTarget();
}
@@ -207,11 +396,45 @@
requireStrongAuth(STRONG_AUTH_NOT_REQUIRED, userId);
}
+ /**
+ * Report successful unlocking with primary auth
+ */
public void reportSuccessfulStrongAuthUnlock(int userId) {
final int argNotUsed = 0;
mHandler.obtainMessage(MSG_SCHEDULE_STRONG_AUTH_TIMEOUT, userId, argNotUsed).sendToTarget();
}
+ /**
+ * Report successful unlocking with biometric
+ */
+ public void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "reportSuccessfulBiometricUnlock for isStrongBiometric="
+ + isStrongBiometric + ", userId=" + userId);
+ }
+ final int argNotUsed = 0;
+ if (isStrongBiometric) { // unlock with strong biometric
+ mHandler.obtainMessage(MSG_STRONG_BIOMETRIC_UNLOCK, userId, argNotUsed)
+ .sendToTarget();
+ } else { // unlock with non-strong biometric (i.e. weak or convenience)
+ mHandler.obtainMessage(MSG_SCHEDULE_NON_STRONG_BIOMETRIC_TIMEOUT, userId, argNotUsed)
+ .sendToTarget();
+ }
+ }
+
+ /**
+ * Schedule idle timeout for non-strong biometric (i.e. weak or convenience)
+ */
+ public void scheduleNonStrongBiometricIdleTimeout(int userId) {
+ if (DEBUG) Slog.d(TAG, "scheduleNonStrongBiometricIdleTimeout for userId=" + userId);
+ final int argNotUsed = 0;
+ mHandler.obtainMessage(MSG_SCHEDULE_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT, userId, argNotUsed)
+ .sendToTarget();
+ }
+
+ /**
+ * Alarm of fallback timeout for primary auth
+ */
private class StrongAuthTimeoutAlarmListener implements OnAlarmListener {
private final int mUserId;
@@ -226,6 +449,41 @@
}
}
+ /**
+ * Alarm of fallback timeout for non-strong biometric (i.e. weak or convenience)
+ */
+ private class NonStrongBiometricTimeoutAlarmListener implements OnAlarmListener {
+
+ private final int mUserId;
+
+ NonStrongBiometricTimeoutAlarmListener(int userId) {
+ mUserId = userId;
+ }
+
+ @Override
+ public void onAlarm() {
+ requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT, mUserId);
+ }
+ }
+
+ /**
+ * Alarm of idle timeout for non-strong biometric (i.e. weak or convenience biometric)
+ */
+ private class NonStrongBiometricIdleTimeoutAlarmListener implements OnAlarmListener {
+
+ private final int mUserId;
+
+ NonStrongBiometricIdleTimeoutAlarmListener(int userId) {
+ mUserId = userId;
+ }
+
+ @Override
+ public void onAlarm() {
+ // disallow unlock with non-strong biometrics
+ setIsNonStrongBiometricAllowed(false, mUserId);
+ }
+ }
+
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
@@ -248,8 +506,38 @@
case MSG_NO_LONGER_REQUIRE_STRONG_AUTH:
handleNoLongerRequireStrongAuth(msg.arg1, msg.arg2);
break;
+ case MSG_SCHEDULE_NON_STRONG_BIOMETRIC_TIMEOUT:
+ handleScheduleNonStrongBiometricTimeout(msg.arg1);
+ break;
+ case MSG_STRONG_BIOMETRIC_UNLOCK:
+ handleStrongBiometricUnlock(msg.arg1);
+ break;
+ case MSG_SCHEDULE_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT:
+ handleScheduleNonStrongBiometricIdleTimeout(msg.arg1);
+ break;
}
}
};
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("PrimaryAuthFlags state:");
+ pw.increaseIndent();
+ for (int i = 0; i < mStrongAuthForUser.size(); i++) {
+ final int key = mStrongAuthForUser.keyAt(i);
+ final int value = mStrongAuthForUser.valueAt(i);
+ pw.println("userId=" + key + ", primaryAuthFlags=" + Integer.toHexString(value));
+ }
+ pw.println();
+ pw.decreaseIndent();
+
+ pw.println("NonStrongBiometricAllowed state:");
+ pw.increaseIndent();
+ for (int i = 0; i < mIsNonStrongBiometricAllowedForUser.size(); i++) {
+ final int key = mIsNonStrongBiometricAllowedForUser.keyAt(i);
+ final boolean value = mIsNonStrongBiometricAllowedForUser.valueAt(i);
+ pw.println("userId=" + key + ", allowed=" + value);
+ }
+ pw.println();
+ pw.decreaseIndent();
+ }
}
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 9b1824f..f144405 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -53,15 +53,15 @@
public abstract void requestCreateSession(String packageName, String routeId, long requestId,
@Nullable Bundle sessionHints);
- public abstract void releaseSession(String sessionId);
+ public abstract void releaseSession(String sessionId, long requestId);
public abstract void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference);
- public abstract void selectRoute(String sessionId, String routeId);
- public abstract void deselectRoute(String sessionId, String routeId);
- public abstract void transferToRoute(String sessionId, String routeId);
+ public abstract void selectRoute(String sessionId, String routeId, long requestId);
+ public abstract void deselectRoute(String sessionId, String routeId, long requestId);
+ public abstract void transferToRoute(String sessionId, String routeId, long requestId);
- public abstract void setRouteVolume(String routeId, int volume);
- public abstract void setSessionVolume(String sessionId, int volume);
+ public abstract void setRouteVolume(String routeId, int volume, long requestId);
+ public abstract void setSessionVolume(String sessionId, int volume, long requestId);
@NonNull
public String getUniqueId() {
@@ -116,5 +116,6 @@
@NonNull RoutingSessionInfo sessionInfo);
void onSessionReleased(@NonNull MediaRoute2Provider provider,
@NonNull RoutingSessionInfo sessionInfo);
+ void onRequestFailed(@NonNull MediaRoute2Provider provider, long requestId, int reason);
}
}
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 60934e0..e64776c 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -87,9 +87,9 @@
}
@Override
- public void releaseSession(String sessionId) {
+ public void releaseSession(String sessionId, long requestId) {
if (mConnectionReady) {
- mActiveConnection.releaseSession(sessionId);
+ mActiveConnection.releaseSession(sessionId, requestId);
updateBinding();
}
}
@@ -103,38 +103,38 @@
}
@Override
- public void selectRoute(String sessionId, String routeId) {
+ public void selectRoute(String sessionId, String routeId, long requestId) {
if (mConnectionReady) {
- mActiveConnection.selectRoute(sessionId, routeId);
+ mActiveConnection.selectRoute(sessionId, routeId, requestId);
}
}
@Override
- public void deselectRoute(String sessionId, String routeId) {
+ public void deselectRoute(String sessionId, String routeId, long requestId) {
if (mConnectionReady) {
- mActiveConnection.deselectRoute(sessionId, routeId);
+ mActiveConnection.deselectRoute(sessionId, routeId, requestId);
}
}
@Override
- public void transferToRoute(String sessionId, String routeId) {
+ public void transferToRoute(String sessionId, String routeId, long requestId) {
if (mConnectionReady) {
- mActiveConnection.transferToRoute(sessionId, routeId);
+ mActiveConnection.transferToRoute(sessionId, routeId, requestId);
}
}
@Override
- public void setRouteVolume(String routeId, int volume) {
+ public void setRouteVolume(String routeId, int volume, long requestId) {
if (mConnectionReady) {
- mActiveConnection.setRouteVolume(routeId, volume);
+ mActiveConnection.setRouteVolume(routeId, volume, requestId);
updateBinding();
}
}
@Override
- public void setSessionVolume(String sessionId, int volume) {
+ public void setSessionVolume(String sessionId, int volume, long requestId) {
if (mConnectionReady) {
- mActiveConnection.setSessionVolume(sessionId, volume);
+ mActiveConnection.setSessionVolume(sessionId, volume, requestId);
updateBinding();
}
}
@@ -333,8 +333,8 @@
return;
}
- if (requestId == MediaRoute2ProviderService.REQUEST_ID_UNKNOWN) {
- Slog.w(TAG, "onSessionCreationFailed: Ignoring requestId REQUEST_ID_UNKNOWN");
+ if (requestId == MediaRoute2ProviderService.REQUEST_ID_NONE) {
+ Slog.w(TAG, "onSessionCreationFailed: Ignoring requestId REQUEST_ID_NONE");
return;
}
@@ -406,6 +406,19 @@
mCallback.onSessionReleased(this, sessionInfo);
}
+ private void onRequestFailed(Connection connection, long requestId, int reason) {
+ if (mActiveConnection != connection) {
+ return;
+ }
+
+ if (requestId == MediaRoute2ProviderService.REQUEST_ID_NONE) {
+ Slog.w(TAG, "onRequestFailed: Ignoring requestId REQUEST_ID_NONE");
+ return;
+ }
+
+ mCallback.onRequestFailed(this, requestId, reason);
+ }
+
private void disconnect() {
if (mActiveConnection != null) {
mConnectionReady = false;
@@ -461,9 +474,9 @@
}
}
- public void releaseSession(String sessionId) {
+ public void releaseSession(String sessionId, long requestId) {
try {
- mService.releaseSession(sessionId);
+ mService.releaseSession(sessionId, requestId);
} catch (RemoteException ex) {
Slog.e(TAG, "releaseSession: Failed to deliver request.");
}
@@ -477,41 +490,41 @@
}
}
- public void selectRoute(String sessionId, String routeId) {
+ public void selectRoute(String sessionId, String routeId, long requestId) {
try {
- mService.selectRoute(sessionId, routeId);
+ mService.selectRoute(sessionId, routeId, requestId);
} catch (RemoteException ex) {
Slog.e(TAG, "selectRoute: Failed to deliver request.", ex);
}
}
- public void deselectRoute(String sessionId, String routeId) {
+ public void deselectRoute(String sessionId, String routeId, long requestId) {
try {
- mService.deselectRoute(sessionId, routeId);
+ mService.deselectRoute(sessionId, routeId, requestId);
} catch (RemoteException ex) {
Slog.e(TAG, "deselectRoute: Failed to deliver request.", ex);
}
}
- public void transferToRoute(String sessionId, String routeId) {
+ public void transferToRoute(String sessionId, String routeId, long requestId) {
try {
- mService.transferToRoute(sessionId, routeId);
+ mService.transferToRoute(sessionId, routeId, requestId);
} catch (RemoteException ex) {
Slog.e(TAG, "transferToRoute: Failed to deliver request.", ex);
}
}
- public void setRouteVolume(String routeId, int volume) {
+ public void setRouteVolume(String routeId, int volume, long requestId) {
try {
- mService.setRouteVolume(routeId, volume);
+ mService.setRouteVolume(routeId, volume, requestId);
} catch (RemoteException ex) {
Slog.e(TAG, "setRouteVolume: Failed to deliver request.", ex);
}
}
- public void setSessionVolume(String sessionId, int volume) {
+ public void setSessionVolume(String sessionId, int volume, long requestId) {
try {
- mService.setSessionVolume(sessionId, volume);
+ mService.setSessionVolume(sessionId, volume, requestId);
} catch (RemoteException ex) {
Slog.e(TAG, "setSessionVolume: Failed to deliver request.", ex);
}
@@ -541,6 +554,10 @@
void postSessionReleased(RoutingSessionInfo sessionInfo) {
mHandler.post(() -> onSessionReleased(Connection.this, sessionInfo));
}
+
+ void postSessionReleased(long requestId, int reason) {
+ mHandler.post(() -> onRequestFailed(Connection.this, requestId, reason));
+ }
}
private static final class ServiceCallbackStub extends
@@ -594,5 +611,13 @@
connection.postSessionReleased(sessionInfo);
}
}
+
+ @Override
+ public void notifyRequestFailed(long requestId, int reason) {
+ Connection connection = mConnectionRef.get();
+ if (connection != null) {
+ connection.postSessionReleased(requestId, reason);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 3588916..e78a35c 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -16,6 +16,7 @@
package com.android.server.media;
+import static android.media.MediaRoute2ProviderService.REQUEST_ID_NONE;
import static android.media.MediaRouter2Utils.getOriginalId;
import static android.media.MediaRouter2Utils.getProviderId;
@@ -30,7 +31,6 @@
import android.media.IMediaRouter2Manager;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
-import android.media.MediaRoute2ProviderService;
import android.media.RouteDiscoveryPreference;
import android.media.RoutingSessionInfo;
import android.os.Binder;
@@ -70,6 +70,12 @@
private static final String TAG = "MR2ServiceImpl";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ /**
+ * TODO: Change this with the real request ID from MediaRouter2 when
+ * MediaRouter2 needs to get notified for the failures.
+ */
+ private static final long DUMMY_REQUEST_ID = -1;
+
private final Context mContext;
private final Object mLock = new Object();
final AtomicInteger mNextRouterOrManagerId = new AtomicInteger(1);
@@ -365,14 +371,14 @@
}
public void setRouteVolumeWithManager(IMediaRouter2Manager manager,
- MediaRoute2Info route, int volume) {
+ MediaRoute2Info route, int volume, int requestId) {
Objects.requireNonNull(manager, "manager must not be null");
Objects.requireNonNull(route, "route must not be null");
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- setRouteVolumeWithManagerLocked(manager, route, volume);
+ setRouteVolumeWithManagerLocked(manager, route, volume, requestId);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -397,7 +403,7 @@
}
public void selectRouteWithManager(IMediaRouter2Manager manager, String uniqueSessionId,
- MediaRoute2Info route) {
+ MediaRoute2Info route, int requestId) {
Objects.requireNonNull(manager, "manager must not be null");
if (TextUtils.isEmpty(uniqueSessionId)) {
throw new IllegalArgumentException("uniqueSessionId must not be empty");
@@ -407,7 +413,7 @@
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- selectRouteWithManagerLocked(manager, uniqueSessionId, route);
+ selectRouteWithManagerLocked(manager, uniqueSessionId, route, requestId);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -415,7 +421,7 @@
}
public void deselectRouteWithManager(IMediaRouter2Manager manager, String uniqueSessionId,
- MediaRoute2Info route) {
+ MediaRoute2Info route, int requestId) {
Objects.requireNonNull(manager, "manager must not be null");
if (TextUtils.isEmpty(uniqueSessionId)) {
throw new IllegalArgumentException("uniqueSessionId must not be empty");
@@ -425,7 +431,7 @@
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- deselectRouteWithManagerLocked(manager, uniqueSessionId, route);
+ deselectRouteWithManagerLocked(manager, uniqueSessionId, route, requestId);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -433,7 +439,7 @@
}
public void transferToRouteWithManager(IMediaRouter2Manager manager, String uniqueSessionId,
- MediaRoute2Info route) {
+ MediaRoute2Info route, int requestId) {
Objects.requireNonNull(manager, "manager must not be null");
if (TextUtils.isEmpty(uniqueSessionId)) {
throw new IllegalArgumentException("uniqueSessionId must not be empty");
@@ -443,7 +449,7 @@
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- transferToRouteWithManagerLocked(manager, uniqueSessionId, route);
+ transferToRouteWithManagerLocked(manager, uniqueSessionId, route, requestId);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -451,7 +457,7 @@
}
public void setSessionVolumeWithManager(IMediaRouter2Manager manager,
- String uniqueSessionId, int volume) {
+ String uniqueSessionId, int volume, int requestId) {
Objects.requireNonNull(manager, "manager must not be null");
if (TextUtils.isEmpty(uniqueSessionId)) {
throw new IllegalArgumentException("uniqueSessionId must not be empty");
@@ -460,14 +466,15 @@
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- setSessionVolumeWithManagerLocked(manager, uniqueSessionId, volume);
+ setSessionVolumeWithManagerLocked(manager, uniqueSessionId, volume, requestId);
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
- public void releaseSessionWithManager(IMediaRouter2Manager manager, String uniqueSessionId) {
+ public void releaseSessionWithManager(IMediaRouter2Manager manager, String uniqueSessionId,
+ int requestId) {
Objects.requireNonNull(manager, "manager must not be null");
if (TextUtils.isEmpty(uniqueSessionId)) {
throw new IllegalArgumentException("uniqueSessionId must not be empty");
@@ -476,7 +483,7 @@
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- releaseSessionWithManagerLocked(manager, uniqueSessionId);
+ releaseSessionWithManagerLocked(manager, uniqueSessionId, requestId);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -572,7 +579,7 @@
obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManagers,
routerRecord.mUserRecord.mHandler, routerRecord));
routerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::updateDiscoveryPreference,
+ obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
routerRecord.mUserRecord.mHandler));
}
@@ -584,7 +591,7 @@
if (routerRecord != null) {
routerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::setRouteVolumeOnHandler,
- routerRecord.mUserRecord.mHandler, route, volume));
+ routerRecord.mUserRecord.mHandler, route, volume, DUMMY_REQUEST_ID));
}
}
@@ -615,7 +622,8 @@
routerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::selectRouteOnHandler,
- routerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route));
+ routerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route,
+ DUMMY_REQUEST_ID));
}
private void deselectRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
@@ -629,7 +637,8 @@
routerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::deselectRouteOnHandler,
- routerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route));
+ routerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route,
+ DUMMY_REQUEST_ID));
}
private void transferToRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
@@ -643,7 +652,8 @@
routerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::transferToRouteOnHandler,
- routerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route));
+ routerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route,
+ DUMMY_REQUEST_ID));
}
private void setSessionVolumeWithRouter2Locked(@NonNull IMediaRouter2 router,
@@ -657,7 +667,8 @@
routerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::setSessionVolumeOnHandler,
- routerRecord.mUserRecord.mHandler, uniqueSessionId, volume));
+ routerRecord.mUserRecord.mHandler, uniqueSessionId, volume,
+ DUMMY_REQUEST_ID));
}
private void releaseSessionWithRouter2Locked(@NonNull IMediaRouter2 router,
@@ -671,7 +682,8 @@
routerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::releaseSessionOnHandler,
- routerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId));
+ routerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId,
+ DUMMY_REQUEST_ID));
}
////////////////////////////////////////////////////////////
@@ -744,16 +756,18 @@
}
private void setRouteVolumeWithManagerLocked(@NonNull IMediaRouter2Manager manager,
- @NonNull MediaRoute2Info route, int volume) {
+ @NonNull MediaRoute2Info route, int volume, int requestId) {
final IBinder binder = manager.asBinder();
ManagerRecord managerRecord = mAllManagerRecords.get(binder);
if (managerRecord == null) {
return;
}
+
+ long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
managerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::setRouteVolumeOnHandler,
- managerRecord.mUserRecord.mHandler, route, volume));
+ managerRecord.mUserRecord.mHandler, route, volume, uniqueRequestId));
}
private void requestCreateSessionWithManagerLocked(@NonNull IMediaRouter2Manager manager,
@@ -778,7 +792,7 @@
}
private void selectRouteWithManagerLocked(@NonNull IMediaRouter2Manager manager,
- @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+ @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, int requestId) {
final IBinder binder = manager.asBinder();
ManagerRecord managerRecord = mAllManagerRecords.get(binder);
@@ -790,13 +804,15 @@
RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
.findRouterforSessionLocked(uniqueSessionId);
+ long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
managerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::selectRouteOnHandler,
- managerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route));
+ managerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route,
+ uniqueRequestId));
}
private void deselectRouteWithManagerLocked(@NonNull IMediaRouter2Manager manager,
- @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+ @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, int requestId) {
final IBinder binder = manager.asBinder();
ManagerRecord managerRecord = mAllManagerRecords.get(binder);
@@ -808,13 +824,15 @@
RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
.findRouterforSessionLocked(uniqueSessionId);
+ long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
managerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::deselectRouteOnHandler,
- managerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route));
+ managerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route,
+ uniqueRequestId));
}
private void transferToRouteWithManagerLocked(@NonNull IMediaRouter2Manager manager,
- @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+ @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, int requestId) {
final IBinder binder = manager.asBinder();
ManagerRecord managerRecord = mAllManagerRecords.get(binder);
@@ -826,13 +844,15 @@
RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
.findRouterforSessionLocked(uniqueSessionId);
+ long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
managerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::transferToRouteOnHandler,
- managerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route));
+ managerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route,
+ uniqueRequestId));
}
private void setSessionVolumeWithManagerLocked(@NonNull IMediaRouter2Manager manager,
- @NonNull String uniqueSessionId, int volume) {
+ @NonNull String uniqueSessionId, int volume, int requestId) {
final IBinder binder = manager.asBinder();
ManagerRecord managerRecord = mAllManagerRecords.get(binder);
@@ -840,13 +860,15 @@
return;
}
+ long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
managerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::setSessionVolumeOnHandler,
- managerRecord.mUserRecord.mHandler, uniqueSessionId, volume));
+ managerRecord.mUserRecord.mHandler, uniqueSessionId, volume,
+ uniqueRequestId));
}
private void releaseSessionWithManagerLocked(@NonNull IMediaRouter2Manager manager,
- @NonNull String uniqueSessionId) {
+ @NonNull String uniqueSessionId, int requestId) {
final IBinder binder = manager.asBinder();
ManagerRecord managerRecord = mAllManagerRecords.get(binder);
@@ -856,10 +878,15 @@
RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
.findRouterforSessionLocked(uniqueSessionId);
+ if (routerRecord == null) {
+ return;
+ }
+ long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
managerRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::releaseSessionOnHandler,
- managerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId));
+ managerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId,
+ uniqueRequestId));
}
////////////////////////////////////////////////////////////
@@ -1100,6 +1127,13 @@
this, provider, sessionInfo));
}
+ @Override
+ public void onRequestFailed(@NonNull MediaRoute2Provider provider, long requestId,
+ int reason) {
+ sendMessage(PooledLambda.obtainMessage(UserHandler::onRequestFailedOnHandler,
+ this, provider, requestId, reason));
+ }
+
@Nullable
public RouterRecord findRouterforSessionLocked(@NonNull String uniqueSessionId) {
return mSessionToRouterMap.get(uniqueSessionId);
@@ -1195,7 +1229,7 @@
if (provider == null) {
Slog.w(TAG, "Ignoring session creation request since no provider found for"
+ " given route=" + route);
- notifySessionCreationFailed(routerRecord, toOriginalRequestId(requestId));
+ notifySessionCreationFailedToRouter(routerRecord, toOriginalRequestId(requestId));
return;
}
@@ -1210,7 +1244,7 @@
// routerRecord can be null if the session is system's.
private void selectRouteOnHandler(@Nullable RouterRecord routerRecord,
- @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+ @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, long requestId) {
if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
"selecting")) {
return;
@@ -1222,12 +1256,12 @@
if (provider == null) {
return;
}
- provider.selectRoute(getOriginalId(uniqueSessionId), route.getOriginalId());
+ provider.selectRoute(getOriginalId(uniqueSessionId), route.getOriginalId(), requestId);
}
// routerRecord can be null if the session is system's.
private void deselectRouteOnHandler(@Nullable RouterRecord routerRecord,
- @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+ @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, long requestId) {
if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
"deselecting")) {
return;
@@ -1239,12 +1273,13 @@
if (provider == null) {
return;
}
- provider.deselectRoute(getOriginalId(uniqueSessionId), route.getOriginalId());
+ provider.deselectRoute(getOriginalId(uniqueSessionId), route.getOriginalId(),
+ requestId);
}
// routerRecord can be null if the session is system's.
private void transferToRouteOnHandler(@Nullable RouterRecord routerRecord,
- @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+ @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, long requestId) {
if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
"transferring to")) {
return;
@@ -1256,7 +1291,8 @@
if (provider == null) {
return;
}
- provider.transferToRoute(getOriginalId(uniqueSessionId), route.getOriginalId());
+ provider.transferToRoute(getOriginalId(uniqueSessionId), route.getOriginalId(),
+ requestId);
}
private boolean checkArgumentsForSessionControl(@Nullable RouterRecord routerRecord,
@@ -1299,7 +1335,7 @@
}
private void releaseSessionOnHandler(@NonNull RouterRecord routerRecord,
- @NonNull String uniqueSessionId) {
+ @NonNull String uniqueSessionId, long uniqueRequestId) {
final RouterRecord matchingRecord = mSessionToRouterMap.get(uniqueSessionId);
if (matchingRecord != routerRecord) {
Slog.w(TAG, "Ignoring releasing session from non-matching router."
@@ -1329,14 +1365,14 @@
return;
}
- provider.releaseSession(sessionId);
+ provider.releaseSession(sessionId, uniqueRequestId);
}
private void onSessionCreatedOnHandler(@NonNull MediaRoute2Provider provider,
@NonNull RoutingSessionInfo sessionInfo, long requestId) {
notifySessionCreatedToManagers(getManagers(), sessionInfo);
- if (requestId == MediaRoute2ProviderService.REQUEST_ID_UNKNOWN) {
+ if (requestId == REQUEST_ID_NONE) {
// The session is created without any matching request.
return;
}
@@ -1362,7 +1398,7 @@
if (sessionInfo == null) {
// Failed
- notifySessionCreationFailed(matchingRequest.mRouterRecord,
+ notifySessionCreationFailedToRouter(matchingRequest.mRouterRecord,
toOriginalRequestId(requestId));
return;
}
@@ -1374,13 +1410,13 @@
Slog.w(TAG, "Created session doesn't match the original request."
+ " originalRouteId=" + originalRouteId
+ ", requestId=" + requestId + ", sessionInfo=" + sessionInfo);
- notifySessionCreationFailed(matchingRequest.mRouterRecord,
+ notifySessionCreationFailedToRouter(matchingRequest.mRouterRecord,
toOriginalRequestId(requestId));
return;
}
// Succeeded
- notifySessionCreated(matchingRequest.mRouterRecord,
+ notifySessionCreatedToRouter(matchingRequest.mRouterRecord,
sessionInfo, toOriginalRequestId(requestId));
mSessionToRouterMap.put(sessionInfo.getId(), routerRecord);
}
@@ -1405,7 +1441,7 @@
}
mSessionCreationRequests.remove(matchingRequest);
- notifySessionCreationFailed(matchingRequest.mRouterRecord,
+ notifySessionCreationFailedToRouter(matchingRequest.mRouterRecord,
toOriginalRequestId(requestId));
}
@@ -1429,7 +1465,7 @@
Slog.w(TAG, "No matching router found for session=" + sessionInfo);
return;
}
- notifySessionInfoChanged(routerRecord, sessionInfo);
+ notifySessionInfoChangedToRouter(routerRecord, sessionInfo);
}
private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider,
@@ -1442,10 +1478,38 @@
Slog.w(TAG, "No matching router found for session=" + sessionInfo);
return;
}
- notifySessionReleased(routerRecord, sessionInfo);
+ notifySessionReleasedToRouter(routerRecord, sessionInfo);
}
- private void notifySessionCreated(@NonNull RouterRecord routerRecord,
+ private void onRequestFailedOnHandler(@NonNull MediaRoute2Provider provider,
+ long requestId, int reason) {
+ final int managerId = toRouterOrManagerId(requestId);
+
+ MediaRouter2ServiceImpl service = mServiceRef.get();
+ if (service == null) {
+ return;
+ }
+
+ ManagerRecord managerToNotifyFailure = null;
+ synchronized (service.mLock) {
+ for (ManagerRecord manager : mUserRecord.mManagerRecords) {
+ if (manager.mManagerId == managerId) {
+ managerToNotifyFailure = manager;
+ break;
+ }
+ }
+ }
+
+ if (managerToNotifyFailure == null) {
+ Slog.w(TAG, "No matching managerRecord found for managerId=" + managerId);
+ return;
+ }
+
+ notifyRequestFailedToManager(
+ managerToNotifyFailure.mManager, toOriginalRequestId(requestId), reason);
+ }
+
+ private void notifySessionCreatedToRouter(@NonNull RouterRecord routerRecord,
@NonNull RoutingSessionInfo sessionInfo, int requestId) {
try {
routerRecord.mRouter.notifySessionCreated(sessionInfo, requestId);
@@ -1455,7 +1519,7 @@
}
}
- private void notifySessionCreationFailed(@NonNull RouterRecord routerRecord,
+ private void notifySessionCreationFailedToRouter(@NonNull RouterRecord routerRecord,
int requestId) {
try {
routerRecord.mRouter.notifySessionCreated(/* sessionInfo= */ null, requestId);
@@ -1465,7 +1529,7 @@
}
}
- private void notifySessionInfoChanged(@NonNull RouterRecord routerRecord,
+ private void notifySessionInfoChangedToRouter(@NonNull RouterRecord routerRecord,
@NonNull RoutingSessionInfo sessionInfo) {
try {
routerRecord.mRouter.notifySessionInfoChanged(sessionInfo);
@@ -1475,7 +1539,7 @@
}
}
- private void notifySessionReleased(@NonNull RouterRecord routerRecord,
+ private void notifySessionReleasedToRouter(@NonNull RouterRecord routerRecord,
@NonNull RoutingSessionInfo sessionInfo) {
try {
routerRecord.mRouter.notifySessionReleased(sessionInfo);
@@ -1485,21 +1549,23 @@
}
}
- private void setRouteVolumeOnHandler(@NonNull MediaRoute2Info route, int volume) {
+ private void setRouteVolumeOnHandler(@NonNull MediaRoute2Info route, int volume,
+ long requestId) {
final MediaRoute2Provider provider = findProvider(route.getProviderId());
if (provider != null) {
- provider.setRouteVolume(route.getOriginalId(), volume);
+ provider.setRouteVolume(route.getOriginalId(), volume, requestId);
}
}
- private void setSessionVolumeOnHandler(@NonNull String uniqueSessionId, int volume) {
+ private void setSessionVolumeOnHandler(@NonNull String uniqueSessionId, int volume,
+ long requestId) {
final MediaRoute2Provider provider = findProvider(getProviderId(uniqueSessionId));
if (provider == null) {
Slog.w(TAG, "setSessionVolume: couldn't find provider for session "
+ "id=" + uniqueSessionId);
return;
}
- provider.setSessionVolume(getOriginalId(uniqueSessionId), volume);
+ provider.setSessionVolume(getOriginalId(uniqueSessionId), volume, requestId);
}
private List<IMediaRouter2> getRouters() {
@@ -1683,7 +1749,17 @@
}
}
- private void updateDiscoveryPreference() {
+ private void notifyRequestFailedToManager(@NonNull IMediaRouter2Manager manager,
+ int requestId, int reason) {
+ try {
+ manager.notifyRequestFailed(requestId, reason);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to notify manager of the request failure."
+ + " Manager probably died.", ex);
+ }
+ }
+
+ private void updateDiscoveryPreferenceOnHandler() {
MediaRouter2ServiceImpl service = mServiceRef.get();
if (service == null) {
return;
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 580fc52..a13ee10 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -543,8 +543,8 @@
// Binder call
@Override
public void setRouteVolumeWithManager(IMediaRouter2Manager manager,
- MediaRoute2Info route, int volume) {
- mService2.setRouteVolumeWithManager(manager, route, volume);
+ MediaRoute2Info route, int volume, int requestId) {
+ mService2.setRouteVolumeWithManager(manager, route, volume, requestId);
}
// Binder call
@@ -557,35 +557,36 @@
// Binder call
@Override
public void selectRouteWithManager(IMediaRouter2Manager manager, String sessionId,
- MediaRoute2Info route) {
- mService2.selectRouteWithManager(manager, sessionId, route);
+ MediaRoute2Info route, int requestId) {
+ mService2.selectRouteWithManager(manager, sessionId, route, requestId);
}
// Binder call
@Override
public void deselectRouteWithManager(IMediaRouter2Manager manager, String sessionId,
- MediaRoute2Info route) {
- mService2.deselectRouteWithManager(manager, sessionId, route);
+ MediaRoute2Info route, int requestId) {
+ mService2.deselectRouteWithManager(manager, sessionId, route, requestId);
}
// Binder call
@Override
public void transferToRouteWithManager(IMediaRouter2Manager manager, String sessionId,
- MediaRoute2Info route) {
- mService2.transferToRouteWithManager(manager, sessionId, route);
+ MediaRoute2Info route, int requestId) {
+ mService2.transferToRouteWithManager(manager, sessionId, route, requestId);
}
// Binder call
@Override
public void setSessionVolumeWithManager(IMediaRouter2Manager manager,
- String sessionId, int volume) {
- mService2.setSessionVolumeWithManager(manager, sessionId, volume);
+ String sessionId, int volume, int requestId) {
+ mService2.setSessionVolumeWithManager(manager, sessionId, volume, requestId);
}
// Binder call
@Override
- public void releaseSessionWithManager(IMediaRouter2Manager manager, String sessionId) {
- mService2.releaseSessionWithManager(manager, sessionId);
+ public void releaseSessionWithManager(IMediaRouter2Manager manager, String sessionId,
+ int requestId) {
+ mService2.releaseSessionWithManager(manager, sessionId, requestId);
}
void restoreBluetoothA2dp() {
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index f7e1398..ce72a8a 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -130,26 +130,27 @@
}
@Override
- public void releaseSession(String sessionId) {
+ public void releaseSession(String sessionId, long requestId) {
// Do nothing
}
+
@Override
public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) {
// Do nothing
}
@Override
- public void selectRoute(String sessionId, String routeId) {
+ public void selectRoute(String sessionId, String routeId, long requestId) {
// Do nothing since we don't support multiple BT yet.
}
@Override
- public void deselectRoute(String sessionId, String routeId) {
+ public void deselectRoute(String sessionId, String routeId, long requestId) {
// Do nothing since we don't support multiple BT yet.
}
@Override
- public void transferToRoute(String sessionId, String routeId) {
+ public void transferToRoute(String sessionId, String routeId, long requestId) {
if (TextUtils.equals(routeId, mDefaultRoute.getId())) {
mBtRouteProvider.transferTo(null);
} else {
@@ -158,7 +159,7 @@
}
@Override
- public void setRouteVolume(String routeId, int volume) {
+ public void setRouteVolume(String routeId, int volume, long requestId) {
if (!TextUtils.equals(routeId, mSelectedRouteId)) {
return;
}
@@ -166,7 +167,7 @@
}
@Override
- public void setSessionVolume(String sessionId, int volume) {
+ public void setSessionVolume(String sessionId, int volume, long requestId) {
// Do nothing since we don't support grouping volume yet.
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index fce10e6..c301cd2 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -27,4 +27,6 @@
String tag, int id, int userId);
void removeForegroundServiceFlagFromNotification(String pkg, int notificationId, int userId);
+
+ void onConversationRemoved(String pkg, int uid, String conversationId);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 0d402e5..475f229 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -772,7 +772,7 @@
parser, mAllowedManagedServicePackages, forRestore, userId);
migratedManagedServices = true;
} else if (mSnoozeHelper.XML_TAG_NAME.equals(parser.getName())) {
- mSnoozeHelper.readXml(parser);
+ mSnoozeHelper.readXml(parser, System.currentTimeMillis());
}
if (LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG.equals(parser.getName())) {
if (forRestore && userId != UserHandle.USER_SYSTEM) {
@@ -2322,6 +2322,8 @@
mConditionProviders.onBootPhaseAppsCanStart();
mHistoryManager.onBootPhaseAppsCanStart();
registerDeviceConfigChange();
+ } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+ mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis());
}
}
@@ -5474,6 +5476,11 @@
});
}
+ @Override
+ public void onConversationRemoved(String pkg, int uid, String conversationId) {
+ onConversationRemovedInternal(pkg, uid, conversationId);
+ }
+
@GuardedBy("mNotificationLock")
private void removeForegroundServiceFlagLocked(NotificationRecord r) {
if (r == null) {
@@ -5676,7 +5683,7 @@
mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground));
}
- public void onConversationRemoved(String pkg, int uid, String conversationId) {
+ private void onConversationRemovedInternal(String pkg, int uid, String conversationId) {
checkCallerIsSystem();
Preconditions.checkStringNotEmpty(pkg);
Preconditions.checkStringNotEmpty(conversationId);
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index bae1dd3..d60c291 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -36,6 +36,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -43,9 +44,7 @@
import java.io.IOException;
import java.io.PrintWriter;
-import java.sql.Array;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
@@ -172,7 +171,7 @@
for (int i = 0; i < allRecords.size(); i++) {
NotificationRecord r = allRecords.valueAt(i);
String currentGroupKey = r.getSbn().getGroup();
- if (currentGroupKey.equals(groupKey)) {
+ if (Objects.equals(currentGroupKey, groupKey)) {
records.add(r);
}
}
@@ -217,7 +216,7 @@
snooze(record);
scheduleRepost(pkg, key, userId, duration);
- Long activateAt = System.currentTimeMillis() + duration;
+ Long activateAt = SystemClock.elapsedRealtime() + duration;
synchronized (mPersistedSnoozedNotifications) {
storeRecord(pkg, key, userId, mPersistedSnoozedNotifications, activateAt);
}
@@ -244,8 +243,6 @@
}
storeRecord(record.getSbn().getPackageName(), record.getKey(),
userId, mSnoozedNotifications, record);
- mPackages.put(record.getKey(), record.getSbn().getPackageName());
- mUsers.put(record.getKey(), userId);
}
private <T> void storeRecord(String pkg, String key, Integer userId,
@@ -258,6 +255,8 @@
keyToValue.put(key, object);
targets.put(getPkgKey(userId, pkg), keyToValue);
+ mPackages.put(key, pkg);
+ mUsers.put(key, userId);
}
private <T> T removeRecord(String pkg, String key, Integer userId,
@@ -425,12 +424,34 @@
PendingIntent.FLAG_UPDATE_CURRENT);
}
+ public void scheduleRepostsForPersistedNotifications(long currentTime) {
+ for (ArrayMap<String, Long> snoozed : mPersistedSnoozedNotifications.values()) {
+ for (int i = 0; i < snoozed.size(); i++) {
+ String key = snoozed.keyAt(i);
+ Long time = snoozed.valueAt(i);
+ String pkg = mPackages.get(key);
+ Integer userId = mUsers.get(key);
+ if (time == null || pkg == null || userId == null) {
+ Slog.w(TAG, "data out of sync: " + time + "|" + pkg + "|" + userId);
+ continue;
+ }
+ if (time != null && time > currentTime) {
+ scheduleRepostAtTime(pkg, key, userId, time);
+ }
+ }
+
+ }
+ }
+
private void scheduleRepost(String pkg, String key, int userId, long duration) {
+ scheduleRepostAtTime(pkg, key, userId, SystemClock.elapsedRealtime() + duration);
+ }
+
+ private void scheduleRepostAtTime(String pkg, String key, int userId, long time) {
long identity = Binder.clearCallingIdentity();
try {
final PendingIntent pi = createPendingIntent(pkg, key, userId);
mAm.cancel(pi);
- long time = SystemClock.elapsedRealtime() + duration;
if (DEBUG) Slog.d(TAG, "Scheduling evaluate for " + new Date(time));
mAm.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, time, pi);
} finally {
@@ -496,6 +517,7 @@
private interface Inserter<T> {
void insert(T t) throws IOException;
}
+
private <T> void writeXml(XmlSerializer out,
ArrayMap<String, ArrayMap<String, T>> targets, String tag,
Inserter<T> attributeInserter)
@@ -503,12 +525,13 @@
synchronized (targets) {
final int M = targets.size();
for (int i = 0; i < M; i++) {
- String userIdPkgKey = targets.keyAt(i);
// T is a String (snoozed until context) or Long (snoozed until time)
ArrayMap<String, T> keyToValue = targets.valueAt(i);
for (int j = 0; j < keyToValue.size(); j++) {
String key = keyToValue.keyAt(j);
T value = keyToValue.valueAt(j);
+ String pkg = mPackages.get(key);
+ Integer userId = mUsers.get(key);
out.startTag(null, tag);
@@ -518,8 +541,7 @@
XML_SNOOZED_NOTIFICATION_VERSION);
out.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, key);
- String pkg = mPackages.get(key);
- int userId = mUsers.get(key);
+
out.attribute(null, XML_SNOOZED_NOTIFICATION_PKG, pkg);
out.attribute(null, XML_SNOOZED_NOTIFICATION_USER_ID,
String.valueOf(userId));
@@ -530,7 +552,7 @@
}
}
- protected void readXml(XmlPullParser parser)
+ protected void readXml(XmlPullParser parser, long currentTime)
throws XmlPullParserException, IOException {
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
@@ -547,16 +569,15 @@
try {
final String key = parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_KEY);
final String pkg = parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_PKG);
- final int userId = Integer.parseInt(
- parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_USER_ID));
+ final int userId = XmlUtils.readIntAttribute(
+ parser, XML_SNOOZED_NOTIFICATION_USER_ID, UserHandle.USER_ALL);
if (tag.equals(XML_SNOOZED_NOTIFICATION)) {
- final Long time = Long.parseLong(
- parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_TIME));
- if (time > System.currentTimeMillis()) { //only read new stuff
+ final Long time = XmlUtils.readLongAttribute(
+ parser, XML_SNOOZED_NOTIFICATION_TIME, 0);
+ if (time > currentTime) { //only read new stuff
synchronized (mPersistedSnoozedNotifications) {
storeRecord(pkg, key, userId, mPersistedSnoozedNotifications, time);
}
- scheduleRepost(pkg, key, userId, time - System.currentTimeMillis());
}
}
if (tag.equals(XML_SNOOZED_NOTIFICATION_CONTEXT)) {
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 8ad3e9d..f37af3a 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -234,11 +234,11 @@
}
public void moveCompleteApp(String fromUuid, String toUuid, String packageName,
- String dataAppName, int appId, String seInfo, int targetSdkVersion,
+ int appId, String seInfo, int targetSdkVersion,
String fromCodePath) throws InstallerException {
if (!checkBeforeRemote()) return;
try {
- mInstalld.moveCompleteApp(fromUuid, toUuid, packageName, dataAppName, appId, seInfo,
+ mInstalld.moveCompleteApp(fromUuid, toUuid, packageName, appId, seInfo,
targetSdkVersion, fromCodePath);
} catch (Exception e) {
throw InstallerException.from(e);
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 261caba..8031eaa 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -72,6 +72,7 @@
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.pm.parsing.pkg.AndroidPackage;
@@ -134,6 +135,8 @@
private final MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
+ private final ShortcutChangeHandler mShortcutChangeHandler;
+
private final Handler mCallbackHandler;
private PackageInstallerService mPackageInstallerService;
@@ -153,6 +156,8 @@
mShortcutServiceInternal = Objects.requireNonNull(
LocalServices.getService(ShortcutServiceInternal.class));
mShortcutServiceInternal.addListener(mPackageMonitor);
+ mShortcutChangeHandler = new ShortcutChangeHandler(mUserManagerInternal);
+ mShortcutServiceInternal.addShortcutChangeCallback(mShortcutChangeHandler);
mCallbackHandler = BackgroundThread.getHandler();
mDpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
}
@@ -720,12 +725,37 @@
@Override
public void registerShortcutChangeCallback(String callingPackage, long changedSince,
String packageName, List shortcutIds, List<LocusId> locusIds,
- ComponentName componentName, int flags, IShortcutChangeCallback callback,
- int callbackId) {
+ ComponentName componentName, int flags, IShortcutChangeCallback callback) {
+ ensureShortcutPermission(callingPackage);
+
+ if (shortcutIds != null && packageName == null) {
+ throw new IllegalArgumentException(
+ "To query by shortcut ID, package name must also be set");
+ }
+ if (locusIds != null && packageName == null) {
+ throw new IllegalArgumentException(
+ "To query by locus ID, package name must also be set");
+ }
+
+ UserHandle user = UserHandle.of(injectCallingUserId());
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ == PackageManager.PERMISSION_GRANTED) {
+ user = null;
+ }
+
+ // TODO: When ShortcutQueryWrapper (ag/10323729) is available, pass that directly.
+ ShortcutChangeHandler.QueryInfo query = new ShortcutChangeHandler.QueryInfo(
+ changedSince, packageName, shortcutIds, locusIds, componentName, flags, user);
+ mShortcutChangeHandler.addShortcutChangeCallback(callback, query);
}
@Override
- public void unregisterShortcutChangeCallback(String callingPackage, int callbackId) {
+ public void unregisterShortcutChangeCallback(String callingPackage,
+ IShortcutChangeCallback callback) {
+ ensureShortcutPermission(callingPackage);
+
+ mShortcutChangeHandler.removeShortcutChangeCallback(callback);
}
@Override
@@ -1005,6 +1035,153 @@
mCallbackHandler.post(r);
}
+ public static class ShortcutChangeHandler implements LauncherApps.ShortcutChangeCallback {
+
+ static class QueryInfo {
+ final long mChangedSince;
+ final String mPackage;
+ final List<String> mShortcutIds;
+ final List<LocusId> mLocusIds;
+ final ComponentName mActivity;
+ final int mQueryFlags;
+ final UserHandle mCallbackUser;
+
+ QueryInfo(long changedSince, String packageName, List<String> shortcutIds,
+ List<LocusId> locusIds, ComponentName activity, int flags,
+ UserHandle callbackUser) {
+ mChangedSince = changedSince;
+ mPackage = packageName;
+ mShortcutIds = shortcutIds;
+ mLocusIds = locusIds;
+ mActivity = activity;
+ mQueryFlags = flags;
+ mCallbackUser = callbackUser;
+ }
+ }
+
+ private final UserManagerInternal mUserManagerInternal;
+
+ ShortcutChangeHandler(UserManagerInternal userManager) {
+ mUserManagerInternal = userManager;
+ }
+
+ private final RemoteCallbackList<IShortcutChangeCallback> mCallbacks =
+ new RemoteCallbackList<>();
+
+ public synchronized void addShortcutChangeCallback(IShortcutChangeCallback callback,
+ QueryInfo query) {
+ mCallbacks.unregister(callback);
+ mCallbacks.register(callback, query);
+ }
+
+ public synchronized void removeShortcutChangeCallback(
+ IShortcutChangeCallback callback) {
+ mCallbacks.unregister(callback);
+ }
+
+ @Override
+ public void onShortcutsAddedOrUpdated(String packageName, List<ShortcutInfo> shortcuts,
+ UserHandle user) {
+ onShortcutEvent(packageName, shortcuts, user, false);
+ }
+
+ @Override
+ public void onShortcutsRemoved(String packageName, List<ShortcutInfo> shortcuts,
+ UserHandle user) {
+ onShortcutEvent(packageName, shortcuts, user, true);
+ }
+
+ private void onShortcutEvent(String packageName,
+ List<ShortcutInfo> shortcuts, UserHandle user, boolean shortcutsRemoved) {
+ int count = mCallbacks.beginBroadcast();
+
+ for (int i = 0; i < count; i++) {
+ final IShortcutChangeCallback callback = mCallbacks.getBroadcastItem(i);
+ final QueryInfo query = (QueryInfo) mCallbacks.getBroadcastCookie(i);
+
+ if (query.mCallbackUser != null && !hasUserAccess(query.mCallbackUser, user)) {
+ // Callback owner does not have access to the shortcuts' user.
+ continue;
+ }
+
+ // Filter the list by query, if any matches exists, send via callback.
+ List<ShortcutInfo> matchedList =
+ filterShortcutsByQuery(packageName, shortcuts, query);
+ if (!CollectionUtils.isEmpty(matchedList)) {
+ try {
+ if (shortcutsRemoved) {
+ callback.onShortcutsRemoved(packageName, matchedList, user);
+ } else {
+ callback.onShortcutsAddedOrUpdated(packageName, matchedList, user);
+ }
+ } catch (RemoteException e) {
+ // The RemoteCallbackList will take care of removing the dead object.
+ }
+ }
+ }
+
+ mCallbacks.finishBroadcast();
+ }
+
+ public static List<ShortcutInfo> filterShortcutsByQuery(String packageName,
+ List<ShortcutInfo> shortcuts, QueryInfo query) {
+ if (query.mPackage != null && query.mPackage != packageName) {
+ return null;
+ }
+
+ List<ShortcutInfo> matches = new ArrayList<>();
+
+ final boolean matchDynamic =
+ (query.mQueryFlags & ShortcutQuery.FLAG_MATCH_DYNAMIC) != 0;
+ final boolean matchPinned =
+ (query.mQueryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0;
+ final boolean matchManifest =
+ (query.mQueryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) != 0;
+ final boolean matchCached =
+ (query.mQueryFlags & ShortcutQuery.FLAG_MATCH_CACHED) != 0;
+ final int shortcutFlags = (matchDynamic ? ShortcutInfo.FLAG_DYNAMIC : 0)
+ | (matchPinned ? ShortcutInfo.FLAG_PINNED : 0)
+ | (matchManifest ? ShortcutInfo.FLAG_MANIFEST : 0)
+ | (matchCached ? ShortcutInfo.FLAG_CACHED : 0);
+
+ for (int i = 0; i < shortcuts.size(); i++) {
+ final ShortcutInfo si = shortcuts.get(i);
+
+ if (query.mActivity != null && !query.mActivity.equals(si.getActivity())) {
+ continue;
+ }
+
+ if (query.mChangedSince != 0
+ && query.mChangedSince > si.getLastChangedTimestamp()) {
+ continue;
+ }
+
+ if (query.mShortcutIds != null && !query.mShortcutIds.contains(si.getId())) {
+ continue;
+ }
+
+ if (query.mLocusIds != null && !query.mLocusIds.contains(si.getLocusId())) {
+ continue;
+ }
+
+ if ((shortcutFlags & si.getFlags()) != 0) {
+ matches.add(si);
+ }
+ }
+
+ return matches;
+ }
+
+ private boolean hasUserAccess(UserHandle callbackUser, UserHandle shortcutUser) {
+ final int callbackUserId = callbackUser.getIdentifier();
+ final int shortcutUserId = shortcutUser.getIdentifier();
+
+ if (shortcutUser == callbackUser) return true;
+ return mUserManagerInternal.isProfileAccessible(callbackUserId, shortcutUserId,
+ null, false);
+ }
+ }
+
private class MyPackageMonitor extends PackageMonitor implements ShortcutChangeListener {
// TODO Simplify with lambdas.
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 92507e5..14964b5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -13991,20 +13991,18 @@
final String fromUuid;
final String toUuid;
final String packageName;
- final String dataAppName;
final int appId;
final String seinfo;
final int targetSdkVersion;
final String fromCodePath;
public MoveInfo(int moveId, String fromUuid, String toUuid, String packageName,
- String dataAppName, int appId, String seinfo, int targetSdkVersion,
+ int appId, String seinfo, int targetSdkVersion,
String fromCodePath) {
this.moveId = moveId;
this.fromUuid = fromUuid;
this.toUuid = toUuid;
this.packageName = packageName;
- this.dataAppName = dataAppName;
this.appId = appId;
this.seinfo = seinfo;
this.targetSdkVersion = targetSdkVersion;
@@ -15120,7 +15118,7 @@
synchronized (mInstaller) {
try {
mInstaller.moveCompleteApp(move.fromUuid, move.toUuid, move.packageName,
- move.dataAppName, move.appId, move.seinfo, move.targetSdkVersion,
+ move.appId, move.seinfo, move.targetSdkVersion,
move.fromCodePath);
} catch (InstallerException e) {
Slog.w(TAG, "Failed to move app", e);
@@ -15128,7 +15126,8 @@
}
}
- codeFile = new File(Environment.getDataAppDirectory(move.toUuid), move.dataAppName);
+ final String toPathName = new File(move.fromCodePath).getName();
+ codeFile = new File(Environment.getDataAppDirectory(move.toUuid), toPathName);
resourceFile = codeFile;
if (DEBUG_INSTALL) Slog.d(TAG, "codeFile after move is " + codeFile);
@@ -15172,8 +15171,9 @@
}
private boolean cleanUp(String volumeUuid) {
+ final String toPathName = new File(move.fromCodePath).getName();
final File codeFile = new File(Environment.getDataAppDirectory(volumeUuid),
- move.dataAppName);
+ toPathName);
Slog.d(TAG, "Cleaning up " + move.packageName + " on " + volumeUuid);
final int[] userIds = mUserManager.getUserIds();
synchronized (mInstallLock) {
@@ -22113,7 +22113,11 @@
targetSdkVersion = pkg.getTargetSdkVersion();
freezer = freezePackage(packageName, "movePackageInternal");
installedUserIds = ps.queryInstalledUsers(mUserManager.getUserIds(), true);
- fromCodePath = pkg.getCodePath();
+ if (codeFile.getParentFile().getName().startsWith(RANDOM_DIR_PREFIX)) {
+ fromCodePath = codeFile.getParentFile().getAbsolutePath();
+ } else {
+ fromCodePath = codeFile.getAbsolutePath();
+ }
}
final Bundle extras = new Bundle();
@@ -22240,9 +22244,8 @@
}
}).start();
- final String dataAppName = codeFile.getName();
move = new MoveInfo(moveId, currentVolumeUuid, volumeUuid, packageName,
- dataAppName, appId, seinfo, targetSdkVersion, fromCodePath);
+ appId, seinfo, targetSdkVersion, fromCodePath);
} else {
move = null;
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 2de9858..c8df5c7 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -36,6 +36,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.server.pm.ShortcutService.DumpFilter;
@@ -365,9 +366,12 @@
/**
* Remove all shortcuts that aren't pinned, cached nor dynamic.
+ *
+ * @return List of removed shortcuts.
*/
- private void removeOrphans() {
+ private List<ShortcutInfo> removeOrphans() {
ArrayList<String> removeList = null; // Lazily initialize.
+ List<ShortcutInfo> removedShortcuts = null;
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
@@ -376,20 +380,26 @@
if (removeList == null) {
removeList = new ArrayList<>();
+ removedShortcuts = new ArrayList<>();
}
removeList.add(si.getId());
+ removedShortcuts.add(si);
}
if (removeList != null) {
for (int i = removeList.size() - 1; i >= 0; i--) {
forceDeleteShortcutInner(removeList.get(i));
}
}
+
+ return removedShortcuts;
}
/**
* Remove all dynamic shortcuts.
+ *
+ * @return List of shortcuts that actually got removed.
*/
- public void deleteAllDynamicShortcuts(boolean ignoreInvisible) {
+ public List<ShortcutInfo> deleteAllDynamicShortcuts(boolean ignoreInvisible) {
final long now = mShortcutUser.mService.injectCurrentTimeMillis();
boolean changed = false;
@@ -404,8 +414,9 @@
}
}
if (changed) {
- removeOrphans();
+ return removeOrphans();
}
+ return null;
}
/**
@@ -1028,7 +1039,8 @@
s.verifyStates();
// This will send a notification to the launcher, and also save .
- s.packageShortcutsChanged(getPackageName(), getPackageUserId());
+ // TODO: List changed and removed manifest shortcuts and pass to packageShortcutsChanged()
+ s.packageShortcutsChanged(getPackageName(), getPackageUserId(), null, null);
return true; // true means changed.
}
@@ -1299,15 +1311,14 @@
*/
public void resolveResourceStrings() {
final ShortcutService s = mShortcutUser.mService;
- boolean changed = false;
+
+ List<ShortcutInfo> changedShortcuts = null;
Resources publisherRes = null;
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
if (si.hasStringResources()) {
- changed = true;
-
if (publisherRes == null) {
publisherRes = getPackageResources();
if (publisherRes == null) {
@@ -1317,10 +1328,15 @@
si.resolveResourceStrings(publisherRes);
si.setTimestamp(s.injectCurrentTimeMillis());
+
+ if (changedShortcuts == null) {
+ changedShortcuts = new ArrayList<>(1);
+ }
+ changedShortcuts.add(si);
}
}
- if (changed) {
- s.packageShortcutsChanged(getPackageName(), getPackageUserId());
+ if (!CollectionUtils.isEmpty(changedShortcuts)) {
+ s.packageShortcutsChanged(getPackageName(), getPackageUserId(), changedShortcuts, null);
}
}
diff --git a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
index 3e44de9..6fd997d 100644
--- a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
+++ b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
@@ -26,7 +26,6 @@
import android.content.pm.LauncherApps.PinItemRequest;
import android.content.pm.ShortcutInfo;
import android.os.Bundle;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.util.Pair;
@@ -36,6 +35,9 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Handles {@link android.content.pm.ShortcutManager#requestPinShortcut} related tasks.
*/
@@ -452,6 +454,8 @@
final String launcherPackage = request.launcherPackage;
final String shortcutId = original.getId();
+ List<ShortcutInfo> changedShortcuts = null;
+
synchronized (mLock) {
if (!(mService.isUserUnlockedL(appUserId)
&& mService.isUserUnlockedL(request.launcherUserId))) {
@@ -506,8 +510,13 @@
Slog.d(TAG, "Pinning " + shortcutId);
}
+
launcher.addPinnedShortcut(appPackageName, appUserId, shortcutId,
/*forPinRequest=*/ true);
+ if (changedShortcuts == null) {
+ changedShortcuts = new ArrayList<>(1);
+ }
+ changedShortcuts.add(original);
if (current == null) {
if (DEBUG) {
@@ -520,7 +529,7 @@
}
mService.verifyStates();
- mService.packageShortcutsChanged(appPackageName, appUserId);
+ mService.packageShortcutsChanged(appPackageName, appUserId, changedShortcuts, null);
return true;
}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 54f9f76..66f3574 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -98,6 +98,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.Preconditions;
@@ -277,6 +278,10 @@
private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1);
@GuardedBy("mLock")
+ private final ArrayList<LauncherApps.ShortcutChangeCallback> mShortcutChangeCallbacks =
+ new ArrayList<>(1);
+
+ @GuardedBy("mLock")
private long mRawLastResetTime;
/**
@@ -1639,8 +1644,12 @@
* - Sends a notification to LauncherApps
* - Write to file
*/
- void packageShortcutsChanged(@NonNull String packageName, @UserIdInt int userId) {
+ void packageShortcutsChanged(@NonNull String packageName, @UserIdInt int userId,
+ @Nullable List<ShortcutInfo> addedOrUpdatedShortcuts,
+ @Nullable List<ShortcutInfo> removedShortcuts) {
notifyListeners(packageName, userId);
+ notifyShortcutChangeCallbacks(packageName, userId, addedOrUpdatedShortcuts,
+ removedShortcuts);
scheduleSaveUser(userId);
}
@@ -1668,6 +1677,34 @@
});
}
+ private void notifyShortcutChangeCallbacks(@NonNull String packageName, @UserIdInt int userId,
+ @Nullable List<ShortcutInfo> addedOrUpdatedShortcuts,
+ @Nullable List<ShortcutInfo> removedShortcuts) {
+ final UserHandle user = UserHandle.of(userId);
+ injectPostToHandler(() -> {
+ try {
+ final ArrayList<LauncherApps.ShortcutChangeCallback> copy;
+ synchronized (mLock) {
+ if (!isUserUnlockedL(userId)) {
+ return;
+ }
+
+ copy = new ArrayList<>(mShortcutChangeCallbacks);
+ }
+ for (int i = copy.size() - 1; i >= 0; i--) {
+ if (!CollectionUtils.isEmpty(addedOrUpdatedShortcuts)) {
+ copy.get(i).onShortcutsAddedOrUpdated(packageName, addedOrUpdatedShortcuts,
+ user);
+ }
+ if (!CollectionUtils.isEmpty(removedShortcuts)) {
+ copy.get(i).onShortcutsRemoved(packageName, removedShortcuts, user);
+ }
+ }
+ } catch (Exception ignore) {
+ }
+ });
+ }
+
/**
* Clean up / validate an incoming shortcut.
* - Make sure all mandatory fields are set.
@@ -1762,6 +1799,8 @@
final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission(
injectBinderCallingPid(), injectBinderCallingUid());
+ List<ShortcutInfo> removedShortcuts = null;
+
synchronized (mLock) {
throwIfUserLockedL(userId);
@@ -1787,7 +1826,7 @@
}
// First, remove all un-pinned; dynamic shortcuts
- ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true);
+ removedShortcuts = ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true);
// Then, add/update all. We need to make sure to take over "pinned" flag.
for (int i = 0; i < size; i++) {
@@ -1798,7 +1837,7 @@
// Lastly, adjust the ranks.
ps.adjustRanks();
}
- packageShortcutsChanged(packageName, userId);
+ packageShortcutsChanged(packageName, userId, newShortcuts, removedShortcuts);
verifyStates();
@@ -1817,6 +1856,8 @@
final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission(
injectBinderCallingPid(), injectBinderCallingUid());
+ List<ShortcutInfo> changedShortcuts = null;
+
synchronized (mLock) {
throwIfUserLockedL(userId);
@@ -1879,12 +1920,17 @@
if (replacingIcon || source.hasStringResources()) {
fixUpShortcutResourceNamesAndValues(target);
}
+
+ if (changedShortcuts == null) {
+ changedShortcuts = new ArrayList<>(1);
+ }
+ changedShortcuts.add(target);
}
// Lastly, adjust the ranks.
ps.adjustRanks();
}
- packageShortcutsChanged(packageName, userId);
+ packageShortcutsChanged(packageName, userId, changedShortcuts, null);
verifyStates();
@@ -1903,6 +1949,8 @@
final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission(
injectBinderCallingPid(), injectBinderCallingUid());
+ List<ShortcutInfo> changedShortcuts = null;
+
synchronized (mLock) {
throwIfUserLockedL(userId);
@@ -1934,12 +1982,17 @@
// Add it.
ps.addOrReplaceDynamicShortcut(newShortcut);
+
+ if (changedShortcuts == null) {
+ changedShortcuts = new ArrayList<>(1);
+ }
+ changedShortcuts.add(newShortcut);
}
// Lastly, adjust the ranks.
ps.adjustRanks();
}
- packageShortcutsChanged(packageName, userId);
+ packageShortcutsChanged(packageName, userId, changedShortcuts, null);
verifyStates();
@@ -1985,7 +2038,7 @@
// Lastly, adjust the ranks.
ps.adjustRanks();
}
- packageShortcutsChanged(packageName, userId);
+ packageShortcutsChanged(packageName, userId, Collections.singletonList(shortcut), null);
verifyStates();
}
@@ -2052,7 +2105,8 @@
ps.updateInvisibleShortcutForPinRequestWith(shortcut);
- packageShortcutsChanged(shortcutPackage, userId);
+ packageShortcutsChanged(shortcutPackage, userId,
+ Collections.singletonList(shortcut), null);
}
}
@@ -2097,7 +2151,8 @@
// We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
ps.adjustRanks();
}
- packageShortcutsChanged(packageName, userId);
+ // TODO: Disabling dynamic shortcuts will removed them if not pinned. Cover all cases.
+ packageShortcutsChanged(packageName, userId, null, null);
verifyStates();
}
@@ -2107,6 +2162,8 @@
verifyCaller(packageName, userId);
Objects.requireNonNull(shortcutIds, "shortcutIds must be provided");
+ List<ShortcutInfo> changedShortcuts = null;
+
synchronized (mLock) {
throwIfUserLockedL(userId);
@@ -2121,9 +2178,18 @@
continue;
}
ps.enableWithId(id);
+
+ final ShortcutInfo si = ps.findShortcutById(id);
+ if (si != null) {
+ if (changedShortcuts == null) {
+ changedShortcuts = new ArrayList<>(1);
+ }
+ changedShortcuts.add(si);
+ }
}
}
- packageShortcutsChanged(packageName, userId);
+
+ packageShortcutsChanged(packageName, userId, changedShortcuts, null);
verifyStates();
}
@@ -2134,6 +2200,9 @@
verifyCaller(packageName, userId);
Objects.requireNonNull(shortcutIds, "shortcutIds must be provided");
+ List<ShortcutInfo> changedShortcuts = null;
+ List<ShortcutInfo> removedShortcuts = null;
+
synchronized (mLock) {
throwIfUserLockedL(userId);
@@ -2147,13 +2216,25 @@
if (!ps.isShortcutExistsAndVisibleToPublisher(id)) {
continue;
}
- ps.deleteDynamicWithId(id, /*ignoreInvisible=*/ true);
+ final ShortcutInfo si = ps.findShortcutById(id);
+ final boolean removed = ps.deleteDynamicWithId(id, /*ignoreInvisible=*/ true);
+ if (removed) {
+ if (removedShortcuts == null) {
+ removedShortcuts = new ArrayList<>(1);
+ }
+ removedShortcuts.add(si);
+ } else {
+ if (changedShortcuts == null) {
+ changedShortcuts = new ArrayList<>(1);
+ }
+ changedShortcuts.add(si);
+ }
}
// We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
ps.adjustRanks();
}
- packageShortcutsChanged(packageName, userId);
+ packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts);
verifyStates();
}
@@ -2162,13 +2243,19 @@
public void removeAllDynamicShortcuts(String packageName, @UserIdInt int userId) {
verifyCaller(packageName, userId);
+ List<ShortcutInfo> changedShortcuts = null;
+ List<ShortcutInfo> removedShortcuts = null;
+
synchronized (mLock) {
throwIfUserLockedL(userId);
final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
- ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true);
+
+ removedShortcuts = ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true);
}
- packageShortcutsChanged(packageName, userId);
+
+ // TODO: Pinned and cached shortcuts are not removed, add those to changedShortcuts list
+ packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts);
verifyStates();
}
@@ -2179,6 +2266,9 @@
verifyCaller(packageName, userId);
Objects.requireNonNull(shortcutIds, "shortcutIds must be provided");
+ List<ShortcutInfo> changedShortcuts = null;
+ List<ShortcutInfo> removedShortcuts = null;
+
synchronized (mLock) {
throwIfUserLockedL(userId);
@@ -2189,13 +2279,29 @@
for (int i = shortcutIds.size() - 1; i >= 0; i--) {
final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i));
- ps.deleteLongLivedWithId(id, /*ignoreInvisible=*/ true);
+
+ final ShortcutInfo si = ps.findShortcutById(id);
+ final boolean removed = ps.deleteLongLivedWithId(id, /*ignoreInvisible=*/ true);
+
+ if (si != null) {
+ if (removed) {
+ if (removedShortcuts == null) {
+ removedShortcuts = new ArrayList<>(1);
+ }
+ removedShortcuts.add(si);
+ } else {
+ if (changedShortcuts == null) {
+ changedShortcuts = new ArrayList<>(1);
+ }
+ changedShortcuts.add(si);
+ }
+ }
}
// We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
ps.adjustRanks();
}
- packageShortcutsChanged(packageName, userId);
+ packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts);
verifyStates();
}
@@ -2787,6 +2893,8 @@
Preconditions.checkStringNotEmpty(packageName, "packageName");
Objects.requireNonNull(shortcutIds, "shortcutIds");
+ List<ShortcutInfo> changedShortcuts = null;
+
synchronized (mLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -2796,8 +2904,23 @@
launcher.attemptToRestoreIfNeededAndSave();
launcher.pinShortcuts(userId, packageName, shortcutIds, /*forPinRequest=*/ false);
+
+ final ShortcutPackage sp = getUserShortcutsLocked(userId)
+ .getPackageShortcutsIfExists(packageName);
+ if (sp != null) {
+ for (int i = 0; i < shortcutIds.size(); i++) {
+ final ShortcutInfo si = sp.findShortcutById(shortcutIds.get(i));
+ if (si != null) {
+ if (changedShortcuts == null) {
+ changedShortcuts = new ArrayList<>(1);
+ }
+ changedShortcuts.add(si);
+ }
+ }
+ }
}
- packageShortcutsChanged(packageName, userId);
+ // TODO: Include previously pinned shortcuts since they are not pinned anymore.
+ packageShortcutsChanged(packageName, userId, changedShortcuts, null);
verifyStates();
}
@@ -2832,6 +2955,9 @@
Preconditions.checkStringNotEmpty(packageName, "packageName");
Objects.requireNonNull(shortcutIds, "shortcutIds");
+ List<ShortcutInfo> changedShortcuts = null;
+ List<ShortcutInfo> removedShortcuts = null;
+
synchronized (mLock) {
throwIfUserLockedL(userId);
throwIfUserLockedL(launcherUserId);
@@ -2853,20 +2979,36 @@
if (doCache) {
if (si.isDynamic() && si.isLongLived()) {
si.addFlags(ShortcutInfo.FLAG_CACHED);
+ if (changedShortcuts == null) {
+ changedShortcuts = new ArrayList<>(1);
+ }
+ changedShortcuts.add(si);
} else {
Log.w(TAG, "Only dynamic long lived shortcuts can get cached. Ignoring"
+ "shortcut " + si.getId());
}
} else {
+ boolean removed = false;
if (si.isDynamic()) {
si.clearFlags(ShortcutInfo.FLAG_CACHED);
} else {
- sp.deleteLongLivedWithId(id, /*ignoreInvisible=*/ true);
+ removed = sp.deleteLongLivedWithId(id, /*ignoreInvisible=*/ true);
+ }
+ if (removed) {
+ if (removedShortcuts == null) {
+ removedShortcuts = new ArrayList<>(1);
+ }
+ removedShortcuts.add(si);
+ } else {
+ if (changedShortcuts == null) {
+ changedShortcuts = new ArrayList<>(1);
+ }
+ changedShortcuts.add(si);
}
}
}
}
- packageShortcutsChanged(packageName, userId);
+ packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts);
verifyStates();
}
@@ -2912,6 +3054,14 @@
}
@Override
+ public void addShortcutChangeCallback(
+ @NonNull LauncherApps.ShortcutChangeCallback callback) {
+ synchronized (mLock) {
+ mShortcutChangeCallbacks.add(Objects.requireNonNull(callback));
+ }
+ }
+
+ @Override
public int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage,
@NonNull String packageName, @NonNull String shortcutId, int userId) {
Objects.requireNonNull(callingPackage, "callingPackage");
diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
index 70a9c09..f445aa8 100644
--- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
+++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
@@ -190,6 +190,7 @@
}
static inline int32_t skipIdSigHeaders(borrowed_fd fd) {
+ readBEInt32(fd); // version
readBytes(fd); // verityRootHash
readBytes(fd); // v3Digest
readBytes(fd); // pkcs7SignatureBlock
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b192dbd..267c444 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -444,10 +444,13 @@
});
/**
- * System property whose value is either "true" or "false", indicating whether
- * device owner is present.
+ * System property whose value indicates whether the device is fully owned by an organization:
+ * it can be either a device owner device, or a device with an organization-owned managed
+ * profile.
+ *
+ * <p>The state is stored as a Boolean string.
*/
- private static final String PROPERTY_DEVICE_OWNER_PRESENT = "ro.organization_owned";
+ private static final String PROPERTY_ORGANIZATION_OWNED = "ro.organization_owned";
private static final int STATUS_BAR_DISABLE_MASK =
StatusBarManager.DISABLE_EXPAND |
@@ -2550,7 +2553,7 @@
void loadOwners() {
synchronized (getLockObject()) {
mOwners.load();
- setDeviceOwnerSystemPropertyLocked();
+ setDeviceOwnershipSystemPropertyLocked();
findOwnerComponentIfNecessaryLocked();
migrateUserRestrictionsIfNecessaryLocked();
@@ -2752,34 +2755,36 @@
}
}
- private void setDeviceOwnerSystemPropertyLocked() {
- final boolean deviceProvisioned =
- mInjector.settingsGlobalGetInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0;
- final boolean hasDeviceOwner = mOwners.hasDeviceOwner();
- // If the device is not provisioned and there is currently no device owner, do not set the
- // read-only system property yet, since Device owner may still be provisioned.
- if (!hasDeviceOwner && !deviceProvisioned) {
- return;
- }
- // Still at the first stage of CryptKeeper double bounce, mOwners.hasDeviceOwner is
- // always false at this point.
+ private void setDeviceOwnershipSystemPropertyLocked() {
+ // Still at the first stage of CryptKeeper double bounce, nothing can be learnt about
+ // the real system at this point.
if (StorageManager.inCryptKeeperBounce()) {
return;
}
-
- if (!mInjector.systemPropertiesGet(PROPERTY_DEVICE_OWNER_PRESENT, "").isEmpty()) {
- Slog.w(LOG_TAG, "Trying to set ro.organization_owned, but it has already been set?");
- } else {
- final String value = Boolean.toString(hasDeviceOwner);
- mInjector.systemPropertiesSet(PROPERTY_DEVICE_OWNER_PRESENT, value);
+ final boolean deviceProvisioned =
+ mInjector.settingsGlobalGetInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ final boolean hasDeviceOwner = mOwners.hasDeviceOwner();
+ final boolean hasOrgOwnedProfile = isOrganizationOwnedDeviceWithManagedProfile();
+ // If the device is not provisioned and there is currently no management, do not set the
+ // read-only system property yet, since device owner / org-owned profile may still be
+ // provisioned.
+ if (!hasDeviceOwner && !hasOrgOwnedProfile && !deviceProvisioned) {
+ return;
+ }
+ final String value = Boolean.toString(hasDeviceOwner || hasOrgOwnedProfile);
+ final String currentVal = mInjector.systemPropertiesGet(PROPERTY_ORGANIZATION_OWNED, null);
+ if (TextUtils.isEmpty(currentVal)) {
Slog.i(LOG_TAG, "Set ro.organization_owned property to " + value);
+ mInjector.systemPropertiesSet(PROPERTY_ORGANIZATION_OWNED, value);
+ } else if (!value.equals(currentVal)) {
+ Slog.w(LOG_TAG, "Cannot change existing ro.organization_owned to " + value);
}
}
private void maybeStartSecurityLogMonitorOnActivityManagerReady() {
synchronized (getLockObject()) {
if (mInjector.securityLogIsLoggingEnabled()) {
- mSecurityLogMonitor.start();
+ mSecurityLogMonitor.start(getSecurityLoggingEnabledUser());
mInjector.runCryptoSelfTest();
maybePauseDeviceWideLoggingLocked();
}
@@ -8391,7 +8396,7 @@
mOwners.setDeviceOwner(admin, ownerName, userId);
mOwners.writeDeviceOwner();
updateDeviceOwnerLocked();
- setDeviceOwnerSystemPropertyLocked();
+ setDeviceOwnershipSystemPropertyLocked();
mInjector.binderWithCleanCallingIdentity(() -> {
// Restrict adding a managed profile when a device owner is set on the device.
@@ -9071,21 +9076,21 @@
return getApplicationLabel(profileOwner.getPackageName(), userHandle);
}
+ private @UserIdInt int getOrganizationOwnedProfileUserId() {
+ for (UserInfo ui : mUserManagerInternal.getUserInfos()) {
+ if (ui.isManagedProfile() && isProfileOwnerOfOrganizationOwnedDevice(ui.id)) {
+ return ui.id;
+ }
+ }
+ return UserHandle.USER_NULL;
+ }
+
@Override
public boolean isOrganizationOwnedDeviceWithManagedProfile() {
if (!mHasFeature) {
return false;
}
-
- return mInjector.binderWithCleanCallingIdentity(() -> {
- for (UserInfo ui : mUserManager.getUsers()) {
- if (ui.isManagedProfile() && isProfileOwnerOfOrganizationOwnedDevice(ui.id)) {
- return true;
- }
- }
-
- return false;
- });
+ return getOrganizationOwnedProfileUserId() != UserHandle.USER_NULL;
}
@Override
@@ -12046,7 +12051,7 @@
synchronized (getLockObject()) {
// Set PROPERTY_DEVICE_OWNER_PRESENT, for the SUW case where setting the property
// is delayed until device is marked as provisioned.
- setDeviceOwnerSystemPropertyLocked();
+ setDeviceOwnershipSystemPropertyLocked();
}
} else if (mDefaultImeChanged.equals(uri)) {
synchronized (getLockObject()) {
@@ -13671,6 +13676,22 @@
});
}
+ private boolean canStartSecurityLogging() {
+ synchronized (getLockObject()) {
+ return isOrganizationOwnedDeviceWithManagedProfile()
+ || areAllUsersAffiliatedWithDeviceLocked();
+ }
+ }
+
+ private @UserIdInt int getSecurityLoggingEnabledUser() {
+ synchronized (getLockObject()) {
+ if (mOwners.hasDeviceOwner()) {
+ return UserHandle.USER_ALL;
+ }
+ }
+ return getOrganizationOwnedProfileUserId();
+ }
+
@Override
public void setSecurityLoggingEnabled(ComponentName admin, boolean enabled) {
if (!mHasFeature) {
@@ -13679,13 +13700,14 @@
Objects.requireNonNull(admin);
synchronized (getLockObject()) {
- getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ getActiveAdminForCallerLocked(admin,
+ DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
if (enabled == mInjector.securityLogGetLoggingEnabledProperty()) {
return;
}
mInjector.securityLogSetLoggingEnabledProperty(enabled);
if (enabled) {
- mSecurityLogMonitor.start();
+ mSecurityLogMonitor.start(getSecurityLoggingEnabledUser());
maybePauseDeviceWideLoggingLocked();
} else {
mSecurityLogMonitor.stop();
@@ -13707,7 +13729,8 @@
synchronized (getLockObject()) {
if (!isCallerWithSystemUid()) {
Objects.requireNonNull(admin);
- getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ getActiveAdminForCallerLocked(admin,
+ DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
}
return mInjector.securityLogGetLoggingEnabledProperty();
}
@@ -13731,7 +13754,10 @@
}
Objects.requireNonNull(admin);
- ensureDeviceOwnerAndAllUsersAffiliated(admin);
+ enforceDeviceOwnerOrProfileOwnerOnOrganizationOwnedDevice(admin);
+ if (!isOrganizationOwnedDeviceWithManagedProfile()) {
+ ensureAllUsersAffiliated();
+ }
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.RETRIEVE_PRE_REBOOT_SECURITY_LOGS)
@@ -13747,6 +13773,10 @@
ArrayList<SecurityEvent> output = new ArrayList<SecurityEvent>();
try {
SecurityLog.readPreviousEvents(output);
+ int enabledUser = getSecurityLoggingEnabledUser();
+ if (enabledUser != UserHandle.USER_ALL) {
+ SecurityLog.redactEvents(output, enabledUser);
+ }
return new ParceledListSlice<SecurityEvent>(output);
} catch (IOException e) {
Slog.w(LOG_TAG, "Fail to read previous events" , e);
@@ -13761,7 +13791,10 @@
}
Objects.requireNonNull(admin);
- ensureDeviceOwnerAndAllUsersAffiliated(admin);
+ enforceDeviceOwnerOrProfileOwnerOnOrganizationOwnedDevice(admin);
+ if (!isOrganizationOwnedDeviceWithManagedProfile()) {
+ ensureAllUsersAffiliated();
+ }
if (!mInjector.securityLogGetLoggingEnabledProperty()) {
return null;
@@ -14287,26 +14320,34 @@
@GuardedBy("getLockObject()")
private void maybePauseDeviceWideLoggingLocked() {
if (!areAllUsersAffiliatedWithDeviceLocked()) {
- Slog.i(LOG_TAG, "There are unaffiliated users, security and network logging will be "
+ Slog.i(LOG_TAG, "There are unaffiliated users, network logging will be "
+ "paused if enabled.");
- mSecurityLogMonitor.pause();
if (mNetworkLogger != null) {
mNetworkLogger.pause();
}
+ if (!isOrganizationOwnedDeviceWithManagedProfile()) {
+ Slog.i(LOG_TAG, "Not org-owned managed profile device, security logging will be "
+ + "paused if enabled.");
+ mSecurityLogMonitor.pause();
+ }
}
}
/** Resumes security and network logging (if they are enabled) if all users are affiliated */
@GuardedBy("getLockObject()")
private void maybeResumeDeviceWideLoggingLocked() {
- if (areAllUsersAffiliatedWithDeviceLocked()) {
- mInjector.binderWithCleanCallingIdentity(() -> {
+ boolean allUsersAffiliated = areAllUsersAffiliatedWithDeviceLocked();
+ boolean orgOwnedProfileDevice = isOrganizationOwnedDeviceWithManagedProfile();
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ if (allUsersAffiliated || orgOwnedProfileDevice) {
mSecurityLogMonitor.resume();
+ }
+ if (allUsersAffiliated) {
if (mNetworkLogger != null) {
mNetworkLogger.resume();
}
- });
- }
+ }
+ });
}
/** Deletes any security and network logs that might have been collected so far */
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
index 1ab3b98..3c445ca 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
@@ -51,15 +51,17 @@
private final Lock mLock = new ReentrantLock();
+ private int mEnabledUser;
+
SecurityLogMonitor(DevicePolicyManagerService service) {
this(service, 0 /* id */);
}
@VisibleForTesting
SecurityLogMonitor(DevicePolicyManagerService service, long id) {
- this.mService = service;
- this.mId = id;
- this.mLastForceNanos = System.nanoTime();
+ mService = service;
+ mId = id;
+ mLastForceNanos = System.nanoTime();
}
private static final boolean DEBUG = false; // STOPSHIP if true.
@@ -136,8 +138,15 @@
@GuardedBy("mForceSemaphore")
private long mLastForceNanos = 0;
- void start() {
- Slog.i(TAG, "Starting security logging.");
+ /**
+ * Start security logging.
+ *
+ * @param enabledUser which user logging is enabled on, or USER_ALL to enable logging for all
+ * users on the device.
+ */
+ void start(int enabledUser) {
+ Slog.i(TAG, "Starting security logging for user " + enabledUser);
+ mEnabledUser = enabledUser;
SecurityLog.writeEvent(SecurityLog.TAG_LOGGING_STARTED);
mLock.lock();
try {
@@ -286,7 +295,7 @@
break;
}
}
-
+ SecurityLog.redactEvents(newLogs, mEnabledUser);
if (DEBUG) Slog.d(TAG, "Got " + newLogs.size() + " new events.");
}
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 859cdf2..41bc361 100644
--- a/services/people/java/com/android/server/people/data/ConversationInfo.java
+++ b/services/people/java/com/android/server/people/data/ConversationInfo.java
@@ -274,6 +274,10 @@
}
protoOutputStream.write(ConversationInfoProto.SHORTCUT_FLAGS, mShortcutFlags);
protoOutputStream.write(ConversationInfoProto.CONVERSATION_FLAGS, mConversationFlags);
+ if (mContactPhoneNumber != null) {
+ protoOutputStream.write(ConversationInfoProto.CONTACT_PHONE_NUMBER,
+ mContactPhoneNumber);
+ }
}
/** Reads from {@link ProtoInputStream} and constructs a {@link ConversationInfo}. */
@@ -315,6 +319,10 @@
builder.setConversationFlags(protoInputStream.readInt(
ConversationInfoProto.CONVERSATION_FLAGS));
break;
+ case (int) ConversationInfoProto.CONTACT_PHONE_NUMBER:
+ builder.setContactPhoneNumber(protoInputStream.readString(
+ ConversationInfoProto.CONTACT_PHONE_NUMBER));
+ break;
default:
Slog.w(TAG, "Could not read undefined field: "
+ protoInputStream.getFieldNumber());
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 62e9da8..89c4972 100644
--- a/services/people/java/com/android/server/people/data/ConversationStore.java
+++ b/services/people/java/com/android/server/people/data/ConversationStore.java
@@ -22,7 +22,6 @@
import android.annotation.WorkerThread;
import android.content.LocusId;
import android.net.Uri;
-import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.proto.ProtoInputStream;
@@ -71,16 +70,13 @@
private final ScheduledExecutorService mScheduledExecutorService;
private final File mPackageDir;
- private final ContactsQueryHelper mHelper;
private ConversationInfosProtoDiskReadWriter mConversationInfosProtoDiskReadWriter;
ConversationStore(@NonNull File packageDir,
- @NonNull ScheduledExecutorService scheduledExecutorService,
- @NonNull ContactsQueryHelper helper) {
+ @NonNull ScheduledExecutorService scheduledExecutorService) {
mScheduledExecutorService = scheduledExecutorService;
mPackageDir = packageDir;
- mHelper = helper;
}
/**
@@ -102,7 +98,6 @@
return;
}
for (ConversationInfo conversationInfo : conversationsOnDisk) {
- conversationInfo = restoreConversationPhoneNumber(conversationInfo);
updateConversationsInMemory(conversationInfo);
}
}
@@ -250,25 +245,6 @@
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}s on disk. */
private static class ConversationInfosProtoDiskReadWriter extends
AbstractProtoDiskReadWriter<List<ConversationInfo>> {
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 27c1692..3a34c6a 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -31,7 +31,9 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager.ShareShortcutInfo;
@@ -53,6 +55,7 @@
import android.telecom.TelecomManager;
import android.text.format.DateUtils;
import android.util.ArraySet;
+import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
@@ -61,11 +64,13 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.telephony.SmsApplication;
import com.android.server.LocalServices;
+import com.android.server.notification.NotificationManagerInternal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
@@ -80,8 +85,8 @@
*/
public class DataManager {
- private static final int MY_UID = Process.myUid();
- private static final int MY_PID = Process.myPid();
+ private static final String TAG = "DataManager";
+
private static final long QUERY_EVENTS_MAX_AGE_MS = DateUtils.DAY_IN_MILLIS;
private static final long USAGE_STATS_QUERY_INTERVAL_SEC = 120L;
@@ -102,6 +107,7 @@
private ShortcutServiceInternal mShortcutServiceInternal;
private PackageManagerInternal mPackageManagerInternal;
+ private NotificationManagerInternal mNotificationManagerInternal;
private UserManager mUserManager;
public DataManager(Context context) {
@@ -120,9 +126,10 @@
public void initialize() {
mShortcutServiceInternal = LocalServices.getService(ShortcutServiceInternal.class);
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+ mNotificationManagerInternal = LocalServices.getService(NotificationManagerInternal.class);
mUserManager = mContext.getSystemService(UserManager.class);
- mShortcutServiceInternal.addListener(new ShortcutServiceListener());
+ mShortcutServiceInternal.addShortcutChangeCallback(new ShortcutServiceCallback());
IntentFilter shutdownIntentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
BroadcastReceiver shutdownBroadcastReceiver = new ShutdownBroadcastReceiver();
@@ -133,8 +140,7 @@
public void onUserUnlocked(int userId) {
UserData userData = mUserDataArray.get(userId);
if (userData == null) {
- userData = new UserData(userId, mDiskReadWriterExecutor,
- mInjector.createContactsQueryHelper(mContext));
+ userData = new UserData(userId, mDiskReadWriterExecutor);
mUserDataArray.put(userId, userData);
}
userData.setUserUnlocked();
@@ -363,7 +369,7 @@
return mShortcutServiceInternal.getShortcuts(
UserHandle.USER_SYSTEM, mContext.getPackageName(),
/*changedSince=*/ 0, packageName, shortcutIds, /*locusIds=*/ null,
- /*componentName=*/ null, queryFlags, userId, MY_PID, MY_UID);
+ /*componentName=*/ null, queryFlags, userId, Process.myPid(), Process.myUid());
}
private void forAllUnlockedUsers(Consumer<UserData> consumer) {
@@ -622,14 +628,12 @@
}
/** Listener for the shortcut data changes. */
- private class ShortcutServiceListener implements
- ShortcutServiceInternal.ShortcutChangeListener {
+ private class ShortcutServiceCallback implements LauncherApps.ShortcutChangeCallback {
@Override
- public void onShortcutChanged(@NonNull String packageName, int userId) {
- BackgroundThread.getExecutor().execute(() -> {
- List<ShortcutInfo> shortcuts = getShortcuts(packageName, userId,
- /*shortcutIds=*/ null);
+ public void onShortcutsAddedOrUpdated(@NonNull String packageName,
+ @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
+ mInjector.getBackgroundExecutor().execute(() -> {
for (ShortcutInfo shortcut : shortcuts) {
if (isPersonShortcut(shortcut)) {
addOrUpdateConversationInfo(shortcut);
@@ -637,6 +641,30 @@
}
});
}
+
+ @Override
+ public void onShortcutsRemoved(@NonNull String packageName,
+ @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
+ mInjector.getBackgroundExecutor().execute(() -> {
+ int uid = Process.INVALID_UID;
+ try {
+ uid = mContext.getPackageManager().getPackageUidAsUser(
+ packageName, user.getIdentifier());
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Package not found: " + packageName, e);
+ }
+ PackageData packageData = getPackage(packageName, user.getIdentifier());
+ for (ShortcutInfo shortcutInfo : shortcuts) {
+ if (packageData != null) {
+ packageData.deleteDataForConversation(shortcutInfo.getId());
+ }
+ if (uid != Process.INVALID_UID) {
+ mNotificationManagerInternal.onConversationRemoved(
+ shortcutInfo.getPackage(), uid, shortcutInfo.getId());
+ }
+ }
+ });
+ }
}
/** Listener for the notifications and their settings changes. */
@@ -789,6 +817,10 @@
return Executors.newSingleThreadScheduledExecutor();
}
+ Executor getBackgroundExecutor() {
+ return BackgroundThread.getExecutor();
+ }
+
ContactsQueryHelper createContactsQueryHelper(Context context) {
return new ContactsQueryHelper(context);
}
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 d47e2cc..35d245f 100644
--- a/services/people/java/com/android/server/people/data/PackageData.java
+++ b/services/people/java/com/android/server/people/data/PackageData.java
@@ -59,16 +59,14 @@
@NonNull Predicate<String> isDefaultDialerPredicate,
@NonNull Predicate<String> isDefaultSmsAppPredicate,
@NonNull ScheduledExecutorService scheduledExecutorService,
- @NonNull File perUserPeopleDataDir,
- @NonNull ContactsQueryHelper helper) {
+ @NonNull File perUserPeopleDataDir) {
mPackageName = packageName;
mUserId = userId;
mPackageDataDir = new File(perUserPeopleDataDir, mPackageName);
mPackageDataDir.mkdirs();
- mConversationStore = new ConversationStore(mPackageDataDir, scheduledExecutorService,
- helper);
+ mConversationStore = new ConversationStore(mPackageDataDir, scheduledExecutorService);
mEventStore = new EventStore(mPackageDataDir, scheduledExecutorService);
mIsDefaultDialerPredicate = isDefaultDialerPredicate;
mIsDefaultSmsAppPredicate = isDefaultSmsAppPredicate;
@@ -83,8 +81,7 @@
@NonNull Predicate<String> isDefaultDialerPredicate,
@NonNull Predicate<String> isDefaultSmsAppPredicate,
@NonNull ScheduledExecutorService scheduledExecutorService,
- @NonNull File perUserPeopleDataDir,
- @NonNull ContactsQueryHelper helper) {
+ @NonNull File perUserPeopleDataDir) {
Map<String, PackageData> results = new ArrayMap<>();
File[] packageDirs = perUserPeopleDataDir.listFiles(File::isDirectory);
if (packageDirs == null) {
@@ -93,7 +90,7 @@
for (File packageDir : packageDirs) {
PackageData packageData = new PackageData(packageDir.getName(), userId,
isDefaultDialerPredicate, isDefaultSmsAppPredicate, scheduledExecutorService,
- perUserPeopleDataDir, helper);
+ perUserPeopleDataDir);
packageData.loadFromDisk();
results.put(packageDir.getName(), packageData);
}
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 d3cecce..0f8b91b 100644
--- a/services/people/java/com/android/server/people/data/UserData.java
+++ b/services/people/java/com/android/server/people/data/UserData.java
@@ -37,8 +37,6 @@
private final ScheduledExecutorService mScheduledExecutorService;
- private final ContactsQueryHelper mHelper;
-
private boolean mIsUnlocked;
private Map<String, PackageData> mPackageDataMap = new ArrayMap<>();
@@ -49,12 +47,10 @@
@Nullable
private String mDefaultSmsApp;
- UserData(@UserIdInt int userId, @NonNull ScheduledExecutorService scheduledExecutorService,
- ContactsQueryHelper helper) {
+ UserData(@UserIdInt int userId, @NonNull ScheduledExecutorService scheduledExecutorService) {
mUserId = userId;
mPerUserPeopleDataDir = new File(Environment.getDataSystemCeDirectory(mUserId), "people");
mScheduledExecutorService = scheduledExecutorService;
- mHelper = helper;
}
@UserIdInt int getUserId() {
@@ -74,7 +70,7 @@
// data from disk.
mPerUserPeopleDataDir.mkdirs();
mPackageDataMap.putAll(PackageData.packagesDataFromDisk(mUserId, this::isDefaultDialer,
- this::isDefaultSmsApp, mScheduledExecutorService, mPerUserPeopleDataDir, mHelper));
+ this::isDefaultSmsApp, mScheduledExecutorService, mPerUserPeopleDataDir));
}
void setUserStopped() {
@@ -131,7 +127,7 @@
private PackageData createPackageData(String packageName) {
return new PackageData(packageName, mUserId, this::isDefaultDialer, this::isDefaultSmsApp,
- mScheduledExecutorService, mPerUserPeopleDataDir, mHelper);
+ mScheduledExecutorService, mPerUserPeopleDataDir);
}
private boolean isDefaultDialer(String packageName) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 1c8b00f..30bb38a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -111,6 +111,29 @@
any());
}
+ @Test
+ public void testRegisterAuthenticator_callsInitConfiguredStrength() throws Exception {
+
+ final String[] config = {
+ "0:2:15", // ID0:Fingerprint:Strong
+ "1:4:255", // ID1:Iris:Weak
+ "2:8:4095", // ID2:Face:Convenience
+ };
+
+ when(mInjector.getConfiguration(any())).thenReturn(config);
+
+ mAuthService = new AuthService(mContext, mInjector);
+ mAuthService.onStart();
+
+ final int fingerprintStrength = 15;
+ final int irisStrength = 255;
+ final int faceStrength = 4095;
+
+ verify(mFingerprintService).initConfiguredStrength(eq(fingerprintStrength));
+ verify(mIrisService).initConfiguredStrength(eq(irisStrength));
+ verify(mFaceService).initConfiguredStrength(eq(faceStrength));
+ }
+
// TODO(b/141025588): Check that an exception is thrown when the userId != callingUserId
@Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index dbf2f14..ac818ea 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -3952,13 +3952,8 @@
}
public void testIsOrganizationOwnedDevice() throws Exception {
- setupProfileOwner();
// Set up the user manager to return correct user info
- UserInfo managedProfileUserInfo = new UserInfo(DpmMockContext.CALLER_USER_HANDLE,
- "managed profile",
- UserInfo.FLAG_MANAGED_PROFILE);
- when(getServices().userManager.getUsers())
- .thenReturn(Arrays.asList(managedProfileUserInfo));
+ addManagedProfile(admin1, DpmMockContext.CALLER_UID, admin1);
// Any caller should be able to call this method.
assertFalse(dpm.isOrganizationOwnedDeviceWithManagedProfile());
@@ -5909,8 +5904,6 @@
}
private void configureProfileOwnerOfOrgOwnedDevice(ComponentName who, int userId) {
- when(getServices().userManager.getProfileParent(eq(UserHandle.of(userId))))
- .thenReturn(UserHandle.SYSTEM);
final long ident = mServiceContext.binder.clearCallingIdentity();
mServiceContext.binder.callingUid = UserHandle.getUid(userId, DpmMockContext.SYSTEM_UID);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 37d4081..01f1a3e 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -265,6 +265,9 @@
.toArray();
}
);
+ when(userManagerInternal.getUserInfos()).thenReturn(
+ mUserInfos.toArray(new UserInfo[mUserInfos.size()]));
+
when(accountManager.getAccountsAsUser(anyInt())).thenReturn(new Account[0]);
// Create a data directory.
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/SecurityEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/SecurityEventTest.java
index 0f05212..8dcf21f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/SecurityEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/SecurityEventTest.java
@@ -1,31 +1,43 @@
package com.android.server.devicepolicy;
import static android.app.admin.SecurityLog.TAG_ADB_SHELL_CMD;
+import static android.app.admin.SecurityLog.TAG_APP_PROCESS_START;
+import static android.app.admin.SecurityLog.TAG_CERT_AUTHORITY_INSTALLED;
+import static android.app.admin.SecurityLog.TAG_CERT_AUTHORITY_REMOVED;
+import static android.app.admin.SecurityLog.TAG_KEY_DESTRUCTION;
+import static android.app.admin.SecurityLog.TAG_KEY_GENERATED;
+import static android.app.admin.SecurityLog.TAG_KEY_IMPORT;
+import static android.app.admin.SecurityLog.TAG_KEY_INTEGRITY_VIOLATION;
+import static android.app.admin.SecurityLog.TAG_MEDIA_MOUNT;
+import static android.app.admin.SecurityLog.TAG_MEDIA_UNMOUNT;
import android.app.admin.SecurityLog.SecurityEvent;
import android.os.Parcel;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.EventLog;
+import android.util.EventLog.Event;
-import java.io.IOException;
+import junit.framework.AssertionFailedError;
+
import java.util.ArrayList;
import java.util.List;
-@SmallTest
public class SecurityEventTest extends DpmTestBase {
- private static long ID = 549;
- private static String DATA = "adb shell some_command";
- public void testSecurityEventId() {
- SecurityEvent event = buildSecurityEvents(1 /* generate a single event */, ID).get(0);
- assertEquals(ID, event.getId());
+ public void testSecurityEventId() throws Exception {
+ SecurityEvent event = createEvent(() -> {
+ EventLog.writeEvent(TAG_ADB_SHELL_CMD, 0);
+ }, TAG_ADB_SHELL_CMD);
event.setId(20);
assertEquals(20, event.getId());
}
- public void testSecurityEventParceling() {
+ public void testSecurityEventParceling() throws Exception {
// GIVEN an event.
- SecurityEvent event = buildSecurityEvents(1 /* generate a single event */, ID).get(0);
+ SecurityEvent event = createEvent(() -> {
+ EventLog.writeEvent(TAG_ADB_SHELL_CMD, "test");
+ }, TAG_ADB_SHELL_CMD);
// WHEN parceling the event.
Parcel p = Parcel.obtain();
p.writeParcelable(event, 0);
@@ -39,23 +51,104 @@
assertEquals(event.getId(), unparceledEvent.getId());
}
- private List<SecurityEvent> buildSecurityEvents(int numEvents, long id) {
- // Write an event to the EventLog.
- for (int i = 0; i < numEvents; i++) {
- EventLog.writeEvent(TAG_ADB_SHELL_CMD, DATA + "_" + i);
+ public void testSecurityEventRedaction() throws Exception {
+ SecurityEvent event;
+
+ // TAG_ADB_SHELL_CMD will has the command redacted
+ event = createEvent(() -> {
+ EventLog.writeEvent(TAG_ADB_SHELL_CMD, "command");
+ }, TAG_ADB_SHELL_CMD);
+ assertFalse(TextUtils.isEmpty((String) event.getData()));
+
+ // TAG_MEDIA_MOUNT will have the volume label redacted (second data)
+ event = createEvent(() -> {
+ EventLog.writeEvent(TAG_MEDIA_MOUNT, new Object[] {"path", "label"});
+ }, TAG_MEDIA_MOUNT);
+ assertFalse(TextUtils.isEmpty(event.getStringData(1)));
+ assertTrue(TextUtils.isEmpty(event.redact(0).getStringData(1)));
+
+ // TAG_MEDIA_UNMOUNT will have the volume label redacted (second data)
+ event = createEvent(() -> {
+ EventLog.writeEvent(TAG_MEDIA_UNMOUNT, new Object[] {"path", "label"});
+ }, TAG_MEDIA_UNMOUNT);
+ assertFalse(TextUtils.isEmpty(event.getStringData(1)));
+ assertTrue(TextUtils.isEmpty(event.redact(0).getStringData(1)));
+
+ // TAG_APP_PROCESS_START will be fully redacted if user does not match
+ event = createEvent(() -> {
+ EventLog.writeEvent(TAG_APP_PROCESS_START, new Object[] {"process", 12345L,
+ UserHandle.getUid(10, 123), 456, "seinfo", "hash"});
+ }, TAG_APP_PROCESS_START);
+ assertNotNull(event.redact(10));
+ assertNull(event.redact(11));
+
+ // TAG_CERT_AUTHORITY_INSTALLED will be fully redacted if user does not match
+ event = createEvent(() -> {
+ EventLog.writeEvent(TAG_CERT_AUTHORITY_INSTALLED, new Object[] {1, "subject", 10});
+ }, TAG_CERT_AUTHORITY_INSTALLED);
+ assertNotNull(event.redact(10));
+ assertNull(event.redact(11));
+
+ // TAG_CERT_AUTHORITY_REMOVED will be fully redacted if user does not match
+ event = createEvent(() -> {
+ EventLog.writeEvent(TAG_CERT_AUTHORITY_REMOVED, new Object[] {1, "subject", 20});
+ }, TAG_CERT_AUTHORITY_REMOVED);
+ assertNotNull(event.redact(20));
+ assertNull(event.redact(0));
+
+ // TAG_KEY_GENERATED will be fully redacted if user does not match
+ event = createEvent(() -> {
+ EventLog.writeEvent(TAG_KEY_GENERATED,
+ new Object[] {1, "alias", UserHandle.getUid(0, 123)});
+ }, TAG_KEY_GENERATED);
+ assertNotNull(event.redact(0));
+ assertNull(event.redact(10));
+
+ // TAG_KEY_IMPORT will be fully redacted if user does not match
+ event = createEvent(() -> {
+ EventLog.writeEvent(TAG_KEY_IMPORT,
+ new Object[] {1, "alias", UserHandle.getUid(1, 123)});
+ }, TAG_KEY_IMPORT);
+ assertNotNull(event.redact(1));
+ assertNull(event.redact(10));
+
+ // TAG_KEY_DESTRUCTION will be fully redacted if user does not match
+ event = createEvent(() -> {
+ EventLog.writeEvent(TAG_KEY_DESTRUCTION,
+ new Object[] {1, "alias", UserHandle.getUid(2, 123)});
+ }, TAG_KEY_DESTRUCTION);
+ assertNotNull(event.redact(2));
+ assertNull(event.redact(10));
+
+ // TAG_KEY_INTEGRITY_VIOLATION will be fully redacted if user does not match
+ event = createEvent(() -> {
+ EventLog.writeEvent(TAG_KEY_INTEGRITY_VIOLATION,
+ new Object[] {"alias", UserHandle.getUid(2, 123)});
+ }, TAG_KEY_INTEGRITY_VIOLATION);
+ assertNotNull(event.redact(2));
+ assertNull(event.redact(10));
+
+ }
+
+ /**
+ * Creates an Event object. Only the native code has the serialization and deserialization logic
+ * so need to actually emit a real log in order to generate the object.
+ */
+ private SecurityEvent createEvent(Runnable generator, int expectedTag) throws Exception {
+ Long markerData = System.currentTimeMillis();
+ EventLog.writeEvent(expectedTag, markerData);
+ generator.run();
+
+ List<Event> events = new ArrayList<>();
+ // Give the message some time to show up in the log
+ Thread.sleep(20);
+ EventLog.readEvents(new int[] {expectedTag}, events);
+
+ for (int i = 0; i < events.size() - 1; i++) {
+ if (markerData.equals(events.get(i).getData())) {
+ return new SecurityEvent(0, events.get(i + 1).getBytes());
+ }
}
- List<EventLog.Event> events = new ArrayList<>();
- try {
- EventLog.readEvents(new int[]{TAG_ADB_SHELL_CMD}, events);
- } catch (IOException e) {
- fail("Reading a test event from storage failed: " + e);
- }
- assertTrue("Unexpected number of events read from the log.", events.size() >= numEvents);
- // Read events generated by test, from the end of the log.
- List<SecurityEvent> securityEvents = new ArrayList<>();
- for (int i = events.size() - numEvents; i < events.size(); i++) {
- securityEvents.add(new SecurityEvent(id++, events.get(i).getBytes()));
- }
- return securityEvents;
+ throw new AssertionFailedError("Unable to locate marker event");
}
}
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 03b5e38..d138700 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,7 +21,6 @@
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;
@@ -63,7 +62,6 @@
private static final String PHONE_NUMBER_3 = "+9234567890";
private MockScheduledExecutorService mMockScheduledExecutorService;
- private TestContactQueryHelper mTestContactQueryHelper;
private ConversationStore mConversationStore;
private File mFile;
@@ -71,7 +69,6 @@
public void setUp() {
Context ctx = InstrumentationRegistry.getContext();
mFile = new File(ctx.getCacheDir(), "testdir");
- mTestContactQueryHelper = new TestContactQueryHelper(ctx);
resetConversationStore();
}
@@ -207,9 +204,6 @@
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);
@@ -240,9 +234,6 @@
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);
@@ -256,10 +247,6 @@
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);
@@ -290,9 +277,6 @@
// 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);
@@ -303,8 +287,7 @@
private void resetConversationStore() {
mFile.mkdir();
mMockScheduledExecutorService = new MockScheduledExecutorService();
- mConversationStore = new ConversationStore(mFile, mMockScheduledExecutorService,
- mTestContactQueryHelper);
+ mConversationStore = new ConversationStore(mFile, mMockScheduledExecutorService);
mConversationStore.loadConversationsFromDisk();
}
@@ -326,54 +309,4 @@
.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/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index b54317b..f0b7d20 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -52,6 +52,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.LauncherApps.ShortcutChangeCallback;
+import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutServiceInternal;
@@ -72,6 +74,7 @@
import com.android.internal.app.ChooserActivity;
import com.android.internal.content.PackageMonitor;
import com.android.server.LocalServices;
+import com.android.server.notification.NotificationManagerInternal;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import org.junit.After;
@@ -79,6 +82,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -87,6 +92,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@@ -102,6 +108,7 @@
private static final String TEST_PKG_NAME = "pkg";
private static final String TEST_CLASS_NAME = "class";
private static final String TEST_SHORTCUT_ID = "sc";
+ private static final int TEST_PKG_UID = 35;
private static final String CONTACT_URI = "content://com.android.contacts/contacts/lookup/123";
private static final String PHONE_NUMBER = "+1234567890";
private static final String NOTIFICATION_CHANNEL_ID = "test : sc";
@@ -111,7 +118,9 @@
@Mock private ShortcutServiceInternal mShortcutServiceInternal;
@Mock private UsageStatsManagerInternal mUsageStatsManagerInternal;
@Mock private PackageManagerInternal mPackageManagerInternal;
+ @Mock private NotificationManagerInternal mNotificationManagerInternal;
@Mock private UserManager mUserManager;
+ @Mock private PackageManager mPackageManager;
@Mock private TelephonyManager mTelephonyManager;
@Mock private TelecomManager mTelecomManager;
@Mock private ContentResolver mContentResolver;
@@ -121,13 +130,16 @@
@Mock private StatusBarNotification mStatusBarNotification;
@Mock private Notification mNotification;
+ @Captor private ArgumentCaptor<ShortcutChangeCallback> mShortcutChangeCallbackCaptor;
+
private NotificationChannel mNotificationChannel;
private DataManager mDataManager;
private CancellationSignal mCancellationSignal;
+ private ShortcutChangeCallback mShortcutChangeCallback;
private TestInjector mInjector;
@Before
- public void setUp() {
+ public void setUp() throws PackageManager.NameNotFoundException {
MockitoAnnotations.initMocks(this);
addLocalServiceMock(ShortcutServiceInternal.class, mShortcutServiceInternal);
@@ -143,8 +155,12 @@
return null;
}).when(mPackageManagerInternal).forEachInstalledPackage(any(Consumer.class), anyInt());
+ addLocalServiceMock(NotificationManagerInternal.class, mNotificationManagerInternal);
+
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
when(mContext.getPackageName()).thenReturn("android");
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
Context originalContext = getInstrumentation().getTargetContext();
when(mContext.getApplicationInfo()).thenReturn(originalContext.getApplicationInfo());
@@ -175,7 +191,8 @@
when(mUserManager.getEnabledProfiles(USER_ID_SECONDARY))
.thenReturn(Collections.singletonList(buildUserInfo(USER_ID_SECONDARY)));
- when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ when(mPackageManager.getPackageUidAsUser(TEST_PKG_NAME, USER_ID_PRIMARY))
+ .thenReturn(TEST_PKG_UID);
when(mStatusBarNotification.getNotification()).thenReturn(mNotification);
when(mStatusBarNotification.getPackageName()).thenReturn(TEST_PKG_NAME);
@@ -192,6 +209,10 @@
mInjector = new TestInjector();
mDataManager = new DataManager(mContext, mInjector);
mDataManager.initialize();
+
+ verify(mShortcutServiceInternal).addShortcutChangeCallback(
+ mShortcutChangeCallbackCaptor.capture());
+ mShortcutChangeCallback = mShortcutChangeCallbackCaptor.getValue();
}
@After
@@ -474,6 +495,43 @@
}
@Test
+ public void testShortcutAddedOrUpdated() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+ buildPerson());
+ mShortcutChangeCallback.onShortcutsAddedOrUpdated(TEST_PKG_NAME,
+ Collections.singletonList(shortcut), UserHandle.of(USER_ID_PRIMARY));
+
+ List<ConversationInfo> conversations = getConversationsInPrimary();
+
+ assertEquals(1, conversations.size());
+ assertEquals(TEST_SHORTCUT_ID, conversations.get(0).getShortcutId());
+ }
+
+ @Test
+ public void testShortcutDeleted() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+ ShortcutInfo shortcut1 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "sc1",
+ buildPerson());
+ ShortcutInfo shortcut2 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "sc2",
+ buildPerson());
+ mShortcutChangeCallback.onShortcutsAddedOrUpdated(TEST_PKG_NAME,
+ Arrays.asList(shortcut1, shortcut2), UserHandle.of(USER_ID_PRIMARY));
+ mShortcutChangeCallback.onShortcutsRemoved(TEST_PKG_NAME,
+ Collections.singletonList(shortcut1), UserHandle.of(USER_ID_PRIMARY));
+
+ List<ConversationInfo> conversations = getConversationsInPrimary();
+
+ assertEquals(1, conversations.size());
+ assertEquals("sc2", conversations.get(0).getShortcutId());
+
+ verify(mNotificationManagerInternal)
+ .onConversationRemoved(TEST_PKG_NAME, TEST_PKG_UID, "sc1");
+ }
+
+ @Test
public void testCallLogContentObserver() {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
mDataManager.onUserUnlocked(USER_ID_SECONDARY);
@@ -765,6 +823,11 @@
}
@Override
+ Executor getBackgroundExecutor() {
+ return Runnable::run;
+ }
+
+ @Override
ContactsQueryHelper createContactsQueryHelper(Context context) {
return mContactsQueryHelper;
}
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 e52cdf5..8191d17 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
@@ -61,7 +61,7 @@
testDir.mkdir();
mPackageData = new PackageData(
PACKAGE_NAME, USER_ID, pkg -> mIsDefaultDialer, pkg -> mIsDefaultSmsApp,
- new MockScheduledExecutorService(), testDir, new ContactsQueryHelper(ctx));
+ new MockScheduledExecutorService(), testDir);
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 418067f..7934d33 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
@@ -79,10 +79,9 @@
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);
+ scheduledExecutorService, testDir);
mPackageData.mConversationStore.mConversationInfo = new ConversationInfo.Builder()
.setShortcutId(SHORTCUT_ID)
.setLocusId(LOCUS_ID_1)
@@ -221,9 +220,8 @@
private ConversationInfo mConversationInfo;
TestConversationStore(File packageDir,
- ScheduledExecutorService scheduledExecutorService,
- ContactsQueryHelper helper) {
- super(packageDir, scheduledExecutorService, helper);
+ ScheduledExecutorService scheduledExecutorService) {
+ super(packageDir, scheduledExecutorService);
}
@Override
@@ -241,12 +239,10 @@
TestPackageData(@NonNull String packageName, @UserIdInt int userId,
@NonNull Predicate<String> isDefaultDialerPredicate,
@NonNull Predicate<String> isDefaultSmsAppPredicate,
- @NonNull ScheduledExecutorService scheduledExecutorService, @NonNull File rootDir,
- @NonNull ContactsQueryHelper helper) {
+ @NonNull ScheduledExecutorService scheduledExecutorService, @NonNull File rootDir) {
super(packageName, userId, isDefaultDialerPredicate, isDefaultSmsAppPredicate,
- scheduledExecutorService, rootDir, helper);
- mConversationStore = new TestConversationStore(rootDir, scheduledExecutorService,
- helper);
+ scheduledExecutorService, rootDir);
+ mConversationStore = new TestConversationStore(rootDir, scheduledExecutorService);
mEventStore = new TestEventStore(rootDir, scheduledExecutorService);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 118c540..bec37e9 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -812,7 +812,7 @@
"android", 0, mUserManager.getPrimaryUser().getUserHandle())
.getSystemService(Context.USER_SERVICE);
- List<UserHandle> profiles = um.getUserProfiles(false);
+ List<UserHandle> profiles = um.getAllProfiles();
assertThat(profiles.size()).isEqualTo(2);
assertThat(profiles.get(0).equals(userProfile.getUserHandle())
|| profiles.get(1).equals(userProfile.getUserHandle())).isTrue();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index ccce043..d0283f7 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3246,7 +3246,7 @@
new BufferedInputStream(new ByteArrayInputStream(upgradeXml.getBytes())),
false,
UserHandle.USER_ALL);
- verify(mSnoozeHelper, times(1)).readXml(any(XmlPullParser.class));
+ verify(mSnoozeHelper, times(1)).readXml(any(XmlPullParser.class), anyLong());
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index 1dd0b1a..816e8e5 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -22,6 +22,7 @@
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
@@ -96,10 +97,33 @@
XmlPullParser parser = Xml.newPullParser();
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml_string.getBytes())), null);
- mSnoozeHelper.readXml(parser);
- assertTrue("Should read the notification time from xml and it should be more than zero",
- 0 < mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
- 0, "pkg", "key").doubleValue());
+ mSnoozeHelper.readXml(parser, 1);
+ assertEquals((long) Long.MAX_VALUE, (long) mSnoozeHelper
+ .getSnoozeTimeForUnpostedNotification(0, "pkg", "key"));
+ verify(mAm, never()).setExactAndAllowWhileIdle(anyInt(), anyLong(), any());
+ }
+
+ @Test
+ public void testWriteXML_afterReading_noNPE()
+ throws XmlPullParserException, IOException {
+ final String max_time_str = Long.toString(Long.MAX_VALUE);
+ final String xml_string = "<snoozed-notifications>"
+ + "<notification version=\"1\" user-id=\"0\" notification=\"notification\" "
+ + "pkg=\"pkg\" key=\"key\" time=\"" + max_time_str + "\"/>"
+ + "<notification version=\"1\" user-id=\"0\" notification=\"notification\" "
+ + "pkg=\"pkg\" key=\"key2\" time=\"" + max_time_str + "\"/>"
+ + "</snoozed-notifications>";
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(xml_string.getBytes())), null);
+ mSnoozeHelper.readXml(parser, 1);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ XmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+ mSnoozeHelper.writeXml(serializer);
+ serializer.endDocument();
+ serializer.flush();
}
@Test
@@ -115,7 +139,7 @@
XmlPullParser parser = Xml.newPullParser();
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml_string.getBytes())), null);
- mSnoozeHelper.readXml(parser);
+ mSnoozeHelper.readXml(parser, 1);
assertEquals("Should read the notification context from xml and it should be `uri",
"uri", mSnoozeHelper.getSnoozeContextForUnpostedNotification(
0, "pkg", "key"));
@@ -137,7 +161,7 @@
XmlPullParser parser = Xml.newPullParser();
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), "utf-8");
- mSnoozeHelper.readXml(parser);
+ mSnoozeHelper.readXml(parser, 1);
assertTrue("Should read the notification time from xml and it should be more than zero",
0 < mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
0, "pkg", r.getKey()).doubleValue());
@@ -161,7 +185,7 @@
XmlPullParser parser = Xml.newPullParser();
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), "utf-8");
- mSnoozeHelper.readXml(parser);
+ mSnoozeHelper.readXml(parser, 2);
int systemUser = UserHandle.SYSTEM.getIdentifier();
assertTrue("Should see a past time returned",
System.currentTimeMillis() > mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
@@ -195,6 +219,30 @@
}
@Test
+ public void testScheduleRepostsForPersistedNotifications() throws Exception {
+ final String xml_string = "<snoozed-notifications>"
+ + "<notification version=\"1\" user-id=\"0\" notification=\"notification\" "
+ + "pkg=\"pkg\" key=\"key\" time=\"" + 10 + "\"/>"
+ + "<notification version=\"1\" user-id=\"0\" notification=\"notification\" "
+ + "pkg=\"pkg\" key=\"key2\" time=\"" + 15+ "\"/>"
+ + "</snoozed-notifications>";
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(xml_string.getBytes())), null);
+ mSnoozeHelper.readXml(parser, 4);
+
+ mSnoozeHelper.scheduleRepostsForPersistedNotifications(5);
+
+ ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq((long) 10), captor.capture());
+ assertEquals("key", captor.getValue().getIntent().getStringExtra(EXTRA_KEY));
+
+ ArgumentCaptor<PendingIntent> captor2 = ArgumentCaptor.forClass(PendingIntent.class);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq((long) 15), captor2.capture());
+ assertEquals("key2", captor2.getValue().getIntent().getStringExtra(EXTRA_KEY));
+ }
+
+ @Test
public void testSnoozeForTime() throws Exception {
NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
mSnoozeHelper.snooze(r, 1000);
@@ -414,6 +462,23 @@
}
@Test
+ public void testGetSnoozedGroupNotifications_nonGrouped() throws Exception {
+ IntArray profileIds = new IntArray();
+ profileIds.add(UserHandle.USER_CURRENT);
+ when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
+ NotificationRecord r = getNotificationRecord("pkg", 1, "tag",
+ UserHandle.CURRENT, "group", true);
+ NotificationRecord r2 = getNotificationRecord("pkg", 2, "tag",
+ UserHandle.CURRENT, null, true);
+ mSnoozeHelper.snooze(r, 1000);
+ mSnoozeHelper.snooze(r2, 1000);
+
+ assertEquals(1,
+ mSnoozeHelper.getNotifications("pkg", "group", UserHandle.USER_CURRENT).size());
+ // and no NPE
+ }
+
+ @Test
public void testGetSnoozedNotificationByKey() throws Exception {
IntArray profileIds = new IntArray();
profileIds.add(UserHandle.USER_CURRENT);
diff --git a/tools/hiddenapi/merge_csv.py b/tools/hiddenapi/merge_csv.py
index 9661927..6a5b0e1 100755
--- a/tools/hiddenapi/merge_csv.py
+++ b/tools/hiddenapi/merge_csv.py
@@ -14,26 +14,56 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""
-Merge mutliple CSV files, possibly with different columns, writing to stdout.
+Merge multiple CSV files, possibly with different columns.
"""
+import argparse
import csv
-import sys
+import io
-csv_readers = [
- csv.DictReader(open(csv_file, 'r'), delimiter=',', quotechar='|')
- for csv_file in sys.argv[1:]
-]
+from zipfile import ZipFile
-# Build union of all columns from source files:
+args_parser = argparse.ArgumentParser(description='Merge given CSV files into a single one.')
+args_parser.add_argument('--header', help='Comma separated field names; '
+ 'if missing determines the header from input files.')
+args_parser.add_argument('--zip_input', help='ZIP archive with all CSV files to merge.')
+args_parser.add_argument('--output', help='Output file for merged CSV.',
+ default='-', type=argparse.FileType('w'))
+args_parser.add_argument('files', nargs=argparse.REMAINDER)
+args = args_parser.parse_args()
+
+
+def dict_reader(input):
+ return csv.DictReader(input, delimiter=',', quotechar='|')
+
+
+if args.zip_input and len(args.files) > 0:
+ raise ValueError('Expecting either a single ZIP with CSV files'
+ ' or a list of CSV files as input; not both.')
+
+csv_readers = []
+if len(args.files) > 0:
+ for file in args.files:
+ csv_readers.append(dict_reader(open(file, 'r')))
+elif args.zip_input:
+ with ZipFile(args.zip_input) as zip:
+ for entry in zip.namelist():
+ if entry.endswith('.uau'):
+ csv_readers.append(dict_reader(io.TextIOWrapper(zip.open(entry, 'r'))))
+
headers = set()
-for reader in csv_readers:
- headers = headers.union(reader.fieldnames)
+if args.header:
+ fieldnames = args.header.split(',')
+else:
+ # Build union of all columns from source files:
+ for reader in csv_readers:
+ headers = headers.union(reader.fieldnames)
+ fieldnames = sorted(headers)
# Concatenate all files to output:
-out = csv.DictWriter(sys.stdout, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL,
- dialect='unix', fieldnames=sorted(headers))
-out.writeheader()
+writer = csv.DictWriter(args.output, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL,
+ dialect='unix', fieldnames=fieldnames)
+writer.writeheader()
for reader in csv_readers:
for row in reader:
- out.writerow(row)
+ writer.writerow(row)