Merge "[PM] Add the flag for providing info of APK-in-APEX" into main
diff --git a/Android.bp b/Android.bp
index 91f03e003..bb93048 100644
--- a/Android.bp
+++ b/Android.bp
@@ -97,6 +97,7 @@
// AIDL sources from external directories
":android.hardware.biometrics.common-V4-java-source",
":android.hardware.biometrics.fingerprint-V3-java-source",
+ ":android.hardware.biometrics.face-V4-java-source",
":android.hardware.gnss-V2-java-source",
":android.hardware.graphics.common-V3-java-source",
":android.hardware.keymaster-V4-java-source",
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 83db4cb..900c902 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1828,7 +1828,9 @@
/* system_measured_source_download_bytes */0,
/* system_measured_source_upload_bytes */ 0,
/* system_measured_calling_download_bytes */0,
- /* system_measured_calling_upload_bytes */ 0);
+ /* system_measured_calling_upload_bytes */ 0,
+ jobStatus.getJob().getIntervalMillis(),
+ jobStatus.getJob().getFlexMillis());
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -2269,7 +2271,9 @@
/* system_measured_source_download_bytes */ 0,
/* system_measured_source_upload_bytes */ 0,
/* system_measured_calling_download_bytes */0,
- /* system_measured_calling_upload_bytes */ 0);
+ /* system_measured_calling_upload_bytes */ 0,
+ cancelled.getJob().getIntervalMillis(),
+ cancelled.getJob().getFlexMillis());
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 6449edc..3addf9f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -534,7 +534,9 @@
/* system_measured_source_download_bytes */ 0,
/* system_measured_source_upload_bytes */ 0,
/* system_measured_calling_download_bytes */ 0,
- /* system_measured_calling_upload_bytes */ 0);
+ /* system_measured_calling_upload_bytes */ 0,
+ job.getJob().getIntervalMillis(),
+ job.getJob().getFlexMillis());
sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
final String sourcePackage = job.getSourcePackageName();
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
@@ -1616,7 +1618,9 @@
TrafficStats.getUidRxBytes(completedJob.getUid())
- mInitialDownloadedBytesFromCalling,
TrafficStats.getUidTxBytes(completedJob.getUid())
- - mInitialUploadedBytesFromCalling);
+ - mInitialUploadedBytesFromCalling,
+ completedJob.getJob().getIntervalMillis(),
+ completedJob.getJob().getFlexMillis());
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
getId());
diff --git a/core/api/current.txt b/core/api/current.txt
index 12a6f74..d3ed89b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -35874,19 +35874,19 @@
field public static final String NAMESPACE = "data2";
}
- public static final class ContactsContract.CommonDataKinds.Im implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
- method public static CharSequence getProtocolLabel(android.content.res.Resources, int, CharSequence);
- method public static int getProtocolLabelResource(int);
- method public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
- method public static int getTypeLabelResource(int);
- field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/im";
- field public static final String CUSTOM_PROTOCOL = "data6";
+ @Deprecated public static final class ContactsContract.CommonDataKinds.Im implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
+ method @Deprecated public static CharSequence getProtocolLabel(android.content.res.Resources, int, CharSequence);
+ method @Deprecated public static int getProtocolLabelResource(int);
+ method @Deprecated public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
+ method @Deprecated public static int getTypeLabelResource(int);
+ field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/im";
+ field @Deprecated public static final String CUSTOM_PROTOCOL = "data6";
field public static final String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX";
field public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS";
field public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "android.provider.extra.ADDRESS_BOOK_INDEX_TITLES";
- field public static final String PROTOCOL = "data5";
+ field @Deprecated public static final String PROTOCOL = "data5";
field @Deprecated public static final int PROTOCOL_AIM = 0; // 0x0
- field public static final int PROTOCOL_CUSTOM = -1; // 0xffffffff
+ field @Deprecated public static final int PROTOCOL_CUSTOM = -1; // 0xffffffff
field @Deprecated public static final int PROTOCOL_GOOGLE_TALK = 5; // 0x5
field @Deprecated public static final int PROTOCOL_ICQ = 6; // 0x6
field @Deprecated public static final int PROTOCOL_JABBER = 7; // 0x7
@@ -35895,9 +35895,9 @@
field @Deprecated public static final int PROTOCOL_QQ = 4; // 0x4
field @Deprecated public static final int PROTOCOL_SKYPE = 3; // 0x3
field @Deprecated public static final int PROTOCOL_YAHOO = 2; // 0x2
- field public static final int TYPE_HOME = 1; // 0x1
- field public static final int TYPE_OTHER = 3; // 0x3
- field public static final int TYPE_WORK = 2; // 0x2
+ field @Deprecated public static final int TYPE_HOME = 1; // 0x1
+ field @Deprecated public static final int TYPE_OTHER = 3; // 0x3
+ field @Deprecated public static final int TYPE_WORK = 2; // 0x2
}
public static final class ContactsContract.CommonDataKinds.Nickname implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
@@ -36012,17 +36012,17 @@
field public static final int TYPE_SPOUSE = 14; // 0xe
}
- public static final class ContactsContract.CommonDataKinds.SipAddress implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
- method public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
- method public static int getTypeLabelResource(int);
- field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sip_address";
+ @Deprecated public static final class ContactsContract.CommonDataKinds.SipAddress implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
+ method @Deprecated public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence);
+ method @Deprecated public static int getTypeLabelResource(int);
+ field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sip_address";
field public static final String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX";
field public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS";
field public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "android.provider.extra.ADDRESS_BOOK_INDEX_TITLES";
- field public static final String SIP_ADDRESS = "data1";
- field public static final int TYPE_HOME = 1; // 0x1
- field public static final int TYPE_OTHER = 3; // 0x3
- field public static final int TYPE_WORK = 2; // 0x2
+ field @Deprecated public static final String SIP_ADDRESS = "data1";
+ field @Deprecated public static final int TYPE_HOME = 1; // 0x1
+ field @Deprecated public static final int TYPE_OTHER = 3; // 0x3
+ field @Deprecated public static final int TYPE_WORK = 2; // 0x2
}
public static final class ContactsContract.CommonDataKinds.StructuredName implements android.provider.ContactsContract.DataColumnsWithJoins {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 42daea2..aaeba66 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -369,11 +369,14 @@
}
public class NotificationManager {
+ method @FlaggedApi("android.app.modes_api") @NonNull public String addAutomaticZenRule(@NonNull android.app.AutomaticZenRule, boolean);
method public void cleanUpCallersAfter(long);
method public android.content.ComponentName getEffectsSuppressor();
method public boolean isNotificationPolicyAccessGrantedForPackage(@NonNull String);
+ method @FlaggedApi("android.app.modes_api") public boolean removeAutomaticZenRule(@NonNull String, boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted(@NonNull android.content.ComponentName, boolean, boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING) public void setToastRateLimitingEnabled(boolean);
+ method @FlaggedApi("android.app.modes_api") public boolean updateAutomaticZenRule(@NonNull String, @NonNull android.app.AutomaticZenRule, boolean);
method public void updateNotificationChannel(@NonNull String, int, @NonNull android.app.NotificationChannel);
}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 9438571..b7db5f5 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -167,7 +167,7 @@
void requestInterruptionFilterFromListener(in INotificationListener token, int interruptionFilter);
int getInterruptionFilterFromListener(in INotificationListener token);
void setOnNotificationPostedTrimFromListener(in INotificationListener token, int trim);
- void setInterruptionFilter(String pkg, int interruptionFilter);
+ void setInterruptionFilter(String pkg, int interruptionFilter, boolean fromUser);
void updateNotificationChannelGroupFromPrivilegedListener(in INotificationListener token, String pkg, in UserHandle user, in NotificationChannelGroup group);
void updateNotificationChannelFromPrivilegedListener(in INotificationListener token, String pkg, in UserHandle user, in NotificationChannel channel);
@@ -205,11 +205,11 @@
@UnsupportedAppUsage
ZenModeConfig getZenModeConfig();
NotificationManager.Policy getConsolidatedNotificationPolicy();
- oneway void setZenMode(int mode, in Uri conditionId, String reason);
+ oneway void setZenMode(int mode, in Uri conditionId, String reason, boolean fromUser);
oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions);
boolean isNotificationPolicyAccessGranted(String pkg);
NotificationManager.Policy getNotificationPolicy(String pkg);
- void setNotificationPolicy(String pkg, in NotificationManager.Policy policy);
+ void setNotificationPolicy(String pkg, in NotificationManager.Policy policy, boolean fromUser);
boolean isNotificationPolicyAccessGrantedForPackage(String pkg);
void setNotificationPolicyAccessGranted(String pkg, boolean granted);
void setNotificationPolicyAccessGrantedForUser(String pkg, int userId, boolean granted);
@@ -217,12 +217,12 @@
Map<String, AutomaticZenRule> getAutomaticZenRules();
// TODO: b/310620812 - Remove getZenRules() when MODES_API is inlined.
List<ZenModeConfig.ZenRule> getZenRules();
- String addAutomaticZenRule(in AutomaticZenRule automaticZenRule, String pkg);
- boolean updateAutomaticZenRule(String id, in AutomaticZenRule automaticZenRule);
- boolean removeAutomaticZenRule(String id);
- boolean removeAutomaticZenRules(String packageName);
+ String addAutomaticZenRule(in AutomaticZenRule automaticZenRule, String pkg, boolean fromUser);
+ boolean updateAutomaticZenRule(String id, in AutomaticZenRule automaticZenRule, boolean fromUser);
+ boolean removeAutomaticZenRule(String id, boolean fromUser);
+ boolean removeAutomaticZenRules(String packageName, boolean fromUser);
int getRuleInstanceCount(in ComponentName owner);
- void setAutomaticZenRuleState(String id, in Condition condition);
+ void setAutomaticZenRuleState(String id, in Condition condition, boolean fromUser);
byte[] getBackupPayload(int user);
void applyRestore(in byte[] payload, int user);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index d23b16d..f76a45b 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1184,14 +1184,20 @@
*/
@UnsupportedAppUsage
public void setZenMode(int mode, Uri conditionId, String reason) {
+ setZenMode(mode, conditionId, reason, /* fromUser= */ false);
+ }
+
+ /** @hide */
+ public void setZenMode(int mode, Uri conditionId, String reason, boolean fromUser) {
INotificationManager service = getService();
try {
- service.setZenMode(mode, conditionId, reason);
+ service.setZenMode(mode, conditionId, reason, fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
+
/**
* @hide
*/
@@ -1325,9 +1331,19 @@
* @return The id of the newly created rule; null if the rule could not be created.
*/
public String addAutomaticZenRule(AutomaticZenRule automaticZenRule) {
+ return addAutomaticZenRule(automaticZenRule, /* fromUser= */ false);
+ }
+
+ /** @hide */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @NonNull
+ public String addAutomaticZenRule(@NonNull AutomaticZenRule automaticZenRule,
+ boolean fromUser) {
INotificationManager service = getService();
try {
- return service.addAutomaticZenRule(automaticZenRule, mContext.getPackageName());
+ return service.addAutomaticZenRule(automaticZenRule,
+ mContext.getPackageName(), fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1347,9 +1363,17 @@
* @return Whether the rule was successfully updated.
*/
public boolean updateAutomaticZenRule(String id, AutomaticZenRule automaticZenRule) {
+ return updateAutomaticZenRule(id, automaticZenRule, /* fromUser= */ false);
+ }
+
+ /** @hide */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public boolean updateAutomaticZenRule(@NonNull String id,
+ @NonNull AutomaticZenRule automaticZenRule, boolean fromUser) {
INotificationManager service = getService();
try {
- return service.updateAutomaticZenRule(id, automaticZenRule);
+ return service.updateAutomaticZenRule(id, automaticZenRule, fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1367,9 +1391,20 @@
* @param condition The new state of this rule
*/
public void setAutomaticZenRuleState(@NonNull String id, @NonNull Condition condition) {
+ if (Flags.modesApi()) {
+ setAutomaticZenRuleState(id, condition,
+ /* fromUser= */ condition.source == Condition.SOURCE_USER_ACTION);
+ } else {
+ setAutomaticZenRuleState(id, condition, /* fromUser= */ false);
+ }
+ }
+
+ /** @hide */
+ public void setAutomaticZenRuleState(@NonNull String id, @NonNull Condition condition,
+ boolean fromUser) {
INotificationManager service = getService();
try {
- service.setAutomaticZenRuleState(id, condition);
+ service.setAutomaticZenRuleState(id, condition, fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1388,9 +1423,16 @@
* @return Whether the rule was successfully deleted.
*/
public boolean removeAutomaticZenRule(String id) {
+ return removeAutomaticZenRule(id, /* fromUser= */ false);
+ }
+
+ /** @hide */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public boolean removeAutomaticZenRule(@NonNull String id, boolean fromUser) {
INotificationManager service = getService();
try {
- return service.removeAutomaticZenRule(id);
+ return service.removeAutomaticZenRule(id, fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1402,9 +1444,14 @@
* @hide
*/
public boolean removeAutomaticZenRules(String packageName) {
+ return removeAutomaticZenRules(packageName, /* fromUser= */ false);
+ }
+
+ /** @hide */
+ public boolean removeAutomaticZenRules(String packageName, boolean fromUser) {
INotificationManager service = getService();
try {
- return service.removeAutomaticZenRules(packageName);
+ return service.removeAutomaticZenRules(packageName, fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1685,10 +1732,15 @@
*/
// TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
public void setNotificationPolicy(@NonNull Policy policy) {
+ setNotificationPolicy(policy, /* fromUser= */ false);
+ }
+
+ /** @hide */
+ public void setNotificationPolicy(@NonNull Policy policy, boolean fromUser) {
checkRequired("policy", policy);
INotificationManager service = getService();
try {
- service.setNotificationPolicy(mContext.getOpPackageName(), policy);
+ service.setNotificationPolicy(mContext.getOpPackageName(), policy, fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2685,9 +2737,16 @@
*/
// TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
public final void setInterruptionFilter(@InterruptionFilter int interruptionFilter) {
+ setInterruptionFilter(interruptionFilter, /* fromUser= */ false);
+ }
+
+ /** @hide */
+ public final void setInterruptionFilter(@InterruptionFilter int interruptionFilter,
+ boolean fromUser) {
final INotificationManager service = getService();
try {
- service.setInterruptionFilter(mContext.getOpPackageName(), interruptionFilter);
+ service.setInterruptionFilter(mContext.getOpPackageName(), interruptionFilter,
+ fromUser);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/usage/UsageEventsQuery.java b/core/java/android/app/usage/UsageEventsQuery.java
index 8c63d18..3cd2923 100644
--- a/core/java/android/app/usage/UsageEventsQuery.java
+++ b/core/java/android/app/usage/UsageEventsQuery.java
@@ -19,9 +19,11 @@
import android.annotation.CurrentTimeMillisLong;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.app.usage.UsageEvents.Event;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.util.ArraySet;
import com.android.internal.util.ArrayUtils;
@@ -40,11 +42,13 @@
private final @CurrentTimeMillisLong long mBeginTimeMillis;
private final @CurrentTimeMillisLong long mEndTimeMillis;
private final @Event.EventType int[] mEventTypes;
+ private final @UserIdInt int mUserId;
private UsageEventsQuery(@NonNull Builder builder) {
mBeginTimeMillis = builder.mBeginTimeMillis;
mEndTimeMillis = builder.mEndTimeMillis;
mEventTypes = ArrayUtils.convertToIntArray(builder.mEventTypes);
+ mUserId = builder.mUserId;
}
private UsageEventsQuery(Parcel in) {
@@ -53,6 +57,7 @@
int eventTypesLength = in.readInt();
mEventTypes = new int[eventTypesLength];
in.readIntArray(mEventTypes);
+ mUserId = in.readInt();
}
/**
@@ -87,6 +92,11 @@
return eventTypeSet;
}
+ /** @hide */
+ public @UserIdInt int getUserId() {
+ return mUserId;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -98,6 +108,7 @@
dest.writeLong(mEndTimeMillis);
dest.writeInt(mEventTypes.length);
dest.writeIntArray(mEventTypes);
+ dest.writeInt(mUserId);
}
@NonNull
@@ -126,6 +137,7 @@
private final @CurrentTimeMillisLong long mBeginTimeMillis;
private final @CurrentTimeMillisLong long mEndTimeMillis;
private final ArraySet<Integer> mEventTypes = new ArraySet<>();
+ private @UserIdInt int mUserId = UserHandle.USER_NULL;
/**
* Constructor that specifies the period for which to return events.
@@ -169,5 +181,15 @@
}
return this;
}
+
+ /**
+ * Specifices the user id for the query.
+ * @param userId for whom the query should be performed.
+ * @hide
+ */
+ public @NonNull Builder setUserId(@UserIdInt int userId) {
+ mUserId = userId;
+ return this;
+ }
}
}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index d13d962..12da665 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1393,6 +1393,18 @@
public static final long OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION = 255940284L;
/**
+ * Enables {@link #SCREEN_ORIENTATION_USER} which overrides any orientation requested
+ * by the activity. Fixed orientation apps can be overridden to fullscreen on large
+ * screen devices with ignoreOrientationRequest enabled with this override.
+ *
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_ANY_ORIENTATION_TO_USER = 310816437L;
+
+ /**
* Compares activity window layout min width/height with require space for multi window to
* determine if it can be put into multi window mode.
*/
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 57025c2..9a1796f 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -56,3 +56,11 @@
description: "Add support to lock private space automatically after a time period"
bug: "303201022"
}
+
+flag {
+ name: "avatar_sync"
+ namespace: "multiuser"
+ description: "Implement new Avatar Picker outside of SetttingsLib with ability to select avatars from Google Account and synchronise to any changes."
+ bug: "296829976"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index f71e853..ffd7212 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -715,6 +715,14 @@
boolean startOffload();
void stopOffload();
+
+ /**
+ * Called when {@link DisplayOffloadSession} tries to block screen turning on.
+ *
+ * @param unblocker a {@link Runnable} executed upon all work required before screen turning
+ * on is done.
+ */
+ void onBlockingScreenOn(Runnable unblocker);
}
/** A session token that associates a internal display with a {@link DisplayOffloader}. */
@@ -734,6 +742,15 @@
*/
void updateBrightness(float brightness);
+ /**
+ * Called while display is turning to state ON to leave a small period for displayoffload
+ * session to finish some work.
+ *
+ * @param unblocker a {@link Runnable} used by displayoffload session to notify
+ * {@link DisplayManager} that it can continue turning screen on.
+ */
+ boolean blockScreenOn(Runnable unblocker);
+
/** Returns whether displayoffload supports the given display state. */
static boolean isSupportedOffloadState(int displayState) {
return Display.isSuspendedState(displayState);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorKosmos.kt b/core/java/android/hardware/face/FaceSensorConfigurations.aidl
similarity index 63%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorKosmos.kt
rename to core/java/android/hardware/face/FaceSensorConfigurations.aidl
index 94d9a72..26367b3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorKosmos.kt
+++ b/core/java/android/hardware/face/FaceSensorConfigurations.aidl
@@ -13,13 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package android.hardware.face;
-package com.android.systemui.common.domain.interactor
-
-import com.android.systemui.common.ui.data.repository.configurationRepository
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-
-val Kosmos.configurationInteractor by Fixture {
- ConfigurationInteractorImpl(repository = configurationRepository)
-}
+parcelable FaceSensorConfigurations;
\ No newline at end of file
diff --git a/core/java/android/hardware/face/FaceSensorConfigurations.java b/core/java/android/hardware/face/FaceSensorConfigurations.java
new file mode 100644
index 0000000..6ef692f
--- /dev/null
+++ b/core/java/android/hardware/face/FaceSensorConfigurations.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.face;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.SensorProps;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+
+/**
+ * Provides the sensor props for face sensor, if available.
+ * @hide
+ */
+public class FaceSensorConfigurations implements Parcelable {
+ private static final String TAG = "FaceSensorConfigurations";
+
+ private final boolean mResetLockoutRequiresChallenge;
+ private final Map<String, SensorProps[]> mSensorPropsMap;
+
+ public static final Creator<FaceSensorConfigurations> CREATOR =
+ new Creator<FaceSensorConfigurations>() {
+ @Override
+ public FaceSensorConfigurations createFromParcel(Parcel in) {
+ return new FaceSensorConfigurations(in);
+ }
+
+ @Override
+ public FaceSensorConfigurations[] newArray(int size) {
+ return new FaceSensorConfigurations[size];
+ }
+ };
+
+ public FaceSensorConfigurations(boolean resetLockoutRequiresChallenge) {
+ mResetLockoutRequiresChallenge = resetLockoutRequiresChallenge;
+ mSensorPropsMap = new HashMap<>();
+ }
+
+ protected FaceSensorConfigurations(Parcel in) {
+ mResetLockoutRequiresChallenge = in.readByte() != 0;
+ mSensorPropsMap = in.readHashMap(null, String.class, SensorProps[].class);
+ }
+
+ /**
+ * Process AIDL instances to extract sensor props and add it to the sensor map.
+ * @param aidlInstances available face AIDL instances
+ * @param getIFace function that provides the daemon for the specific instance
+ */
+ public void addAidlConfigs(@NonNull String[] aidlInstances,
+ @NonNull Function<String, IFace> getIFace) {
+ for (String aidlInstance : aidlInstances) {
+ final String fqName = IFace.DESCRIPTOR + "/" + aidlInstance;
+ IFace face = getIFace.apply(fqName);
+ try {
+ if (face != null) {
+ mSensorPropsMap.put(aidlInstance, face.getSensorProps());
+ } else {
+ Slog.e(TAG, "Unable to get declared service: " + fqName);
+ }
+ } catch (RemoteException e) {
+ Log.d(TAG, "Unable to get sensor properties!");
+ }
+ }
+ }
+
+ /**
+ * Parse through HIDL configuration and add it to the sensor map.
+ */
+ public void addHidlConfigs(@NonNull String[] hidlConfigStrings,
+ @NonNull Context context) {
+ final List<HidlFaceSensorConfig> hidlFaceSensorConfigs = new ArrayList<>();
+ for (String hidlConfig: hidlConfigStrings) {
+ final HidlFaceSensorConfig hidlFaceSensorConfig = new HidlFaceSensorConfig();
+ try {
+ hidlFaceSensorConfig.parse(hidlConfig, context);
+ } catch (Exception e) {
+ Log.e(TAG, "HIDL sensor configuration format is incorrect.");
+ continue;
+ }
+ if (hidlFaceSensorConfig.getModality() == TYPE_FACE) {
+ hidlFaceSensorConfigs.add(hidlFaceSensorConfig);
+ }
+ }
+ final String hidlHalInstanceName = "defaultHIDL";
+ mSensorPropsMap.put(hidlHalInstanceName, hidlFaceSensorConfigs.toArray(
+ new SensorProps[hidlFaceSensorConfigs.size()]));
+ }
+
+ /**
+ * Returns true if any face sensors have been added.
+ */
+ public boolean hasSensorConfigurations() {
+ return mSensorPropsMap.size() > 0;
+ }
+
+ /**
+ * Returns true if there is only a single face sensor configuration available.
+ */
+ public boolean isSingleSensorConfigurationPresent() {
+ return mSensorPropsMap.size() == 1;
+ }
+
+ /**
+ * Return sensor props for the given instance. If instance is not available,
+ * then null is returned.
+ */
+ @Nullable
+ public Pair<String, SensorProps[]> getSensorPairForInstance(String instance) {
+ if (mSensorPropsMap.containsKey(instance)) {
+ return new Pair<>(instance, mSensorPropsMap.get(instance));
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the first pair of instance and sensor props, which does not correspond to the given
+ * If instance is not available, then null is returned.
+ */
+ @Nullable
+ public Pair<String, SensorProps[]> getSensorPairNotForInstance(String instance) {
+ Optional<String> notAVirtualInstance = mSensorPropsMap.keySet().stream().filter(
+ (instanceName) -> !instanceName.equals(instance)).findFirst();
+ return notAVirtualInstance.map(this::getSensorPairForInstance).orElseGet(
+ this::getSensorPair);
+ }
+
+ /**
+ * Returns the first pair of instance and sensor props that has been added to the map.
+ */
+ @Nullable
+ public Pair<String, SensorProps[]> getSensorPair() {
+ Optional<String> optionalInstance = mSensorPropsMap.keySet().stream().findFirst();
+ return optionalInstance.map(this::getSensorPairForInstance).orElse(null);
+
+ }
+
+ public boolean getResetLockoutRequiresChallenge() {
+ return mResetLockoutRequiresChallenge;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeByte((byte) (mResetLockoutRequiresChallenge ? 1 : 0));
+ dest.writeMap(mSensorPropsMap);
+ }
+}
diff --git a/core/java/android/hardware/face/HidlFaceSensorConfig.java b/core/java/android/hardware/face/HidlFaceSensorConfig.java
new file mode 100644
index 0000000..cab146d
--- /dev/null
+++ b/core/java/android/hardware/face/HidlFaceSensorConfig.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.face;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.common.SensorStrength;
+import android.hardware.biometrics.face.SensorProps;
+
+import com.android.internal.R;
+
+/**
+ * Parse HIDL face sensor config and map it to SensorProps.aidl to match AIDL.
+ * See core/res/res/values/config.xml config_biometric_sensors
+ * @hide
+ */
+public final class HidlFaceSensorConfig extends SensorProps {
+ private int mSensorId;
+ private int mModality;
+ private int mStrength;
+
+ /**
+ * Parse through the config string and map it to SensorProps.aidl.
+ * @throws IllegalArgumentException when config string has unexpected format
+ */
+ public void parse(@NonNull String config, @NonNull Context context)
+ throws IllegalArgumentException {
+ final String[] elems = config.split(":");
+ if (elems.length < 3) {
+ throw new IllegalArgumentException();
+ }
+ mSensorId = Integer.parseInt(elems[0]);
+ mModality = Integer.parseInt(elems[1]);
+ mStrength = Integer.parseInt(elems[2]);
+ mapHidlToAidlFaceSensorConfigurations(context);
+ }
+
+ @BiometricAuthenticator.Modality
+ public int getModality() {
+ return mModality;
+ }
+
+ private void mapHidlToAidlFaceSensorConfigurations(@NonNull Context context) {
+ commonProps = new CommonProps();
+ commonProps.sensorId = mSensorId;
+ commonProps.sensorStrength = authenticatorStrengthToPropertyStrength(mStrength);
+ halControlsPreview = context.getResources().getBoolean(
+ R.bool.config_faceAuthSupportsSelfIllumination);
+ commonProps.maxEnrollmentsPerUser = context.getResources().getInteger(
+ R.integer.config_faceMaxTemplatesPerUser);
+ commonProps.componentInfo = null;
+ supportsDetectInteraction = false;
+ }
+
+ private byte authenticatorStrengthToPropertyStrength(
+ @BiometricManager.Authenticators.Types int strength) {
+ switch (strength) {
+ case BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE:
+ return SensorStrength.CONVENIENCE;
+ case BiometricManager.Authenticators.BIOMETRIC_WEAK:
+ return SensorStrength.WEAK;
+ case BiometricManager.Authenticators.BIOMETRIC_STRONG:
+ return SensorStrength.STRONG;
+ default:
+ throw new IllegalArgumentException("Unknown strength: " + strength);
+ }
+ }
+}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 7080133..0096877 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -26,6 +26,7 @@
import android.hardware.face.Face;
import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.FaceSensorConfigurations;
import android.view.Surface;
/**
@@ -167,6 +168,10 @@
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
void registerAuthenticators(in List<FaceSensorPropertiesInternal> hidlSensors);
+ //Register all available face sensors.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+ void registerAuthenticatorsLegacy(in FaceSensorConfigurations faceSensorConfigurations);
+
// Adds a callback which gets called when the service registers all of the face
// authenticators. The callback is automatically removed after it's invoked.
void addAuthenticatorsRegisteredCallback(IFaceAuthenticatorsRegisteredCallback callback);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorKosmos.kt b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.aidl
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorKosmos.kt
copy to core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.aidl
index 94d9a72..ebb05dc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorKosmos.kt
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.aidl
@@ -13,13 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package android.hardware.fingerprint;
-package com.android.systemui.common.domain.interactor
-
-import com.android.systemui.common.ui.data.repository.configurationRepository
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.Kosmos.Fixture
-
-val Kosmos.configurationInteractor by Fixture {
- ConfigurationInteractorImpl(repository = configurationRepository)
-}
+parcelable FingerprintSensorConfigurations;
\ No newline at end of file
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java
new file mode 100644
index 0000000..f214494a
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.fingerprint;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+
+/**
+ * Provides the sensor props for fingerprint sensor, if available.
+ * @hide
+ */
+
+public class FingerprintSensorConfigurations implements Parcelable {
+ private static final String TAG = "FingerprintSensorConfigurations";
+
+ private final Map<String, SensorProps[]> mSensorPropsMap;
+ private final boolean mResetLockoutRequiresHardwareAuthToken;
+
+ public static final Creator<FingerprintSensorConfigurations> CREATOR =
+ new Creator<>() {
+ @Override
+ public FingerprintSensorConfigurations createFromParcel(Parcel in) {
+ return new FingerprintSensorConfigurations(in);
+ }
+
+ @Override
+ public FingerprintSensorConfigurations[] newArray(int size) {
+ return new FingerprintSensorConfigurations[size];
+ }
+ };
+
+ public FingerprintSensorConfigurations(boolean resetLockoutRequiresHardwareAuthToken) {
+ mResetLockoutRequiresHardwareAuthToken = resetLockoutRequiresHardwareAuthToken;
+ mSensorPropsMap = new HashMap<>();
+ }
+
+ /**
+ * Process AIDL instances to extract sensor props and add it to the sensor map.
+ * @param aidlInstances available face AIDL instances
+ * @param getIFingerprint function that provides the daemon for the specific instance
+ */
+ public void addAidlSensors(@NonNull String[] aidlInstances,
+ @NonNull Function<String, IFingerprint> getIFingerprint) {
+ for (String aidlInstance : aidlInstances) {
+ try {
+ final String fqName = IFingerprint.DESCRIPTOR + "/" + aidlInstance;
+ final IFingerprint fp = getIFingerprint.apply(fqName);
+ if (fp != null) {
+ SensorProps[] props = fp.getSensorProps();
+ mSensorPropsMap.put(aidlInstance, props);
+ } else {
+ Log.d(TAG, "IFingerprint null for instance " + aidlInstance);
+ }
+ } catch (RemoteException e) {
+ Log.d(TAG, "Unable to get sensor properties!");
+ }
+ }
+ }
+
+ /**
+ * Parse through HIDL configuration and add it to the sensor map.
+ */
+ public void addHidlSensors(@NonNull String[] hidlConfigStrings,
+ @NonNull Context context) {
+ final List<HidlFingerprintSensorConfig> hidlFingerprintSensorConfigs = new ArrayList<>();
+ for (String hidlConfigString : hidlConfigStrings) {
+ final HidlFingerprintSensorConfig hidlFingerprintSensorConfig =
+ new HidlFingerprintSensorConfig();
+ try {
+ hidlFingerprintSensorConfig.parse(hidlConfigString, context);
+ } catch (Exception e) {
+ Log.e(TAG, "HIDL sensor configuration format is incorrect.");
+ continue;
+ }
+ if (hidlFingerprintSensorConfig.getModality() == TYPE_FINGERPRINT) {
+ hidlFingerprintSensorConfigs.add(hidlFingerprintSensorConfig);
+ }
+ }
+ final String hidlHalInstanceName = "defaultHIDL";
+ mSensorPropsMap.put(hidlHalInstanceName,
+ hidlFingerprintSensorConfigs.toArray(
+ new HidlFingerprintSensorConfig[hidlFingerprintSensorConfigs.size()]));
+ }
+
+ protected FingerprintSensorConfigurations(Parcel in) {
+ mResetLockoutRequiresHardwareAuthToken = in.readByte() != 0;
+ mSensorPropsMap = in.readHashMap(null /* loader */, String.class, SensorProps[].class);
+ }
+
+ /**
+ * Returns true if any fingerprint sensors have been added.
+ */
+ public boolean hasSensorConfigurations() {
+ return mSensorPropsMap.size() > 0;
+ }
+
+ /**
+ * Returns true if there is only a single fingerprint sensor configuration available.
+ */
+ public boolean isSingleSensorConfigurationPresent() {
+ return mSensorPropsMap.size() == 1;
+ }
+
+ /**
+ * Return sensor props for the given instance. If instance is not available,
+ * then null is returned.
+ */
+ @Nullable
+ public Pair<String, SensorProps[]> getSensorPairForInstance(String instance) {
+ if (mSensorPropsMap.containsKey(instance)) {
+ return new Pair<>(instance, mSensorPropsMap.get(instance));
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the first pair of instance and sensor props, which does not correspond to the given
+ * If instance is not available, then null is returned.
+ */
+ @Nullable
+ public Pair<String, SensorProps[]> getSensorPairNotForInstance(String instance) {
+ Optional<String> notAVirtualInstance = mSensorPropsMap.keySet().stream().filter(
+ (instanceName) -> !instanceName.equals(instance)).findFirst();
+ return notAVirtualInstance.map(this::getSensorPairForInstance).orElseGet(
+ this::getSensorPair);
+ }
+
+ /**
+ * Returns the first pair of instance and sensor props that has been added to the map.
+ */
+ @Nullable
+ public Pair<String, SensorProps[]> getSensorPair() {
+ Optional<String> optionalInstance = mSensorPropsMap.keySet().stream().findFirst();
+ return optionalInstance.map(this::getSensorPairForInstance).orElse(null);
+
+ }
+
+ public boolean getResetLockoutRequiresHardwareAuthToken() {
+ return mResetLockoutRequiresHardwareAuthToken;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeByte((byte) (mResetLockoutRequiresHardwareAuthToken ? 1 : 0));
+ dest.writeMap(mSensorPropsMap);
+ }
+}
diff --git a/core/java/android/hardware/fingerprint/HidlFingerprintSensorConfig.java b/core/java/android/hardware/fingerprint/HidlFingerprintSensorConfig.java
new file mode 100644
index 0000000..d481153
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/HidlFingerprintSensorConfig.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.fingerprint;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.common.SensorStrength;
+import android.hardware.biometrics.fingerprint.FingerprintSensorType;
+import android.hardware.biometrics.fingerprint.SensorLocation;
+import android.hardware.biometrics.fingerprint.SensorProps;
+
+import com.android.internal.R;
+import com.android.internal.util.ArrayUtils;
+
+/**
+ * Parse HIDL fingerprint sensor config and map it to SensorProps.aidl to match AIDL.
+ * See core/res/res/values/config.xml config_biometric_sensors
+ * @hide
+ */
+public final class HidlFingerprintSensorConfig extends SensorProps {
+ private int mSensorId;
+ private int mModality;
+ private int mStrength;
+
+ /**
+ * Parse through the config string and map it to SensorProps.aidl.
+ * @throws IllegalArgumentException when config string has unexpected format
+ */
+ public void parse(@NonNull String config, @NonNull Context context)
+ throws IllegalArgumentException {
+ final String[] elems = config.split(":");
+ if (elems.length < 3) {
+ throw new IllegalArgumentException();
+ }
+ mSensorId = Integer.parseInt(elems[0]);
+ mModality = Integer.parseInt(elems[1]);
+ mStrength = Integer.parseInt(elems[2]);
+ mapHidlToAidlSensorConfiguration(context);
+ }
+
+ @BiometricAuthenticator.Modality
+ public int getModality() {
+ return mModality;
+ }
+
+ private void mapHidlToAidlSensorConfiguration(@NonNull Context context) {
+ commonProps = new CommonProps();
+ commonProps.componentInfo = null;
+ commonProps.sensorId = mSensorId;
+ commonProps.sensorStrength = authenticatorStrengthToPropertyStrength(mStrength);
+ commonProps.maxEnrollmentsPerUser = context.getResources().getInteger(
+ R.integer.config_fingerprintMaxTemplatesPerUser);
+ halControlsIllumination = false;
+ sensorLocations = new SensorLocation[1];
+
+ final int[] udfpsProps = context.getResources().getIntArray(
+ com.android.internal.R.array.config_udfps_sensor_props);
+ final boolean isUdfps = !ArrayUtils.isEmpty(udfpsProps);
+ // config_is_powerbutton_fps indicates whether device has a power button fingerprint sensor.
+ final boolean isPowerbuttonFps = context.getResources().getBoolean(
+ R.bool.config_is_powerbutton_fps);
+
+ if (isUdfps) {
+ sensorType = FingerprintSensorType.UNKNOWN;
+ } else if (isPowerbuttonFps) {
+ sensorType = FingerprintSensorType.POWER_BUTTON;
+ } else {
+ sensorType = FingerprintSensorType.REAR;
+ }
+
+ if (isUdfps && udfpsProps.length == 3) {
+ setSensorLocation(udfpsProps[0], udfpsProps[1], udfpsProps[2]);
+ } else {
+ setSensorLocation(540 /* sensorLocationX */, 1636 /* sensorLocationY */,
+ 130 /* sensorRadius */);
+ }
+
+ }
+
+ private void setSensorLocation(int sensorLocationX,
+ int sensorLocationY, int sensorRadius) {
+ sensorLocations[0] = new SensorLocation();
+ sensorLocations[0].display = "";
+ sensorLocations[0].sensorLocationX = sensorLocationX;
+ sensorLocations[0].sensorLocationY = sensorLocationY;
+ sensorLocations[0].sensorRadius = sensorRadius;
+ }
+
+ private byte authenticatorStrengthToPropertyStrength(
+ @BiometricManager.Authenticators.Types int strength) {
+ switch (strength) {
+ case BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE:
+ return SensorStrength.CONVENIENCE;
+ case BiometricManager.Authenticators.BIOMETRIC_WEAK:
+ return SensorStrength.WEAK;
+ case BiometricManager.Authenticators.BIOMETRIC_STRONG:
+ return SensorStrength.STRONG;
+ default:
+ throw new IllegalArgumentException("Unknown strength: " + strength);
+ }
+ }
+}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index f594c00..606b171 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -31,6 +31,7 @@
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
import java.util.List;
/**
@@ -173,6 +174,10 @@
@EnforcePermission("MANAGE_FINGERPRINT")
void removeClientActiveCallback(IFingerprintClientActiveCallback callback);
+ //Register all available fingerprint sensors.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+ void registerAuthenticatorsLegacy(in FingerprintSensorConfigurations fingerprintSensorConfigurations);
+
// Registers all HIDL and AIDL sensors. Only HIDL sensor properties need to be provided, because
// AIDL sensor properties are retrieved directly from the available HALs. If no HIDL HALs exist,
// hidlSensors must be non-null and empty. See AuthService.java
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 4bfff16..7d00b80 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -6911,7 +6911,11 @@
* <td></td>
* </tr>
* </table>
+ *
+ * @deprecated This field may not be well supported by some contacts apps and is discouraged
+ * to use.
*/
+ @Deprecated
public static final class Im implements DataColumnsWithJoins, CommonColumns, ContactCounts {
/**
* This utility class cannot be instantiated
@@ -7721,7 +7725,11 @@
* <td></td>
* </tr>
* </table>
+ *
+ * @deprecated This field may not be well supported by some contacts apps and is discouraged
+ * to use.
*/
+ @Deprecated
public static final class SipAddress implements DataColumnsWithJoins, CommonColumns,
ContactCounts {
/**
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 875031f..23c8393 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -960,8 +960,8 @@
mKeyphraseMetadata = new KeyphraseMetadata(1, mText, fakeSupportedLocales,
AlwaysOnHotwordDetector.RECOGNITION_MODE_VOICE_TRIGGER);
}
+ notifyStateChangedLocked();
}
- notifyStateChanged(availability);
}
/**
@@ -1371,8 +1371,8 @@
mAvailability = STATE_INVALID;
mIsAvailabilityOverriddenByTestApi = false;
+ notifyStateChangedLocked();
}
- notifyStateChanged(STATE_INVALID);
super.destroy();
}
@@ -1402,8 +1402,6 @@
*/
// TODO(b/281608561): remove the enrollment flow from AlwaysOnHotwordDetector
void onSoundModelsChanged() {
- boolean notifyError = false;
-
synchronized (mLock) {
if (mAvailability == STATE_INVALID
|| mAvailability == STATE_HARDWARE_UNAVAILABLE
@@ -1444,9 +1442,6 @@
// calling stopRecognition where there is no started session.
Log.w(TAG, "Failed to stop recognition after enrollment update: code="
+ result);
-
- // Execute a refresh availability task - which should then notify of a change.
- new RefreshAvailabilityTask().execute();
} catch (Exception e) {
Slog.w(TAG, "Failed to stop recognition after enrollment update", e);
if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) {
@@ -1455,14 +1450,14 @@
+ Log.getStackTraceString(e),
FailureSuggestedAction.RECREATE_DETECTOR));
} else {
- notifyError = true;
+ updateAndNotifyStateChangedLocked(STATE_ERROR);
}
+ return;
}
}
- }
- if (notifyError) {
- updateAndNotifyStateChanged(STATE_ERROR);
+ // Execute a refresh availability task - which should then notify of a change.
+ new RefreshAvailabilityTask().execute();
}
}
@@ -1578,11 +1573,10 @@
}
}
- private void updateAndNotifyStateChanged(int availability) {
- synchronized (mLock) {
- updateAvailabilityLocked(availability);
- }
- notifyStateChanged(availability);
+ @GuardedBy("mLock")
+ private void updateAndNotifyStateChangedLocked(int availability) {
+ updateAvailabilityLocked(availability);
+ notifyStateChangedLocked();
}
@GuardedBy("mLock")
@@ -1596,17 +1590,17 @@
}
}
- private void notifyStateChanged(int newAvailability) {
+ @GuardedBy("mLock")
+ private void notifyStateChangedLocked() {
Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED);
- message.arg1 = newAvailability;
+ message.arg1 = mAvailability;
message.sendToTarget();
}
+ @GuardedBy("mLock")
private void sendUnknownFailure(String failureMessage) {
- synchronized (mLock) {
- // update but do not call onAvailabilityChanged callback for STATE_ERROR
- updateAvailabilityLocked(STATE_ERROR);
- }
+ // update but do not call onAvailabilityChanged callback for STATE_ERROR
+ updateAvailabilityLocked(STATE_ERROR);
Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE, failureMessage).sendToTarget();
}
@@ -1822,17 +1816,19 @@
availability = STATE_KEYPHRASE_UNENROLLED;
}
}
+ updateAndNotifyStateChangedLocked(availability);
}
- updateAndNotifyStateChanged(availability);
} catch (Exception e) {
// Any exception here not caught will crash the process because AsyncTask does not
// bubble up the exceptions to the client app, so we must propagate it to the app.
Slog.w(TAG, "Failed to refresh availability", e);
- if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) {
- sendUnknownFailure(
- "Failed to refresh availability: " + Log.getStackTraceString(e));
- } else {
- updateAndNotifyStateChanged(STATE_ERROR);
+ synchronized (mLock) {
+ if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) {
+ sendUnknownFailure(
+ "Failed to refresh availability: " + Log.getStackTraceString(e));
+ } else {
+ updateAndNotifyStateChangedLocked(STATE_ERROR);
+ }
}
}
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index b7d9705..adc54f5 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -94,7 +94,9 @@
*/
public void updateState(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) {
- mInitializationDelegate.updateState(options, sharedMemory);
+ synchronized (mInitializationDelegate.getLock()) {
+ mInitializationDelegate.updateState(options, sharedMemory);
+ }
}
@@ -116,18 +118,21 @@
if (DEBUG) {
Slog.i(TAG, "#startRecognition");
}
- // check if the detector is active with the initialization delegate
- mInitializationDelegate.startRecognition();
+ synchronized (mInitializationDelegate.getLock()) {
+ // check if the detector is active with the initialization delegate
+ mInitializationDelegate.startRecognition();
- try {
- mManagerService.startPerceiving(new BinderCallback(mExecutor, mCallback));
- } catch (SecurityException e) {
- Slog.e(TAG, "startRecognition failed: " + e);
- return false;
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ try {
+ mManagerService.startPerceiving(new BinderCallback(
+ mExecutor, mCallback, mInitializationDelegate.getLock()));
+ } catch (SecurityException e) {
+ Slog.e(TAG, "startRecognition failed: " + e);
+ return false;
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return true;
}
- return true;
}
/**
@@ -140,15 +145,17 @@
if (DEBUG) {
Slog.i(TAG, "#stopRecognition");
}
- // check if the detector is active with the initialization delegate
- mInitializationDelegate.startRecognition();
+ synchronized (mInitializationDelegate.getLock()) {
+ // check if the detector is active with the initialization delegate
+ mInitializationDelegate.stopRecognition();
- try {
- mManagerService.stopPerceiving();
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ try {
+ mManagerService.stopPerceiving();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return true;
}
- return true;
}
/**
@@ -160,12 +167,16 @@
if (DEBUG) {
Slog.i(TAG, "#destroy");
}
- mInitializationDelegate.destroy();
+ synchronized (mInitializationDelegate.getLock()) {
+ mInitializationDelegate.destroy();
+ }
}
/** @hide */
public void dump(String prefix, PrintWriter pw) {
- // TODO: implement this
+ synchronized (mInitializationDelegate.getLock()) {
+ mInitializationDelegate.dump(prefix, pw);
+ }
}
/** @hide */
@@ -175,7 +186,9 @@
/** @hide */
void registerOnDestroyListener(Consumer<AbstractDetector> onDestroyListener) {
- mInitializationDelegate.registerOnDestroyListener(onDestroyListener);
+ synchronized (mInitializationDelegate.getLock()) {
+ mInitializationDelegate.registerOnDestroyListener(onDestroyListener);
+ }
}
/**
@@ -282,6 +295,15 @@
public boolean isUsingSandboxedDetectionService() {
return true;
}
+
+ @Override
+ public void dump(String prefix, PrintWriter pw) {
+ // No-op
+ }
+
+ private Object getLock() {
+ return mLock;
+ }
}
private static class BinderCallback
@@ -289,31 +311,43 @@
private final Executor mExecutor;
private final VisualQueryDetector.Callback mCallback;
- BinderCallback(Executor executor, VisualQueryDetector.Callback callback) {
+ private final Object mLock;
+
+ BinderCallback(Executor executor, VisualQueryDetector.Callback callback, Object lock) {
this.mExecutor = executor;
this.mCallback = callback;
+ this.mLock = lock;
}
/** Called when the detected result is valid. */
@Override
public void onQueryDetected(@NonNull String partialQuery) {
Slog.v(TAG, "BinderCallback#onQueryDetected");
- Binder.withCleanCallingIdentity(() -> mExecutor.execute(
- () -> mCallback.onQueryDetected(partialQuery)));
+ Binder.withCleanCallingIdentity(() -> {
+ synchronized (mLock) {
+ mCallback.onQueryDetected(partialQuery);
+ }
+ });
}
@Override
public void onQueryFinished() {
Slog.v(TAG, "BinderCallback#onQueryFinished");
- Binder.withCleanCallingIdentity(() -> mExecutor.execute(
- () -> mCallback.onQueryFinished()));
+ Binder.withCleanCallingIdentity(() -> {
+ synchronized (mLock) {
+ mCallback.onQueryFinished();
+ }
+ });
}
@Override
public void onQueryRejected() {
Slog.v(TAG, "BinderCallback#onQueryRejected");
- Binder.withCleanCallingIdentity(() -> mExecutor.execute(
- () -> mCallback.onQueryRejected()));
+ Binder.withCleanCallingIdentity(() -> {
+ synchronized (mLock) {
+ mCallback.onQueryRejected();
+ }
+ });
}
/** Called when the detection fails due to an error. */
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 31d759e..18080e4 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -287,6 +287,16 @@
}
/**
+ * Called when a display hdcp levels change event is received.
+ *
+ * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
+ * @param connectedLevel the new connected HDCP level
+ * @param maxLevel the maximum HDCP level
+ */
+ public void onHdcpLevelsChanged(long physicalDisplayId, int connectedLevel, int maxLevel) {
+ }
+
+ /**
* Represents a mapping between a UID and an override frame rate
*/
public static class FrameRateOverride {
@@ -374,4 +384,11 @@
onFrameRateOverridesChanged(timestampNanos, physicalDisplayId, overrides);
}
+ // Called from native code.
+ @SuppressWarnings("unused")
+ private void dispatchHdcpLevelsChanged(long physicalDisplayId, int connectedLevel,
+ int maxLevel) {
+ onHdcpLevelsChanged(physicalDisplayId, connectedLevel, maxLevel);
+ }
+
}
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index 0077dab..4b5595f 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -36,3 +36,10 @@
bug: "316139088"
is_fixed_read_only: true
}
+
+flag {
+ name: "user_min_aspect_ratio_app_default"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether the API PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT is available"
+ bug: "310816437"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 07beb11..52ad49a 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -15,6 +15,14 @@
}
flag {
+ name: "enforce_edge_to_edge"
+ namespace: "windowing_frontend"
+ description: "Make app go edge-to-edge when targeting SDK level 35 or greater"
+ bug: "309578419"
+ is_fixed_read_only: true
+}
+
+flag {
name: "defer_display_updates"
namespace: "windowing_frontend"
description: "Feature flag for deferring DisplayManager updates to WindowManager if Shell transition is running"
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 201b23c..31910ac 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -169,20 +169,12 @@
private final static int DEFAULT_BACKGROUND_FADE_DURATION_MS = 300;
/**
- * Makes navigation bar color transparent by default if the target SDK is
- * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or above.
- */
- @ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
- private static final long NAV_BAR_COLOR_DEFAULT_TRANSPARENT = 232195501L;
-
- /**
* Make app go edge-to-edge by default if the target SDK is
* {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or above.
*/
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
- private static final long EDGE_TO_EDGE_BY_DEFAULT = 309578419;
+ private static final long ENFORCE_EDGE_TO_EDGE = 309578419;
private static final int CUSTOM_TITLE_COMPATIBLE_FEATURES = DEFAULT_FEATURES |
(1 << FEATURE_CUSTOM_TITLE) |
@@ -193,9 +185,9 @@
private static final Transition USE_DEFAULT_TRANSITION = new TransitionSet();
/**
- * Since which target SDK version this window should be edge-to-edge by default.
+ * Since which target SDK version this window is enforced to go edge-to-edge.
*/
- private static final int DEFAULT_EDGE_TO_EDGE_SDK_VERSION =
+ private static final int ENFORCE_EDGE_TO_EDGE_SDK_VERSION =
SystemProperties.getInt("persist.wm.debug.default_e2e_since_sdk", Integer.MAX_VALUE);
/**
@@ -376,7 +368,7 @@
boolean mDecorFitsSystemWindows = true;
@VisibleForTesting
- public final boolean mDefaultEdgeToEdge;
+ public final boolean mEdgeToEdgeEnforced;
private final ProxyOnBackInvokedDispatcher mProxyOnBackInvokedDispatcher;
@@ -396,11 +388,11 @@
mProxyOnBackInvokedDispatcher = new ProxyOnBackInvokedDispatcher(context);
mAllowFloatingWindowsFillScreen = context.getResources().getBoolean(
com.android.internal.R.bool.config_allowFloatingWindowsFillScreen);
- mDefaultEdgeToEdge =
- context.getApplicationInfo().targetSdkVersion >= DEFAULT_EDGE_TO_EDGE_SDK_VERSION
- || (CompatChanges.isChangeEnabled(EDGE_TO_EDGE_BY_DEFAULT)
- && Flags.edgeToEdgeByDefault());
- if (mDefaultEdgeToEdge) {
+ mEdgeToEdgeEnforced =
+ context.getApplicationInfo().targetSdkVersion >= ENFORCE_EDGE_TO_EDGE_SDK_VERSION
+ || (CompatChanges.isChangeEnabled(ENFORCE_EDGE_TO_EDGE)
+ && Flags.enforceEdgeToEdge());
+ if (mEdgeToEdgeEnforced) {
mDecorFitsSystemWindows = false;
}
}
@@ -2472,7 +2464,7 @@
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
params.setFitInsetsSides(0);
params.setFitInsetsTypes(0);
- if (mDefaultEdgeToEdge) {
+ if (mEdgeToEdgeEnforced) {
params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
}
}
@@ -2562,7 +2554,7 @@
final int statusBarColor = a.getColor(R.styleable.Window_statusBarColor,
statusBarDefaultColor);
- mStatusBarColor = statusBarColor == statusBarDefaultColor && !mDefaultEdgeToEdge
+ mStatusBarColor = statusBarColor == statusBarDefaultColor && !mEdgeToEdgeEnforced
? statusBarCompatibleColor
: statusBarColor;
}
@@ -2574,9 +2566,7 @@
mNavigationBarColor =
navBarColor == navBarDefaultColor
- && !mDefaultEdgeToEdge
- && !(CompatChanges.isChangeEnabled(NAV_BAR_COLOR_DEFAULT_TRANSPARENT)
- && Flags.navBarTransparentByDefault())
+ && !mEdgeToEdgeEnforced
&& !context.getResources().getBoolean(
R.bool.config_navBarDefaultTransparent)
? navBarCompatibleColor
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index c24d21d..17aad43 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -653,6 +653,8 @@
sizeof("-Xps-save-resolved-classes-delay-ms:")-1 + PROPERTY_VALUE_MAX];
char profileMinSavePeriodOptsBuf[sizeof("-Xps-min-save-period-ms:")-1 + PROPERTY_VALUE_MAX];
char profileMinFirstSaveOptsBuf[sizeof("-Xps-min-first-save-ms:") - 1 + PROPERTY_VALUE_MAX];
+ char profileInlineCacheThresholdOptsBuf[
+ sizeof("-Xps-inline-cache-threshold:") - 1 + PROPERTY_VALUE_MAX];
char madviseWillNeedFileSizeVdex[
sizeof("-XMadviseWillNeedVdexFileSize:")-1 + PROPERTY_VALUE_MAX];
char madviseWillNeedFileSizeOdex[
@@ -898,6 +900,9 @@
parseRuntimeOption("dalvik.vm.ps-min-first-save-ms", profileMinFirstSaveOptsBuf,
"-Xps-min-first-save-ms:");
+ parseRuntimeOption("dalvik.vm.ps-inline-cache-threshold", profileInlineCacheThresholdOptsBuf,
+ "-Xps-inline-cache-threshold:");
+
property_get("ro.config.low_ram", propBuf, "");
if (strcmp(propBuf, "true") == 0) {
addOption("-XX:LowMemoryMode");
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index fef8ad7..f007cc5 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -41,6 +41,7 @@
jmethodID dispatchHotplugConnectionError;
jmethodID dispatchModeChanged;
jmethodID dispatchFrameRateOverrides;
+ jmethodID dispatchHdcpLevelsChanged;
struct {
jclass clazz;
@@ -96,6 +97,8 @@
void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
std::vector<FrameRateOverride> overrides) override;
void dispatchNullEvent(nsecs_t timestamp, PhysicalDisplayId displayId) override {}
+ void dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int connectedLevel,
+ int maxLevel) override;
};
NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak,
@@ -294,6 +297,22 @@
mMessageQueue->raiseAndClearException(env, "dispatchModeChanged");
}
+void NativeDisplayEventReceiver::dispatchHdcpLevelsChanged(PhysicalDisplayId displayId,
+ int connectedLevel, int maxLevel) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+
+ ScopedLocalRef<jobject> receiverObj(env, GetReferent(env, mReceiverWeakGlobal));
+ if (receiverObj.get()) {
+ ALOGV("receiver %p ~ Invoking hdcp levels changed handler.", this);
+ env->CallVoidMethod(receiverObj.get(),
+ gDisplayEventReceiverClassInfo.dispatchHdcpLevelsChanged,
+ displayId.value, connectedLevel, maxLevel);
+ ALOGV("receiver %p ~ Returned from hdcp levels changed handler.", this);
+ }
+
+ mMessageQueue->raiseAndClearException(env, "dispatchHdcpLevelsChanged");
+}
+
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject vsyncEventDataWeak,
jobject messageQueueObj, jint vsyncSource, jint eventRegistration,
jlong layerHandle) {
@@ -385,6 +404,9 @@
GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz,
"dispatchFrameRateOverrides",
"(JJ[Landroid/view/DisplayEventReceiver$FrameRateOverride;)V");
+ gDisplayEventReceiverClassInfo.dispatchHdcpLevelsChanged =
+ GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchHdcpLevelsChanged",
+ "(JII)V");
jclass frameRateOverrideClazz =
FindClassOrDie(env, "android/view/DisplayEventReceiver$FrameRateOverride");
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index c5889ba..75cfba0 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -429,6 +429,7 @@
optional string base_dir = 1;
optional string res_dir = 2;
optional string data_dir = 3;
+ optional int32 targetSdkVersion = 4;
}
optional AppInfo appinfo = 8;
optional ProcessRecordProto app = 9;
diff --git a/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java b/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java
new file mode 100644
index 0000000..da3a465
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/face/FaceSensorConfigurationsTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.face;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.SensorProps;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.function.Function;
+
+@Presubmit
+@SmallTest
+public class FaceSensorConfigurationsTest {
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+ @Mock
+ private Context mContext;
+ @Mock
+ private Resources mResources;
+ @Mock
+ private IFace mFace;
+ @Mock
+ private Function<String, IFace> mGetIFace;
+
+ private final String[] mAidlInstances = new String[]{"default", "virtual"};
+ private String[] mHidlConfigStrings = new String[]{"0:2:15", "0:8:15"};
+ private FaceSensorConfigurations mFaceSensorConfigurations;
+
+ @Before
+ public void setUp() throws RemoteException {
+ when(mGetIFace.apply(anyString())).thenReturn(mFace);
+ when(mFace.getSensorProps()).thenReturn(new SensorProps[]{});
+ when(mContext.getResources()).thenReturn(mResources);
+ }
+
+ @Test
+ public void testAidlInstanceSensorProps() {
+ mFaceSensorConfigurations = new FaceSensorConfigurations(false);
+ mFaceSensorConfigurations.addAidlConfigs(mAidlInstances, mGetIFace);
+
+ assertThat(mFaceSensorConfigurations.hasSensorConfigurations()).isTrue();
+ assertThat(!mFaceSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+ assertThat(mFaceSensorConfigurations.getResetLockoutRequiresChallenge())
+ .isFalse();
+ }
+
+ @Test
+ public void testHidlConfigStrings() {
+ mFaceSensorConfigurations = new FaceSensorConfigurations(true);
+ mFaceSensorConfigurations.addHidlConfigs(mHidlConfigStrings, mContext);
+
+ assertThat(mFaceSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+ assertThat(mFaceSensorConfigurations.getResetLockoutRequiresChallenge())
+ .isTrue();
+ }
+
+ @Test
+ public void testHidlConfigStrings_incorrectFormat() {
+ mHidlConfigStrings = new String[]{"0:8:15", "0:2", "0:face:15"};
+ mFaceSensorConfigurations = new FaceSensorConfigurations(true);
+ mFaceSensorConfigurations.addHidlConfigs(mHidlConfigStrings, mContext);
+
+ assertThat(mFaceSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+ assertThat(mFaceSensorConfigurations.getResetLockoutRequiresChallenge())
+ .isTrue();
+ }
+}
diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java
new file mode 100644
index 0000000..613089c
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintSensorConfigurationsTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.fingerprint;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.function.Function;
+
+
+@Presubmit
+@SmallTest
+public class FingerprintSensorConfigurationsTest {
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+ @Mock
+ private Context mContext;
+ @Mock
+ private Resources mResources;
+ @Mock
+ private IFingerprint mFingerprint;
+ @Mock
+ private Function<String, IFingerprint> mGetIFingerprint;
+
+ private final String[] mAidlInstances = new String[]{"default", "virtual"};
+ private String[] mHidlConfigStrings = new String[]{"0:2:15", "0:8:15"};
+ private FingerprintSensorConfigurations mFingerprintSensorConfigurations;
+
+ @Before
+ public void setUp() throws RemoteException {
+ when(mGetIFingerprint.apply(anyString())).thenReturn(mFingerprint);
+ when(mFingerprint.getSensorProps()).thenReturn(new SensorProps[]{});
+ when(mContext.getResources()).thenReturn(mResources);
+ }
+
+ @Test
+ public void testAidlInstanceSensorProps() {
+ mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
+ true /* resetLockoutRequiresHardwareAuthToken */);
+ mFingerprintSensorConfigurations.addAidlSensors(mAidlInstances, mGetIFingerprint);
+
+ assertThat(mFingerprintSensorConfigurations.hasSensorConfigurations()).isTrue();
+ assertThat(!mFingerprintSensorConfigurations.isSingleSensorConfigurationPresent())
+ .isTrue();
+ assertThat(mFingerprintSensorConfigurations.getResetLockoutRequiresHardwareAuthToken())
+ .isTrue();
+ }
+
+ @Test
+ public void testHidlConfigStrings() {
+ mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
+ false /* resetLockoutRequiresHardwareAuthToken */);
+ mFingerprintSensorConfigurations.addHidlSensors(mHidlConfigStrings, mContext);
+
+ assertThat(mFingerprintSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+ assertThat(mFingerprintSensorConfigurations.getResetLockoutRequiresHardwareAuthToken())
+ .isFalse();
+ }
+
+ @Test
+ public void testHidlConfigStrings_incorrectFormat() {
+ mHidlConfigStrings = new String[]{"0:8:15", "0:2", "0:fingerprint:15"};
+ mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
+ false /* resetLockoutRequiresHardwareAuthToken */);
+ mFingerprintSensorConfigurations.addHidlSensors(mHidlConfigStrings, mContext);
+
+ assertThat(mFingerprintSensorConfigurations.isSingleSensorConfigurationPresent()).isTrue();
+ assertThat(mFingerprintSensorConfigurations.getResetLockoutRequiresHardwareAuthToken())
+ .isFalse();
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
index 6bdc06a..de55b07 100644
--- a/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
+++ b/core/tests/coretests/src/com/android/internal/policy/PhoneWindowTest.java
@@ -63,7 +63,7 @@
createPhoneWindowWithTheme(R.style.LayoutInDisplayCutoutModeUnset);
installDecor();
- if (mPhoneWindow.mDefaultEdgeToEdge && !mPhoneWindow.isFloating()) {
+ if (mPhoneWindow.mEdgeToEdgeEnforced && !mPhoneWindow.isFloating()) {
assertThat(mPhoneWindow.getAttributes().layoutInDisplayCutoutMode,
is(LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS));
} else {
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index aaddf0e..742d5a2 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -379,12 +379,6 @@
"group": "WM_DEBUG_IME",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
- "-1770075711": {
- "message": "Adding window client %s that is dead, aborting.",
- "level": "WARN",
- "group": "WM_ERROR",
- "at": "com\/android\/server\/wm\/WindowManagerService.java"
- },
"-1768557332": {
"message": "removeWallpaperAnimation()",
"level": "DEBUG",
@@ -3253,6 +3247,12 @@
"group": "WM_DEBUG_LOCKTASK",
"at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
},
+ "723575093": {
+ "message": "Attempted to add window with a client %s that is dead. Aborting.",
+ "level": "WARN",
+ "group": "WM_ERROR",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"726205185": {
"message": "Moving to DESTROYED: %s (destroy skipped)",
"level": "VERBOSE",
@@ -4237,12 +4237,6 @@
"group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "1720696061": {
- "message": "Adding window to Display that has been removed.",
- "level": "WARN",
- "group": "WM_ERROR",
- "at": "com\/android\/server\/wm\/WindowManagerService.java"
- },
"1730300180": {
"message": "PendingStartTransaction found",
"level": "VERBOSE",
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
index 562d20d..7fb959a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -20,6 +20,7 @@
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.AlertDialog;
+import android.app.Flags;
import android.app.NotificationManager;
import android.content.Context;
import android.content.DialogInterface;
@@ -143,9 +144,16 @@
Slog.d(TAG, "Invalid manual condition: " + tag.condition);
}
// always triggers priority-only dnd with chosen condition
- mNotificationManager.setZenMode(
- Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
- getRealConditionId(tag.condition), TAG);
+ if (Flags.modesApi()) {
+ mNotificationManager.setZenMode(
+ Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ getRealConditionId(tag.condition), TAG,
+ /* fromUser= */ true);
+ } else {
+ mNotificationManager.setZenMode(
+ Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ getRealConditionId(tag.condition), TAG);
+ }
}
});
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
index 9084aa2..e83b9bc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
@@ -22,6 +22,7 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
+import android.multiuser.Flags;
import android.net.Uri;
import android.util.Log;
import android.widget.ImageView;
@@ -59,6 +60,9 @@
private static final String IMAGES_DIR = "multi_user";
private static final String NEW_USER_PHOTO_FILE_NAME = "NewUserPhoto.png";
+ private static final String AVATAR_PICKER_ACTION = "com.android.avatarpicker"
+ + ".FULL_SCREEN_ACTIVITY";
+
private final Activity mActivity;
private final ActivityStarter mActivityStarter;
private final ImageView mImageView;
@@ -105,7 +109,6 @@
onPhotoCropped(data.getData());
return true;
}
-
}
return false;
}
@@ -115,7 +118,13 @@
}
private void showAvatarPicker() {
- Intent intent = new Intent(mImageView.getContext(), AvatarPickerActivity.class);
+ Intent intent;
+ if (Flags.avatarSync()) {
+ intent = new Intent(AVATAR_PICKER_ACTION);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ } else {
+ intent = new Intent(mImageView.getContext(), AvatarPickerActivity.class);
+ }
intent.putExtra(AvatarPickerActivity.EXTRA_FILE_AUTHORITY, mFileAuthority);
mActivityStarter.startActivityForResult(intent, REQUEST_CODE_PICK_AVATAR);
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 5dc1079..9d9b0a9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -235,18 +235,16 @@
}
override fun ContentDrawScope.draw() {
- if (shouldDrawElement(layoutImpl, scene, element)) {
- val drawScale = getDrawScale(layoutImpl, element, scene, sceneValues)
- if (drawScale == Scale.Default) {
- drawContent()
- } else {
- scale(
- drawScale.scaleX,
- drawScale.scaleY,
- if (drawScale.pivot.isUnspecified) center else drawScale.pivot,
- ) {
- this@draw.drawContent()
- }
+ val drawScale = getDrawScale(layoutImpl, element, scene, sceneValues)
+ if (drawScale == Scale.Default) {
+ drawContent()
+ } else {
+ scale(
+ drawScale.scaleX,
+ drawScale.scaleY,
+ if (drawScale.pivot.isUnspecified) center else drawScale.pivot,
+ ) {
+ this@draw.drawContent()
}
}
}
@@ -516,13 +514,18 @@
with(placementScope) {
// Update the offset (relative to the SceneTransitionLayout) this element has in this scene
// when idle.
- val coords = coordinates!!
+ val coords = coordinates ?: error("Element ${element.key} does not have any coordinates")
val targetOffsetInScene = lookaheadScopeCoordinates.localLookaheadPositionOf(coords)
if (targetOffsetInScene != sceneValues.targetOffset) {
// TODO(b/290930950): Better handle when this changes to avoid instant offset jumps.
sceneValues.targetOffset = targetOffsetInScene
}
+ // No need to place the element in this scene if we don't want to draw it anyways.
+ if (!shouldDrawElement(layoutImpl, scene, element)) {
+ return
+ }
+
val currentOffset = lookaheadScopeCoordinates.localPositionOf(coords, Offset.Zero)
val lastSharedValues = element.lastSharedValues
val lastValues = sceneValues.lastValues
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 439dc00..d332910 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -116,7 +116,13 @@
toSceneContent = {
Box(Modifier.size(layoutSize)) {
// Shared element.
- Element(TestElements.Foo, elementSize, elementOffset)
+ Element(
+ TestElements.Foo,
+ elementSize,
+ elementOffset,
+ onLayout = { fooLayouts++ },
+ onPlacement = { fooPlacements++ },
+ )
}
},
transition = {
@@ -127,21 +133,25 @@
scaleSize(TestElements.Bar, width = 1f, height = 1f)
},
) {
- var numberOfLayoutsAfterOneAnimationFrame = 0
- var numberOfPlacementsAfterOneAnimationFrame = 0
+ var fooLayoutsAfterOneAnimationFrame = 0
+ var fooPlacementsAfterOneAnimationFrame = 0
+ var barLayoutsAfterOneAnimationFrame = 0
+ var barPlacementsAfterOneAnimationFrame = 0
fun assertNumberOfLayoutsAndPlacements() {
- assertThat(fooLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame)
- assertThat(fooPlacements).isEqualTo(numberOfPlacementsAfterOneAnimationFrame)
- assertThat(barLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame)
- assertThat(barPlacements).isEqualTo(numberOfPlacementsAfterOneAnimationFrame)
+ assertThat(fooLayouts).isEqualTo(fooLayoutsAfterOneAnimationFrame)
+ assertThat(fooPlacements).isEqualTo(fooPlacementsAfterOneAnimationFrame)
+ assertThat(barLayouts).isEqualTo(barLayoutsAfterOneAnimationFrame)
+ assertThat(barPlacements).isEqualTo(barPlacementsAfterOneAnimationFrame)
}
at(16) {
// Capture the number of layouts and placements that happened after 1 animation
// frame.
- numberOfLayoutsAfterOneAnimationFrame = fooLayouts
- numberOfPlacementsAfterOneAnimationFrame = fooPlacements
+ fooLayoutsAfterOneAnimationFrame = fooLayouts
+ fooPlacementsAfterOneAnimationFrame = fooPlacements
+ barLayoutsAfterOneAnimationFrame = barLayouts
+ barPlacementsAfterOneAnimationFrame = barPlacements
}
repeat(nFrames - 2) { i ->
// Ensure that all animation frames (except the final one) don't relayout or replace
@@ -187,7 +197,13 @@
toSceneContent = {
Box(Modifier.size(layoutSize)) {
// Shared element.
- Element(TestElements.Foo, elementSize, offset = 20.dp)
+ Element(
+ TestElements.Foo,
+ elementSize,
+ offset = 20.dp,
+ onLayout = { fooLayouts++ },
+ onPlacement = { fooPlacements++ },
+ )
}
},
transition = {
@@ -198,25 +214,30 @@
scaleSize(TestElements.Bar, width = 1f, height = 1f)
},
) {
- var numberOfLayoutsAfterOneAnimationFrame = 0
- var lastNumberOfPlacements = 0
+ var fooLayoutsAfterOneAnimationFrame = 0
+ var barLayoutsAfterOneAnimationFrame = 0
+ var lastFooPlacements = 0
+ var lastBarPlacements = 0
fun assertNumberOfLayoutsAndPlacements() {
// The number of layouts have not changed.
- assertThat(fooLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame)
- assertThat(barLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame)
+ assertThat(fooLayouts).isEqualTo(fooLayoutsAfterOneAnimationFrame)
+ assertThat(barLayouts).isEqualTo(barLayoutsAfterOneAnimationFrame)
// The number of placements have increased.
- assertThat(fooPlacements).isGreaterThan(lastNumberOfPlacements)
- assertThat(barPlacements).isGreaterThan(lastNumberOfPlacements)
- lastNumberOfPlacements = fooPlacements
+ assertThat(fooPlacements).isGreaterThan(lastFooPlacements)
+ assertThat(barPlacements).isGreaterThan(lastBarPlacements)
+ lastFooPlacements = fooPlacements
+ lastBarPlacements = barPlacements
}
at(16) {
// Capture the number of layouts and placements that happened after 1 animation
// frame.
- numberOfLayoutsAfterOneAnimationFrame = fooLayouts
- lastNumberOfPlacements = fooPlacements
+ fooLayoutsAfterOneAnimationFrame = fooLayouts
+ barLayoutsAfterOneAnimationFrame = barLayouts
+ lastFooPlacements = fooPlacements
+ lastBarPlacements = barPlacements
}
repeat(nFrames - 2) { i ->
// Ensure that all animation frames (except the final one) only replaced the
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
index 83af630..3cd65cd 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -30,6 +30,7 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.hasParent
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createComposeRule
@@ -92,15 +93,15 @@
}
at(32) {
- // In the middle of the transition, there are 2 copies of the counter: the previous
- // one from scene A (equal to 3) and the new one from scene B (equal to 0).
+ // In the middle of the transition, 2 copies of the counter are composed but only
+ // the one in scene B is placed/drawn.
rule
.onNode(
hasText("count: 3") and
hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneA))
)
- .assertIsDisplayed()
- .assertSizeIsEqualTo(75.dp, 75.dp)
+ .assertExists()
+ .assertIsNotDisplayed()
rule
.onNode(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 321cf63..ebbd500 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -40,9 +40,7 @@
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
import androidx.compose.ui.test.assertWidthIsEqualTo
import androidx.compose.ui.test.junit4.createAndroidComposeRule
-import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onChild
-import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.unit.Dp
@@ -251,9 +249,9 @@
// Advance to the middle of the animation.
rule.mainClock.advanceTimeBy(TestTransitionDuration / 2)
- // We need to use onAllNodesWithTag().onFirst() here given that shared elements are
- // composed and laid out in both scenes (but drawn only in one).
- sharedFoo = rule.onAllNodesWithTag(TestElements.Foo.testTag).onFirst()
+ // Foo is shared between Scene A and Scene B, and is therefore placed/drawn in Scene B given
+ // that B has a higher zIndex than A.
+ sharedFoo = rule.onNode(isElement(TestElements.Foo, TestScenes.SceneB))
// In scene B, foo is at the top start (x = 0, y = 0) of the layout and has a size of
// 100.dp. We pause at the middle of the transition, so it should now be 75.dp given that we
@@ -287,7 +285,7 @@
val expectedLeft = 0.dp
val expectedSize = 100.dp + (150.dp - 100.dp) * interpolatedProgress
- sharedFoo = rule.onAllNodesWithTag(TestElements.Foo.testTag).onFirst()
+ sharedFoo = rule.onNode(isElement(TestElements.Foo, TestScenes.SceneC))
assertThat((layoutState.transitionState as TransitionState.Transition).progress)
.isEqualTo(interpolatedProgress)
sharedFoo.assertWidthIsEqualTo(expectedSize)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
index e94eff3..8001f41 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt
@@ -23,6 +23,7 @@
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertPositionInRootIsEqualTo
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.unit.dp
@@ -33,7 +34,6 @@
import com.android.compose.animation.scene.inScene
import com.android.compose.animation.scene.testTransition
import com.android.compose.test.assertSizeIsEqualTo
-import com.android.compose.test.onEach
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -63,28 +63,34 @@
onElement(TestElements.Foo).assertSizeIsEqualTo(20.dp, 80.dp)
}
at(0) {
- onSharedElement(TestElements.Foo).onEach {
- assertPositionInRootIsEqualTo(10.dp, 50.dp)
- assertSizeIsEqualTo(20.dp, 80.dp)
- }
+ // Shared elements are by default placed and drawn only in the scene with highest
+ // zIndex.
+ onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed()
+
+ onElement(TestElements.Foo, TestScenes.SceneB)
+ .assertPositionInRootIsEqualTo(10.dp, 50.dp)
+ .assertSizeIsEqualTo(20.dp, 80.dp)
}
at(16) {
- onSharedElement(TestElements.Foo).onEach {
- assertPositionInRootIsEqualTo(20.dp, 55.dp)
- assertSizeIsEqualTo(17.5.dp, 70.dp)
- }
+ onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed()
+
+ onElement(TestElements.Foo, TestScenes.SceneB)
+ .assertPositionInRootIsEqualTo(20.dp, 55.dp)
+ .assertSizeIsEqualTo(17.5.dp, 70.dp)
}
at(32) {
- onSharedElement(TestElements.Foo).onEach {
- assertPositionInRootIsEqualTo(30.dp, 60.dp)
- assertSizeIsEqualTo(15.dp, 60.dp)
- }
+ onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed()
+
+ onElement(TestElements.Foo, TestScenes.SceneB)
+ .assertPositionInRootIsEqualTo(30.dp, 60.dp)
+ .assertSizeIsEqualTo(15.dp, 60.dp)
}
at(48) {
- onSharedElement(TestElements.Foo).onEach {
- assertPositionInRootIsEqualTo(40.dp, 65.dp)
- assertSizeIsEqualTo(12.5.dp, 50.dp)
- }
+ onElement(TestElements.Foo, TestScenes.SceneA).assertIsNotDisplayed()
+
+ onElement(TestElements.Foo, TestScenes.SceneB)
+ .assertPositionInRootIsEqualTo(40.dp, 65.dp)
+ .assertSizeIsEqualTo(12.5.dp, 50.dp)
}
after {
onElement(TestElements.Foo).assertPositionInRootIsEqualTo(50.dp, 70.dp)
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt
new file mode 100644
index 0000000..e743c78
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.hasParent
+import androidx.compose.ui.test.hasTestTag
+
+/** A [SemanticsMatcher] that matches [element], optionally restricted to scene [scene]. */
+fun isElement(element: ElementKey, scene: SceneKey? = null): SemanticsMatcher {
+ return if (scene == null) {
+ hasTestTag(element.testTag)
+ } else {
+ hasTestTag(element.testTag) and hasParent(hasTestTag(scene.testTag))
+ }
+}
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index 06de296..6d6d575 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -21,13 +21,8 @@
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.SemanticsNodeInteraction
-import androidx.compose.ui.test.SemanticsNodeInteractionCollection
-import androidx.compose.ui.test.hasParent
-import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.junit4.ComposeContentTestRule
-import androidx.compose.ui.test.onAllNodesWithTag
@DslMarker annotation class TransitionTestDsl
@@ -62,23 +57,15 @@
@TransitionTestDsl
interface TransitionTestAssertionScope {
- fun isElement(element: ElementKey, scene: SceneKey? = null): SemanticsMatcher
-
/**
* Assert on [element].
*
* Note that presence/value assertions on the returned [SemanticsNodeInteraction] will fail if 0
* or more than 1 elements matched [element]. If you need to assert on a shared element that
- * will be present multiple times in the layout during transitions, either specify the [scene]
- * in which you are matching or use [onSharedElement] instead.
+ * will be present multiple times in the layout during transitions, specify the [scene] in which
+ * you are matching.
*/
fun onElement(element: ElementKey, scene: SceneKey? = null): SemanticsNodeInteraction
-
- /**
- * Assert on a shared [element]. This will throw if [element] is not shared and present only in
- * one scene during a transition.
- */
- fun onSharedElement(element: ElementKey): SemanticsNodeInteractionCollection
}
/**
@@ -131,29 +118,12 @@
val test = transitionTest(builder)
val assertionScope =
object : TransitionTestAssertionScope {
- override fun isElement(element: ElementKey, scene: SceneKey?): SemanticsMatcher {
- return if (scene == null) {
- hasTestTag(element.testTag)
- } else {
- hasTestTag(element.testTag) and hasParent(hasTestTag(scene.testTag))
- }
- }
-
override fun onElement(
element: ElementKey,
scene: SceneKey?
): SemanticsNodeInteraction {
return onNode(isElement(element, scene))
}
-
- override fun onSharedElement(element: ElementKey): SemanticsNodeInteractionCollection {
- val interaction = onAllNodesWithTag(element.testTag)
- val matches = interaction.fetchSemanticsNodes(atLeastOneRootRequired = false).size
- if (matches < 2) {
- error("Element $element is not shared ($matches matches)")
- }
- return interaction
- }
}
var currentScene by mutableStateOf(from)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
index 0870122..adf4fc6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -32,18 +32,15 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
-import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class AlternateBouncerInteractorTest : SysuiTestCase() {
@@ -167,7 +164,6 @@
}
@Test
- @Ignore("b/287599719")
fun canShowAlternateBouncerForFingerprint_rearFps() {
mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
initializeUnderTest()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
new file mode 100644
index 0000000..9daf186
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AlternateBouncerToAodTransitionViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+ private val biometricSettingsRepository = kosmos.biometricSettingsRepository
+ private val underTest = kosmos.alternateBouncerToAodTransitionViewModel
+
+ @Test
+ fun deviceEntryParentViewAppear() =
+ testScope.runTest {
+ fingerprintPropertyRepository.setProperties(
+ sensorId = 0,
+ strength = SensorStrength.STRONG,
+ sensorType = FingerprintSensorType.UDFPS_OPTICAL,
+ sensorLocations = emptyMap(),
+ )
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ val values by collectValues(underTest.deviceEntryParentViewAlpha)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ step(0f, TransitionState.STARTED),
+ step(0f),
+ step(0.1f),
+ step(0.2f),
+ step(0.3f),
+ step(1f),
+ ),
+ testScope,
+ )
+
+ values.forEach { assertThat(it).isEqualTo(1f) }
+ }
+
+ @Test
+ fun deviceEntryBackgroundViewDisappear() =
+ testScope.runTest {
+ val values by collectValues(underTest.deviceEntryBackgroundViewAlpha)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ step(0f, TransitionState.STARTED),
+ step(0f),
+ step(0.1f),
+ step(0.2f),
+ step(0.3f),
+ step(1f),
+ ),
+ testScope,
+ )
+
+ assertThat(values.size).isEqualTo(6)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
+ }
+
+ private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = AOD,
+ value = value,
+ transitionState = state,
+ ownerName = "AlternateBouncerToAodTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
new file mode 100644
index 0000000..3f7e0df
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AlternateBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
+ val kosmos =
+ testKosmos().apply {
+ fakeFeatureFlagsClassic.apply {
+ set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
+ set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ }
+ }
+ private val testScope = kosmos.testScope
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val underTest = kosmos.alternateBouncerToGoneTransitionViewModel
+
+ @Test
+ fun deviceEntryParentViewDisappear() =
+ testScope.runTest {
+ val values by collectValues(underTest.deviceEntryParentViewAlpha)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ step(0f, TransitionState.STARTED),
+ step(0f),
+ step(0.1f),
+ step(0.2f),
+ step(0.3f),
+ step(1f),
+ ),
+ testScope,
+ )
+
+ values.forEach { assertThat(it).isEqualTo(0f) }
+ }
+
+ private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.ALTERNATE_BOUNCER,
+ to = GONE,
+ value = value,
+ transitionState = state,
+ ownerName = "AlternateBouncerToGoneTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
index cf076c5..a84b9fa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
@@ -24,16 +24,19 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.external.TileServiceKey
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
import com.android.systemui.qs.tiles.impl.custom.commons.copy
+import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
+import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade
+import com.android.systemui.qs.tiles.impl.custom.tileSpec
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -44,194 +47,260 @@
@OptIn(ExperimentalCoroutinesApi::class)
class CustomTileRepositoryTest : SysuiTestCase() {
- private val testScope = TestScope()
-
- private val persister = FakeCustomTileStatePersister()
-
+ private val kosmos = Kosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
private val underTest: CustomTileRepository =
- CustomTileRepositoryImpl(
- TileSpec.create(TEST_COMPONENT),
- persister,
- testScope.testScheduler,
- )
+ with(kosmos) {
+ CustomTileRepositoryImpl(
+ tileSpec,
+ customTileStatePersister,
+ packageManagerAdapterFacade.packageManagerAdapter,
+ testScope.testScheduler,
+ )
+ }
@Test
fun persistableTileIsRestoredForUser() =
- testScope.runTest {
- persister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
- persister.persistState(TEST_TILE_KEY_2, TEST_TILE_2)
+ with(kosmos) {
+ testScope.runTest {
+ customTileStatePersister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
+ customTileStatePersister.persistState(TEST_TILE_KEY_2, TEST_TILE_2)
- underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
- runCurrent()
+ underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
+ runCurrent()
- assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
- assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
+ assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+ assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
+ }
}
@Test
fun notPersistableTileIsNotRestored() =
- testScope.runTest {
- persister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
- val tiles = collectValues(underTest.getTiles(TEST_USER_1))
+ with(kosmos) {
+ testScope.runTest {
+ customTileStatePersister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
+ val tiles = collectValues(underTest.getTiles(TEST_USER_1))
- underTest.restoreForTheUserIfNeeded(TEST_USER_1, false)
- runCurrent()
+ underTest.restoreForTheUserIfNeeded(TEST_USER_1, false)
+ runCurrent()
- assertThat(tiles()).isEmpty()
+ assertThat(tiles()).isEmpty()
+ }
}
@Test
fun emptyPersistedStateIsHandled() =
- testScope.runTest {
- val tiles = collectValues(underTest.getTiles(TEST_USER_1))
+ with(kosmos) {
+ testScope.runTest {
+ val tiles = collectValues(underTest.getTiles(TEST_USER_1))
- underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
- runCurrent()
+ underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
+ runCurrent()
- assertThat(tiles()).isEmpty()
+ assertThat(tiles()).isEmpty()
+ }
}
@Test
fun updatingWithPersistableTilePersists() =
- testScope.runTest {
- underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+ runCurrent()
- assertThat(persister.readState(TEST_TILE_KEY_1)).isEqualTo(TEST_TILE_1)
+ assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1))
+ .isEqualTo(TEST_TILE_1)
+ }
}
@Test
fun updatingWithNotPersistableTileDoesntPersist() =
- testScope.runTest {
- underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, false)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, false)
+ runCurrent()
- assertThat(persister.readState(TEST_TILE_KEY_1)).isNull()
+ assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1)).isNull()
+ }
}
@Test
fun updateWithTileEmits() =
- testScope.runTest {
- underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+ runCurrent()
- assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
- assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+ assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
+ assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+ }
}
@Test
fun updatingPeristableWithDefaultsPersists() =
- testScope.runTest {
- underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+ runCurrent()
- assertThat(persister.readState(TEST_TILE_KEY_1)).isEqualTo(TEST_TILE_1)
+ assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1))
+ .isEqualTo(TEST_TILE_1)
+ }
}
@Test
fun updatingNotPersistableWithDefaultsDoesntPersist() =
- testScope.runTest {
- underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, false)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, false)
+ runCurrent()
- assertThat(persister.readState(TEST_TILE_KEY_1)).isNull()
+ assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1)).isNull()
+ }
}
@Test
fun updatingPeristableWithErrorDefaultsDoesntPersist() =
- testScope.runTest {
- underTest.updateWithDefaults(TEST_USER_1, CustomTileDefaults.Error, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithDefaults(TEST_USER_1, CustomTileDefaults.Error, true)
+ runCurrent()
- assertThat(persister.readState(TEST_TILE_KEY_1)).isNull()
+ assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1)).isNull()
+ }
}
@Test
fun updateWithDefaultsEmits() =
- testScope.runTest {
- underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+ runCurrent()
- assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
- assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+ assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
+ assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+ }
}
@Test
fun getTileForAnotherUserReturnsNull() =
- testScope.runTest {
- underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+ runCurrent()
- assertThat(underTest.getTile(TEST_USER_2)).isNull()
+ assertThat(underTest.getTile(TEST_USER_2)).isNull()
+ }
}
@Test
fun getTilesForAnotherUserEmpty() =
- testScope.runTest {
- val tiles = collectValues(underTest.getTiles(TEST_USER_2))
+ with(kosmos) {
+ testScope.runTest {
+ val tiles = collectValues(underTest.getTiles(TEST_USER_2))
- underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
- runCurrent()
+ underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+ runCurrent()
- assertThat(tiles()).isEmpty()
+ assertThat(tiles()).isEmpty()
+ }
}
@Test
fun updatingWithTileForTheSameUserAddsData() =
- testScope.runTest {
- underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+ runCurrent()
- underTest.updateWithTile(TEST_USER_1, Tile().apply { subtitle = "test_subtitle" }, true)
- runCurrent()
+ underTest.updateWithTile(
+ TEST_USER_1,
+ Tile().apply { subtitle = "test_subtitle" },
+ true
+ )
+ runCurrent()
- val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
- assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
- assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
+ val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
+ assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
+ assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
+ }
}
@Test
fun updatingWithTileForAnotherUserOverridesTile() =
- testScope.runTest {
- underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
+ runCurrent()
- val tiles = collectValues(underTest.getTiles(TEST_USER_2))
- underTest.updateWithTile(TEST_USER_2, TEST_TILE_2, true)
- runCurrent()
+ val tiles = collectValues(underTest.getTiles(TEST_USER_2))
+ underTest.updateWithTile(TEST_USER_2, TEST_TILE_2, true)
+ runCurrent()
- assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
- assertThat(tiles()).hasSize(1)
- assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
+ assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
+ assertThat(tiles()).hasSize(1)
+ assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
+ }
}
@Test
fun updatingWithDefaultsForTheSameUserAddsData() =
- testScope.runTest {
- underTest.updateWithTile(TEST_USER_1, Tile().apply { subtitle = "test_subtitle" }, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithTile(
+ TEST_USER_1,
+ Tile().apply { subtitle = "test_subtitle" },
+ true
+ )
+ runCurrent()
- underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
- runCurrent()
+ underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+ runCurrent()
- val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
- assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
- assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
+ val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
+ assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
+ assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
+ }
}
@Test
fun updatingWithDefaultsForAnotherUserOverridesTile() =
- testScope.runTest {
- underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
- runCurrent()
+ with(kosmos) {
+ testScope.runTest {
+ underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
+ runCurrent()
- val tiles = collectValues(underTest.getTiles(TEST_USER_2))
- underTest.updateWithDefaults(TEST_USER_2, TEST_DEFAULTS_2, true)
- runCurrent()
+ val tiles = collectValues(underTest.getTiles(TEST_USER_2))
+ underTest.updateWithDefaults(TEST_USER_2, TEST_DEFAULTS_2, true)
+ runCurrent()
- assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
- assertThat(tiles()).hasSize(1)
- assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
+ assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
+ assertThat(tiles()).hasSize(1)
+ assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
+ }
+ }
+
+ @Test
+ fun isActiveFollowsPackageManagerAdapter() =
+ with(kosmos) {
+ testScope.runTest {
+ packageManagerAdapterFacade.setIsActive(false)
+ assertThat(underTest.isTileActive()).isFalse()
+
+ packageManagerAdapterFacade.setIsActive(true)
+ assertThat(underTest.isTileActive()).isTrue()
+ }
+ }
+
+ @Test
+ fun isToggleableFollowsPackageManagerAdapter() =
+ with(kosmos) {
+ testScope.runTest {
+ packageManagerAdapterFacade.setIsToggleable(false)
+ assertThat(underTest.isTileToggleable()).isFalse()
+
+ packageManagerAdapterFacade.setIsToggleable(true)
+ assertThat(underTest.isTileToggleable()).isTrue()
+ }
}
private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
index eebb145..90779cb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
@@ -25,157 +25,159 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.external.TileServiceKey
-import com.android.systemui.qs.external.TileServiceManager
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
+import com.android.systemui.qs.tiles.impl.custom.customTileDefaultsRepository
+import com.android.systemui.qs.tiles.impl.custom.customTileRepository
+import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
-import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileDefaultsRepository
-import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileRepository
-import com.android.systemui.util.mockito.whenever
+import com.android.systemui.qs.tiles.impl.custom.tileSpec
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class CustomTileInteractorTest : SysuiTestCase() {
- @Mock private lateinit var tileServiceManager: TileServiceManager
+ private val kosmos = Kosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
- private val testScope = TestScope()
-
- private val defaultsRepository = FakeCustomTileDefaultsRepository()
- private val customTileStatePersister = FakeCustomTileStatePersister()
- private val customTileRepository =
- FakeCustomTileRepository(
- TEST_TILE_SPEC,
- customTileStatePersister,
- testScope.testScheduler,
- )
-
- private lateinit var underTest: CustomTileInteractor
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
-
- underTest =
+ private val underTest: CustomTileInteractor =
+ with(kosmos) {
CustomTileInteractor(
- TEST_USER,
- defaultsRepository,
+ customTileDefaultsRepository,
customTileRepository,
- tileServiceManager,
testScope.backgroundScope,
testScope.testScheduler,
)
- }
+ }
@Test
fun activeTileIsAvailableAfterRestored() =
- testScope.runTest {
- whenever(tileServiceManager.isActiveTile).thenReturn(true)
- customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
- )
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(true)
+ customTileStatePersister.persistState(
+ TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+ TEST_TILE,
+ )
- underTest.init()
+ underTest.initForUser(TEST_USER)
- assertThat(underTest.tile).isEqualTo(TEST_TILE)
- assertThat(underTest.tiles.first()).isEqualTo(TEST_TILE)
+ assertThat(underTest.getTile(TEST_USER)).isEqualTo(TEST_TILE)
+ assertThat(underTest.getTiles(TEST_USER).first()).isEqualTo(TEST_TILE)
+ }
}
@Test
fun notActiveTileIsAvailableAfterUpdated() =
- testScope.runTest {
- whenever(tileServiceManager.isActiveTile).thenReturn(false)
- customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
- )
- val tiles = collectValues(underTest.tiles)
- val initJob = launch { underTest.init() }
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(false)
+ customTileStatePersister.persistState(
+ TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+ TEST_TILE,
+ )
+ val tiles = collectValues(underTest.getTiles(TEST_USER))
+ val initJob = launch { underTest.initForUser(TEST_USER) }
- underTest.updateTile(TEST_TILE)
- runCurrent()
- initJob.join()
+ underTest.updateTile(TEST_TILE)
+ runCurrent()
+ initJob.join()
- assertThat(tiles()).hasSize(1)
- assertThat(tiles().last()).isEqualTo(TEST_TILE)
+ assertThat(tiles()).hasSize(1)
+ assertThat(tiles().last()).isEqualTo(TEST_TILE)
+ }
}
@Test
fun notActiveTileIsAvailableAfterDefaultsUpdated() =
- testScope.runTest {
- whenever(tileServiceManager.isActiveTile).thenReturn(false)
- customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
- )
- val tiles = collectValues(underTest.tiles)
- val initJob = launch { underTest.init() }
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(false)
+ customTileStatePersister.persistState(
+ TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+ TEST_TILE,
+ )
+ val tiles = collectValues(underTest.getTiles(TEST_USER))
+ val initJob = launch { underTest.initForUser(TEST_USER) }
- defaultsRepository.putDefaults(TEST_USER, TEST_COMPONENT, TEST_DEFAULTS)
- defaultsRepository.requestNewDefaults(TEST_USER, TEST_COMPONENT)
- runCurrent()
- initJob.join()
+ customTileDefaultsRepository.putDefaults(TEST_USER, TEST_COMPONENT, TEST_DEFAULTS)
+ customTileDefaultsRepository.requestNewDefaults(TEST_USER, TEST_COMPONENT)
+ runCurrent()
+ initJob.join()
- assertThat(tiles()).hasSize(1)
- assertThat(tiles().last()).isEqualTo(TEST_TILE)
+ assertThat(tiles()).hasSize(1)
+ assertThat(tiles().last()).isEqualTo(TEST_TILE)
+ }
}
@Test(expected = IllegalStateException::class)
- fun getTileBeforeInitThrows() = testScope.runTest { underTest.tile }
+ fun getTileBeforeInitThrows() =
+ with(kosmos) { testScope.runTest { underTest.getTile(TEST_USER) } }
@Test
fun initSuspendsForActiveTileNotRestoredAndNotUpdated() =
- testScope.runTest {
- whenever(tileServiceManager.isActiveTile).thenReturn(true)
- val tiles = collectValues(underTest.tiles)
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(true)
+ val tiles = collectValues(underTest.getTiles(TEST_USER))
- val initJob = backgroundScope.launch { underTest.init() }
- advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
+ val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER) }
+ advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
- // Is still suspended
- assertThat(initJob.isActive).isTrue()
- assertThat(tiles()).isEmpty()
+ // Is still suspended
+ assertThat(initJob.isActive).isTrue()
+ assertThat(tiles()).isEmpty()
+ }
}
@Test
fun initSuspendedForNotActiveTileWithoutUpdates() =
- testScope.runTest {
- whenever(tileServiceManager.isActiveTile).thenReturn(false)
- customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
- )
- val tiles = collectValues(underTest.tiles)
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(false)
+ customTileStatePersister.persistState(
+ TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
+ TEST_TILE,
+ )
+ val tiles = collectValues(underTest.getTiles(TEST_USER))
- val initJob = backgroundScope.launch { underTest.init() }
- advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
+ val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER) }
+ advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
- // Is still suspended
- assertThat(initJob.isActive).isTrue()
- assertThat(tiles()).isEmpty()
+ // Is still suspended
+ assertThat(initJob.isActive).isTrue()
+ assertThat(tiles()).isEmpty()
+ }
}
+ @Test
+ fun toggleableFollowsTheRepository() {
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileToggleable(false)
+ assertThat(underTest.isTileToggleable()).isFalse()
+
+ customTileRepository.setTileToggleable(true)
+ assertThat(underTest.isTileToggleable()).isTrue()
+ }
+ }
+ }
+
private companion object {
val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
- val TEST_TILE_SPEC = TileSpec.create(TEST_COMPONENT)
val TEST_USER = UserHandle.of(1)!!
val TEST_TILE =
Tile().apply {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationContentDescriptionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationContentDescriptionTest.kt
new file mode 100644
index 0000000..f67c70c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationContentDescriptionTest.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification
+
+import android.app.Notification
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NotificationContentDescriptionTest : SysuiTestCase() {
+
+ private val TITLE = "this is a title"
+ private val TEXT = "this is text"
+ private val TICKER = "this is a ticker"
+
+ @Test
+ fun notificationWithAllDifferentFields_descriptionIsTitle() {
+ val n = createNotification(TITLE, TEXT, TICKER)
+ val description = contentDescForNotification(context, n)
+ assertThat(description).isEqualTo(createDescriptionText(n, TITLE))
+ }
+
+ @Test
+ fun notificationWithAllDifferentFields_titleMatchesAppName_descriptionIsText() {
+ val n = createNotification(getTestAppName(), TEXT, TICKER)
+ val description = contentDescForNotification(context, n)
+ assertThat(description).isEqualTo(createDescriptionText(n, TEXT))
+ }
+
+ @Test
+ fun notificationWithAllDifferentFields_titleMatchesAppNameNoText_descriptionIsTicker() {
+ val n = createNotification(getTestAppName(), null, TICKER)
+ val description = contentDescForNotification(context, n)
+ assertThat(description).isEqualTo(createDescriptionText(n, TICKER))
+ }
+
+ @Test
+ fun notificationWithAllDifferentFields_titleMatchesAppNameNoTextNoTicker_descriptionEmpty() {
+ val appName = getTestAppName()
+ val n = createNotification(appName, null, null)
+ val description = contentDescForNotification(context, n)
+ assertThat(description).isEqualTo(createDescriptionText(n, ""))
+ }
+
+ @Test
+ fun nullNotification_descriptionIsAppName() {
+ val description = contentDescForNotification(context, null)
+ assertThat(description).isEqualTo(createDescriptionText(null, ""))
+ }
+
+ private fun createNotification(
+ title: String? = null,
+ text: String? = null,
+ ticker: String? = null
+ ): Notification =
+ Notification.Builder(context)
+ .setContentTitle(title)
+ .setContentText(text)
+ .setTicker(ticker)
+ .build()
+
+ private fun getTestAppName(): String {
+ return getAppName(createNotification("", "", ""))
+ }
+
+ private fun getAppName(n: Notification?) =
+ n?.let {
+ val builder = Notification.Builder.recoverBuilder(context, it)
+ builder.loadHeaderAppName()
+ }
+ ?: ""
+
+ private fun createDescriptionText(n: Notification?, desc: String?): String {
+ val appName = getAppName(n)
+ return context.getString(R.string.accessibility_desc_notification_icon, appName, desc)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 2ecf01f..1237347 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -16,7 +16,8 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
-import android.platform.test.flag.junit.SetFlagsRule
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION
@@ -60,8 +61,6 @@
private lateinit var underTest: WifiViewModel
- private val setFlagsRule = SetFlagsRule()
-
@Mock private lateinit var tableLogBuffer: TableLogBuffer
@Mock private lateinit var connectivityConstants: ConnectivityConstants
@Mock private lateinit var wifiConstants: WifiConstants
@@ -187,11 +186,9 @@
}
@Test
+ @DisableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
fun activity_nullSsid_outputsFalse_staticFlagOff() =
testScope.runTest {
- // GIVEN flag is disabled
- setFlagsRule.disableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
-
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
@@ -214,11 +211,9 @@
}
@Test
+ @EnableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
fun activity_nullSsid_outputsFalse_staticFlagOn() =
testScope.runTest {
- // GIVEN flag is enabled
- setFlagsRule.enableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
-
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
@@ -371,11 +366,9 @@
}
@Test
+ @DisableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
fun activityContainer_inAndOutFalse_outputsTrue_staticFlagOff() =
testScope.runTest {
- // GIVEN the flag is off
- setFlagsRule.disableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
-
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
@@ -389,11 +382,9 @@
}
@Test
+ @EnableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
fun activityContainer_inAndOutFalse_outputsTrue_staticFlagOn() =
testScope.runTest {
- // GIVEN the flag is on
- setFlagsRule.enableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
-
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
diff --git a/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml b/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml
index 7b90270..50d5607 100644
--- a/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml
+++ b/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml
@@ -15,7 +15,7 @@
~
-->
-<FrameLayout
+<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:id="@+id/alternate_bouncer"
@@ -32,4 +32,4 @@
android:importantForAccessibility="no"
sysui:ignoreRightInset="true"
/>
-</FrameLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 87c31c8..dca84b9 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -116,6 +116,10 @@
android:inflatedId="@+id/multi_shade"
android:layout="@layout/multi_shade" />
+ <include layout="@layout/alternate_bouncer"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
<com.android.systemui.biometrics.AuthRippleView
android:id="@+id/auth_ripple"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index deed6c8..90cc1fb 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -966,14 +966,6 @@
<bool name="config_edgeToEdgeBottomSheetDialog">true</bool>
<!--
- Whether the scene container framework is enabled.
-
- The scene container framework is a newer (2023) way to organize the various "scenes" between the
- bouncer, lockscreen, shade, and quick settings.
- -->
- <bool name="config_sceneContainerFrameworkEnabled">true</bool>
-
- <!--
Time in milliseconds the user has to touch the side FPS sensor to successfully authenticate
TODO(b/302332976) Get this value from the HAL if they can provide an API for it.
-->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b947801..0267454 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1905,4 +1905,7 @@
<!-- Bouncer user switcher margins -->
<dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
<dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
+
+ <!-- UDFPS view attributes -->
+ <dimen name="udfps_icon_size">6mm</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index cf63cc7..6cb2d45 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -259,4 +259,7 @@
<item type="id" name="device_entry_icon_view" />
<item type="id" name="device_entry_icon_fg" />
<item type="id" name="device_entry_icon_bg" />
+
+ <!--Id for the device-entry UDFPS icon that lives in the alternate bouncer. -->
+ <item type="id" name="alternate_bouncer_udfps_icon_view" />
</resources>
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
index 8ad32b4..a00cdfa 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
@@ -30,6 +30,10 @@
fun isUdfps(): Boolean {
return (this == UDFPS_OPTICAL) || (this == UDFPS_ULTRASONIC)
}
+
+ fun isPowerButton(): Boolean {
+ return this == POWER_BUTTON
+ }
}
/** Convert [this] to corresponding [FingerprintSensorType] */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
index 38043b4..f4a2811 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics.domain.interactor
+import android.content.Context
import android.view.MotionEvent
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
@@ -23,12 +24,15 @@
import com.android.systemui.common.coroutine.ConflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** Encapsulates business logic for interacting with the UDFPS overlay. */
@@ -36,10 +40,12 @@
class UdfpsOverlayInteractor
@Inject
constructor(
+ @Application context: Context,
private val authController: AuthController,
private val selectedUserInteractor: SelectedUserInteractor,
@Application scope: CoroutineScope
) {
+ private val iconSize: Int = context.resources.getDimensionPixelSize(R.dimen.udfps_icon_size)
/** Whether a touch is within the under-display fingerprint sensor area */
fun isTouchWithinUdfpsArea(ev: MotionEvent): Boolean {
@@ -70,6 +76,14 @@
}
.stateIn(scope, started = SharingStarted.Eagerly, initialValue = UdfpsOverlayParams())
+ // Padding between the fingerprint icon and its bounding box in pixels.
+ val iconPadding: Flow<Int> =
+ udfpsOverlayParams.map { params ->
+ val sensorWidth = params.nativeSensorBounds.right - params.nativeSensorBounds.left
+ val nativePadding = (sensorWidth - iconSize) / 2
+ (nativePadding * params.scaleFactor).toInt()
+ }
+
companion object {
private const val TAG = "UdfpsOverlayInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt
index c19ea19..7106674 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics.ui.viewmodel
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.hideAffordancesRequest
@@ -26,22 +27,30 @@
/**
* View model for the UdfpsTouchOverlay for when UDFPS is being requested for device entry. Handles
- * touches as long as the device entry views are visible.
+ * touches as long as the device entry view is visible (the lockscreen or the alternate bouncer
+ * view).
*/
@ExperimentalCoroutinesApi
class DeviceEntryUdfpsTouchOverlayViewModel
@Inject
constructor(
deviceEntryIconViewModel: DeviceEntryIconViewModel,
+ alternateBouncerInteractor: AlternateBouncerInteractor,
systemUIDialogManager: SystemUIDialogManager,
) : UdfpsTouchOverlayViewModel {
- // TODO (b/305234447): AlternateBouncer showing overrides sysuiDialogHideAffordancesRequest
- override val shouldHandleTouches: Flow<Boolean> =
+ private val showingUdfpsAffordance: Flow<Boolean> =
combine(
deviceEntryIconViewModel.deviceEntryViewAlpha,
+ alternateBouncerInteractor.isVisible,
+ ) { deviceEntryViewAlpha, alternateBouncerVisible ->
+ deviceEntryViewAlpha > ALLOW_TOUCH_ALPHA_THRESHOLD || alternateBouncerVisible
+ }
+ override val shouldHandleTouches: Flow<Boolean> =
+ combine(
+ showingUdfpsAffordance,
systemUIDialogManager.hideAffordancesRequest,
- ) { deviceEntryViewAlpha, dialogRequestingHideAffordances ->
- deviceEntryViewAlpha > ALLOW_TOUCH_ALPHA_THRESHOLD && !dialogRequestingHideAffordances
+ ) { showingUdfpsAffordance, dialogRequestingHideAffordances ->
+ showingUdfpsAffordance && !dialogRequestingHideAffordances
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
index 84f7dd5..af32eb5 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -29,9 +29,10 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/** Encapsulates business logic for interacting with the lock-screen alternate bouncer. */
@SysUISingleton
@@ -52,19 +53,13 @@
private val alternateBouncerUiAvailableFromSource: HashSet<String> = HashSet()
private val alternateBouncerSupported: StateFlow<Boolean> =
if (DeviceEntryUdfpsRefactor.isEnabled) {
- // The device entry udfps refactor doesn't currently support the alternate bouncer.
- // TODO: Re-enable when b/287599719 is ready.
- MutableStateFlow(false).asStateFlow()
- // fingerprintPropertyRepository.sensorType
- // .map { sensorType ->
- // sensorType.isUdfps() || sensorType ==
- // FingerprintSensorType.POWER_BUTTON
- // }
- // .stateIn(
- // scope = scope,
- // started = SharingStarted.Eagerly,
- // initialValue = false,
- // )
+ fingerprintPropertyRepository.sensorType
+ .map { sensorType -> sensorType.isUdfps() || sensorType.isPowerButton() }
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
} else {
bouncerRepository.alternateBouncerUIAvailable
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt
deleted file mode 100644
index 7be2eaf..0000000
--- a/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.common.domain
-
-import com.android.systemui.common.domain.interactor.ConfigurationInteractor
-import com.android.systemui.common.domain.interactor.ConfigurationInteractorImpl
-import dagger.Binds
-import dagger.Module
-
-@Module
-abstract class CommonDomainLayerModule {
- @Binds abstract fun bindInteractor(impl: ConfigurationInteractorImpl): ConfigurationInteractor
-}
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/ConfigurationInteractor.kt
deleted file mode 100644
index 89053d1..0000000
--- a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/ConfigurationInteractor.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package com.android.systemui.common.domain.interactor
-
-import android.content.res.Configuration
-import android.graphics.Rect
-import android.view.Surface
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
-
-interface ConfigurationInteractor {
- /**
- * Returns screen size adjusted to rotation, so returned screen sizes are stable across all
- * rotations, could be useful if you need to react to screen resize (e.g. fold/unfold on
- * foldable devices)
- */
- val naturalMaxBounds: Flow<Rect>
-}
-
-class ConfigurationInteractorImpl
-@Inject
-constructor(private val repository: ConfigurationRepository) : ConfigurationInteractor {
-
- override val naturalMaxBounds: Flow<Rect>
- get() = repository.configurationValues.map { it.naturalScreenBounds }.distinctUntilChanged()
-
- /**
- * Returns screen size adjusted to rotation, so returned screen size is stable across all
- * rotations
- */
- private val Configuration.naturalScreenBounds: Rect
- get() {
- val rotation = windowConfiguration.displayRotation
- val maxBounds = windowConfiguration.maxBounds
- return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
- Rect(0, 0, maxBounds.width(), maxBounds.height())
- } else {
- Rect(0, 0, maxBounds.height(), maxBounds.width())
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
index 3648f3b..1353985 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
@@ -17,17 +17,45 @@
package com.android.systemui.common.ui.domain.interactor
+import android.content.res.Configuration
+import android.graphics.Rect
+import android.view.Surface
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onStart
/** Business logic related to configuration changes. */
@SysUISingleton
class ConfigurationInteractor @Inject constructor(private val repository: ConfigurationRepository) {
+ /**
+ * Returns screen size adjusted to rotation, so returned screen size is stable across all
+ * rotations
+ */
+ private val Configuration.naturalScreenBounds: Rect
+ get() {
+ val rotation = windowConfiguration.displayRotation
+ val maxBounds = windowConfiguration.maxBounds
+ return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
+ Rect(0, 0, maxBounds.width(), maxBounds.height())
+ } else {
+ Rect(0, 0, maxBounds.height(), maxBounds.width())
+ }
+ }
+
+ /**
+ * Returns screen size adjusted to rotation, so returned screen sizes are stable across all
+ * rotations, could be useful if you need to react to screen resize (e.g. fold/unfold on
+ * foldable devices)
+ */
+ val naturalMaxBounds: Flow<Rect> =
+ repository.configurationValues.map { it.naturalScreenBounds }.distinctUntilChanged()
+
/** Given [resourceId], emit the dimension pixel size on config change */
fun dimensionPixelSize(resourceId: Int): Flow<Int> {
return onAnyConfigurationChange.mapLatest { repository.getDimensionPixelSize(resourceId) }
@@ -43,4 +71,7 @@
/** Emit an event on any config change */
val onAnyConfigurationChange: Flow<Unit> =
repository.onAnyConfigurationChange.onStart { emit(Unit) }
+
+ /** Emits the current resolution scaling factor */
+ val scaleForResolution: Flow<Float> = repository.scaleForResolution
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index ca8268d..a25c788 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -43,7 +43,6 @@
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
import com.android.systemui.common.data.CommonDataLayerModule;
-import com.android.systemui.common.domain.CommonDomainLayerModule;
import com.android.systemui.communal.dagger.CommunalModule;
import com.android.systemui.complication.dagger.ComplicationComponent;
import com.android.systemui.controls.dagger.ControlsModule;
@@ -179,7 +178,6 @@
ClockRegistryModule.class,
CommunalModule.class,
CommonDataLayerModule.class,
- CommonDomainLayerModule.class,
ConnectivityModule.class,
ControlsModule.class,
CoroutinesModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 5ec51f4..1540423 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -25,7 +25,9 @@
import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
@@ -36,20 +38,28 @@
class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, handler: Handler) :
FlagDependenciesBase(featureFlags, handler) {
override fun defineDependencies() {
+ // Internal notification backend dependencies
+ crossAppPoliteNotifications dependsOn politeNotifications
+ vibrateWhileUnlockedToken dependsOn politeNotifications
+
+ // Internal notification frontend dependencies
NotificationsLiveDataStoreRefactor.token dependsOn NotificationIconContainerRefactor.token
FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token
- val keyguardBottomAreaRefactor = FlagToken(
- FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor())
+ // Internal keyguard dependencies
KeyguardShadeMigrationNssl.token dependsOn keyguardBottomAreaRefactor
- val crossAppPoliteNotifToken =
- FlagToken(FLAG_CROSS_APP_POLITE_NOTIFICATIONS, crossAppPoliteNotifications())
- val politeNotifToken = FlagToken(FLAG_POLITE_NOTIFICATIONS, politeNotifications())
- crossAppPoliteNotifToken dependsOn politeNotifToken
-
- val vibrateWhileUnlockedToken =
- FlagToken(FLAG_VIBRATE_WHILE_UNLOCKED, vibrateWhileUnlocked())
- vibrateWhileUnlockedToken dependsOn politeNotifToken
+ // SceneContainer dependencies
+ SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta }
+ SceneContainerFlag.getMainStaticFlag() dependsOn MIGRATE_KEYGUARD_STATUS_BAR_VIEW
}
+
+ private inline val politeNotifications
+ get() = FlagToken(FLAG_POLITE_NOTIFICATIONS, politeNotifications())
+ private inline val crossAppPoliteNotifications
+ get() = FlagToken(FLAG_CROSS_APP_POLITE_NOTIFICATIONS, crossAppPoliteNotifications())
+ private inline val vibrateWhileUnlockedToken: FlagToken
+ get() = FlagToken(FLAG_VIBRATE_WHILE_UNLOCKED, vibrateWhileUnlocked())
+ private inline val keyguardBottomAreaRefactor
+ get() = FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor())
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 7e360cf..52f2759 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -141,5 +141,6 @@
companion object {
val TRANSITION_DURATION_MS = 300.milliseconds
val TO_GONE_DURATION = 500.milliseconds
+ val TO_AOD_DURATION = TRANSITION_DURATION_MS
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
new file mode 100644
index 0000000..d12d193
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.content.res.ColorStateList
+import android.view.View
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+object AlternateBouncerUdfpsViewBinder {
+
+ /** Updates UI for the UDFPS icon on the alternate bouncer. */
+ @JvmStatic
+ fun bind(
+ view: DeviceEntryIconView,
+ viewModel: AlternateBouncerUdfpsIconViewModel,
+ ) {
+ if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) {
+ return
+ }
+ val fgIconView = view.iconView
+ val bgView = view.bgView
+
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.accessibilityDelegateHint.collect { hint ->
+ view.accessibilityHintType = hint
+ }
+ }
+ }
+
+ fgIconView.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.fgViewModel.collect { fgViewModel ->
+ fgIconView.setImageState(
+ view.getIconState(fgViewModel.type, fgViewModel.useAodVariant),
+ /* merge */ false
+ )
+ fgIconView.imageTintList = ColorStateList.valueOf(fgViewModel.tint)
+ fgIconView.setPadding(
+ fgViewModel.padding,
+ fgViewModel.padding,
+ fgViewModel.padding,
+ fgViewModel.padding,
+ )
+ }
+ }
+ }
+
+ bgView.visibility = View.VISIBLE
+ bgView.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.bgViewModel.collect { bgViewModel ->
+ bgView.alpha = bgViewModel.alpha
+ bgView.imageTintList = ColorStateList.valueOf(bgViewModel.tint)
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index d1d323f..78906ac 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -17,47 +17,49 @@
package com.android.systemui.keyguard.ui.binder
import android.view.View
-import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.classifier.Classifier
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.scrim.ScrimView
-import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.gesture.TapGestureDetector
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
-/** Binds the alternate bouncer view to its view-model. */
+/**
+ * Binds the alternate bouncer view to its view-model.
+ *
+ * For devices that support UDFPS, this includes a UDFPS view.
+ */
@ExperimentalCoroutinesApi
object AlternateBouncerViewBinder {
/** Binds the view to the view-model, continuing to update the former based on the latter. */
@JvmStatic
fun bind(
- view: ViewGroup,
+ view: ConstraintLayout,
viewModel: AlternateBouncerViewModel,
- scope: CoroutineScope,
- notificationShadeWindowController: NotificationShadeWindowController,
falsingManager: FalsingManager,
swipeUpAnywhereGestureHandler: SwipeUpAnywhereGestureHandler,
tapGestureDetector: TapGestureDetector,
+ alternateBouncerUdfpsIconViewModel: AlternateBouncerUdfpsIconViewModel,
) {
- if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) return
- scope.launch {
- // forcePluginOpen is necessary to show over occluded apps.
- // This cannot be tied to the view's lifecycle because setting this allows the view
- // to be started in the first place.
- viewModel.forcePluginOpen.collect {
- notificationShadeWindowController.setForcePluginOpen(it, this)
- }
+ if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) {
+ return
}
+ optionallyAddUdfpsView(
+ view = view,
+ alternateBouncerUdfpsIconViewModel = alternateBouncerUdfpsIconViewModel,
+ )
val scrim = view.requireViewById(R.id.alternate_bouncer_scrim) as ScrimView
view.repeatWhenAttached { alternateBouncerViewContainer ->
@@ -99,6 +101,52 @@
}
}
}
+
+ private fun optionallyAddUdfpsView(
+ view: ConstraintLayout,
+ alternateBouncerUdfpsIconViewModel: AlternateBouncerUdfpsIconViewModel,
+ ) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ alternateBouncerUdfpsIconViewModel.iconLocation.collect { iconLocation ->
+ val viewId = R.id.alternate_bouncer_udfps_icon_view
+ var udfpsView = view.getViewById(viewId)
+ if (udfpsView == null) {
+ udfpsView =
+ DeviceEntryIconView(view.context, null).apply { id = viewId }
+ view.addView(udfpsView)
+ AlternateBouncerUdfpsViewBinder.bind(
+ udfpsView,
+ alternateBouncerUdfpsIconViewModel,
+ )
+ }
+
+ val constraintSet = ConstraintSet().apply { clone(view) }
+ constraintSet.apply {
+ constrainWidth(viewId, iconLocation.width)
+ constrainHeight(viewId, iconLocation.height)
+ connect(
+ viewId,
+ ConstraintSet.TOP,
+ ConstraintSet.PARENT_ID,
+ ConstraintSet.TOP,
+ iconLocation.top,
+ )
+ connect(
+ viewId,
+ ConstraintSet.START,
+ ConstraintSet.PARENT_ID,
+ ConstraintSet.START,
+ iconLocation.left
+ )
+ }
+ constraintSet.applyTo(view)
+ }
+ }
+ }
+ }
+ }
}
private const val swipeTag = "AlternateBouncer-SWIPE"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 01a1ca3..08e2a8f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -393,7 +393,6 @@
iconsAppearTranslationPx: Int,
screenOffAnimationController: ScreenOffAnimationController,
) {
- val statusViewMigrated = KeyguardShadeMigrationNssl.isEnabled
animate().cancel()
val animatorListener =
object : AnimatorListenerAdapter() {
@@ -404,13 +403,13 @@
when {
!isVisible.isAnimating -> {
alpha = 1f
- if (!statusViewMigrated) {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
translationY = 0f
}
visibility = if (isVisible.value) View.VISIBLE else View.INVISIBLE
}
newAodTransition() -> {
- animateInIconTranslation(statusViewMigrated)
+ animateInIconTranslation()
if (isVisible.value) {
CrossFadeHelper.fadeIn(this, animatorListener)
} else {
@@ -419,7 +418,7 @@
}
!isVisible.value -> {
// Let's make sure the icon are translated to 0, since we cancelled it above
- animateInIconTranslation(statusViewMigrated)
+ animateInIconTranslation()
CrossFadeHelper.fadeOut(this, animatorListener)
}
visibility != View.VISIBLE -> {
@@ -429,13 +428,12 @@
appearIcons(
animate = screenOffAnimationController.shouldAnimateAodIcons(),
iconsAppearTranslationPx,
- statusViewMigrated,
animatorListener,
)
}
else -> {
// Let's make sure the icons are translated to 0, since we cancelled it above
- animateInIconTranslation(statusViewMigrated)
+ animateInIconTranslation()
// We were fading out, let's fade in instead
CrossFadeHelper.fadeIn(this, animatorListener)
}
@@ -445,11 +443,10 @@
private fun View.appearIcons(
animate: Boolean,
iconAppearTranslation: Int,
- statusViewMigrated: Boolean,
animatorListener: Animator.AnimatorListener,
) {
if (animate) {
- if (!statusViewMigrated) {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
translationY = -iconAppearTranslation.toFloat()
}
alpha = 0f
@@ -457,19 +454,19 @@
.alpha(1f)
.setInterpolator(Interpolators.LINEAR)
.setDuration(AOD_ICONS_APPEAR_DURATION)
- .apply { if (statusViewMigrated) animateInIconTranslation() }
+ .apply { if (KeyguardShadeMigrationNssl.isEnabled) animateInIconTranslation() }
.setListener(animatorListener)
.start()
} else {
alpha = 1.0f
- if (!statusViewMigrated) {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
translationY = 0f
}
}
}
- private fun View.animateInIconTranslation(statusViewMigrated: Boolean) {
- if (!statusViewMigrated) {
+ private fun View.animateInIconTranslation() {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
animate().animateInIconTranslation().setDuration(AOD_ICONS_APPEAR_DURATION).start()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
index 9d557bb..920fc04 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt
@@ -15,6 +15,8 @@
*/
package com.android.systemui.keyguard.ui.transitions
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToAodTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
@@ -39,6 +41,18 @@
abstract class DeviceEntryIconTransitionModule {
@Binds
@IntoSet
+ abstract fun alternateBouncerToAod(
+ impl: AlternateBouncerToAodTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
+ abstract fun alternateBouncerToGone(
+ impl: AlternateBouncerToGoneTransitionViewModel
+ ): DeviceEntryIconTransition
+
+ @Binds
+ @IntoSet
abstract fun aodToLockscreen(
impl: AodToLockscreenTransitionViewModel
): DeviceEntryIconTransition
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index a693ec9..0bf9ad0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -23,7 +23,6 @@
import android.util.DisplayMetrics
import android.view.View
import android.view.WindowManager
-import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
@@ -32,31 +31,24 @@
import com.android.keyguard.LockIconViewController
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.biometrics.AuthController
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
-import com.android.systemui.keyguard.ui.binder.AlternateBouncerViewBinder
import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
-import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
-import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.statusbar.gesture.TapGestureDetector
import dagger.Lazy
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-/** Includes both the device entry icon and the alternate bouncer scrim. */
+/** Includes the device entry icon. */
@ExperimentalCoroutinesApi
class DefaultDeviceEntrySection
@Inject
@@ -72,27 +64,15 @@
private val deviceEntryForegroundViewModel: Lazy<DeviceEntryForegroundViewModel>,
private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
private val falsingManager: Lazy<FalsingManager>,
- private val alternateBouncerViewModel: Lazy<AlternateBouncerViewModel>,
- private val notificationShadeWindowController: Lazy<NotificationShadeWindowController>,
- @Application private val scope: CoroutineScope,
- private val swipeUpAnywhereGestureHandler: Lazy<SwipeUpAnywhereGestureHandler>,
- private val tapGestureDetector: Lazy<TapGestureDetector>,
private val vibratorHelper: Lazy<VibratorHelper>,
) : KeyguardSection() {
private val deviceEntryIconViewId = R.id.device_entry_icon_view
- private val alternateBouncerViewId = R.id.alternate_bouncer
override fun addViews(constraintLayout: ConstraintLayout) {
if (!keyguardBottomAreaRefactor() && !DeviceEntryUdfpsRefactor.isEnabled) {
return
}
- if (DeviceEntryUdfpsRefactor.isEnabled) {
- // The alternate bouncer scrim needs to be below the device entry icon view, so
- // we add the view here before adding the device entry icon view.
- View.inflate(context, R.layout.alternate_bouncer, constraintLayout)
- }
-
notificationPanelView.findViewById<View>(R.id.lock_icon_view).let {
notificationPanelView.removeView(it)
}
@@ -119,17 +99,6 @@
vibratorHelper.get(),
)
}
- constraintLayout.findViewById<FrameLayout?>(alternateBouncerViewId)?.let {
- AlternateBouncerViewBinder.bind(
- it,
- alternateBouncerViewModel.get(),
- scope,
- notificationShadeWindowController.get(),
- falsingManager.get(),
- swipeUpAnywhereGestureHandler.get(),
- tapGestureDetector.get(),
- )
- }
} else {
constraintLayout.findViewById<LockIconView?>(R.id.lock_icon_view)?.let {
lockIconViewController.get().setLockIconView(it)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
new file mode 100644
index 0000000..a8e3be7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+
+/**
+ * Breaks down ALTERNATE BOUNCER->AOD transition into discrete steps for corresponding views to
+ * consume.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class AlternateBouncerToAodTransitionViewModel
+@Inject
+constructor(
+ interactor: KeyguardTransitionInteractor,
+ deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+ animationFlow: KeyguardTransitionAnimationFlow,
+) : DeviceEntryIconTransition {
+ private val transitionAnimation =
+ animationFlow.setup(
+ duration = FromAlternateBouncerTransitionInteractor.TO_AOD_DURATION,
+ stepFlow = interactor.transition(KeyguardState.ALTERNATE_BOUNCER, KeyguardState.AOD),
+ )
+
+ val deviceEntryBackgroundViewAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = FromAlternateBouncerTransitionInteractor.TO_AOD_DURATION,
+ onStep = { 1 - it },
+ onFinish = { 0f },
+ )
+
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolledAndEnabled
+ ->
+ if (udfpsEnrolledAndEnabled) {
+ transitionAnimation.immediatelyTransitionTo(1f)
+ } else {
+ emptyFlow()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
index 023d16ca..5d6b0ce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
@@ -18,8 +18,12 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TO_GONE_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.ScrimAlpha
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -33,10 +37,20 @@
class AlternateBouncerToGoneTransitionViewModel
@Inject
constructor(
+ interactor: KeyguardTransitionInteractor,
bouncerToGoneFlows: BouncerToGoneFlows,
-) {
+ animationFlow: KeyguardTransitionAnimationFlow,
+) : DeviceEntryIconTransition {
+ private val transitionAnimation =
+ animationFlow.setup(
+ duration = TO_GONE_DURATION,
+ stepFlow = interactor.transition(ALTERNATE_BOUNCER, KeyguardState.GONE),
+ )
/** Scrim alpha values */
val scrimAlpha: Flow<ScrimAlpha> =
bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, ALTERNATE_BOUNCER)
+
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
new file mode 100644
index 0000000..e18893a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.content.Context
+import android.hardware.biometrics.SensorLocationInternal
+import com.android.settingslib.Utils
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/** Models the UI state for the UDFPS icon view in the alternate bouncer view. */
+@ExperimentalCoroutinesApi
+class AlternateBouncerUdfpsIconViewModel
+@Inject
+constructor(
+ val context: Context,
+ configurationInteractor: ConfigurationInteractor,
+ deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+ fingerprintPropertyRepository: FingerprintPropertyRepository,
+) {
+ private val isSupported: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported
+ val iconLocation: Flow<IconLocation> =
+ isSupported.flatMapLatest { supportsUI ->
+ if (supportsUI) {
+ fingerprintPropertyRepository.sensorLocations.map { sensorLocations ->
+ val sensorLocation =
+ sensorLocations.getOrDefault("", SensorLocationInternal.DEFAULT)
+ IconLocation(
+ left = sensorLocation.sensorLocationX - sensorLocation.sensorRadius,
+ top = sensorLocation.sensorLocationY - sensorLocation.sensorRadius,
+ right = sensorLocation.sensorLocationX + sensorLocation.sensorRadius,
+ bottom = sensorLocation.sensorLocationY + sensorLocation.sensorRadius,
+ )
+ }
+ } else {
+ emptyFlow()
+ }
+ }
+ val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> =
+ flowOf(DeviceEntryIconView.AccessibilityHintType.ENTER)
+
+ private val fgIconColor: Flow<Int> =
+ configurationInteractor.onAnyConfigurationChange
+ .map { Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) }
+ .onStart {
+ emit(Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary))
+ }
+ private val fgIconPadding: Flow<Int> =
+ configurationInteractor.scaleForResolution.map { scale ->
+ (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
+ .roundToInt()
+ }
+ val fgViewModel: Flow<DeviceEntryForegroundViewModel.ForegroundIconViewModel> =
+ combine(
+ fgIconColor,
+ fgIconPadding,
+ ) { color, padding ->
+ DeviceEntryForegroundViewModel.ForegroundIconViewModel(
+ type = DeviceEntryIconView.IconType.FINGERPRINT,
+ useAodVariant = false,
+ tint = color,
+ padding = padding,
+ )
+ }
+
+ private val bgColor: Flow<Int> =
+ configurationInteractor.onAnyConfigurationChange
+ .map {
+ Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorSurface)
+ }
+ .onStart {
+ emit(
+ Utils.getColorAttrDefaultColor(
+ context,
+ com.android.internal.R.attr.colorSurface
+ )
+ )
+ }
+ val bgViewModel: Flow<DeviceEntryBackgroundViewModel.BackgroundViewModel> =
+ bgColor.map { color ->
+ DeviceEntryBackgroundViewModel.BackgroundViewModel(
+ alpha = 1f,
+ tint = color,
+ )
+ }
+
+ data class IconLocation(
+ val left: Int,
+ val top: Int,
+ val right: Int,
+ val bottom: Int,
+ ) {
+ val width = right - left
+ val height = bottom - top
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
index 3e8bbb3..c45caf0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -42,6 +42,7 @@
occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel,
occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+ alternateBouncerToAodTransitionViewModel: AlternateBouncerToAodTransitionViewModel,
) {
private val color: Flow<Int> =
configurationRepository.onAnyConfigurationChange
@@ -65,6 +66,7 @@
occludedToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
occludedToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
dreamingToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ alternateBouncerToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
)
.merge()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index 9a50d83..fe0b365 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -19,6 +19,7 @@
import android.content.Context
import com.android.settingslib.Utils
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -26,7 +27,6 @@
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.res.R
import javax.inject.Inject
-import kotlin.math.roundToInt
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -45,6 +45,7 @@
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
transitionInteractor: KeyguardTransitionInteractor,
deviceEntryIconViewModel: DeviceEntryIconViewModel,
+ udfpsOverlayInteractor: UdfpsOverlayInteractor,
) {
private val isShowingAod: Flow<Boolean> =
transitionInteractor.startedKeyguardState.map { keyguardState ->
@@ -73,11 +74,7 @@
isTransitionToAod && isUdfps
}
.distinctUntilChanged()
- private val padding: Flow<Int> =
- configurationRepository.scaleForResolution.map { scale ->
- (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale)
- .roundToInt()
- }
+ private val padding: Flow<Int> = udfpsOverlayInteractor.iconPadding
val viewModel: Flow<ForegroundIconViewModel> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index f95713b..eacaa40 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -29,6 +29,7 @@
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.util.kotlin.sample
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -52,15 +53,12 @@
transitionInteractor: KeyguardTransitionInteractor,
val keyguardInteractor: KeyguardInteractor,
val viewModel: AodToLockscreenTransitionViewModel,
- val shadeDependentFlows: ShadeDependentFlows,
private val sceneContainerFlags: SceneContainerFlags,
private val keyguardViewController: Lazy<KeyguardViewController>,
private val deviceEntryInteractor: DeviceEntryInteractor,
) {
private val intEvaluator = IntEvaluator()
private val floatEvaluator = FloatEvaluator()
- private val toAodFromState: Flow<KeyguardState> =
- transitionInteractor.transitionStepsToState(KeyguardState.AOD).map { it.from }
private val showingAlternateBouncer: Flow<Boolean> =
transitionInteractor.startedKeyguardState.map { keyguardState ->
keyguardState == KeyguardState.ALTERNATE_BOUNCER
@@ -95,13 +93,31 @@
fullyDozingBurnInProgress,
)
}
+
+ private val dozeAmount: Flow<Float> =
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep,
+ merge(
+ transitionInteractor.transitionStepsFromState(KeyguardState.AOD).map {
+ 1f - it.value
+ },
+ transitionInteractor.transitionStepsToState(KeyguardState.AOD).map { it.value }
+ ),
+ ) { startedKeyguardTransitionStep, aodTransitionAmount ->
+ if (
+ startedKeyguardTransitionStep.to == KeyguardState.AOD ||
+ startedKeyguardTransitionStep.from == KeyguardState.AOD
+ ) {
+ aodTransitionAmount
+ } else {
+ // in case a new transition (ie: to occluded) cancels a transition to or from
+ // aod, then we want to make sure the doze amount is still updated to 0
+ 0f
+ }
+ }
// Burn-in offsets that animate based on the transition amount to AOD
private val animatedBurnInOffsets: Flow<BurnInOffsets> =
- combine(
- nonAnimatedBurnInOffsets,
- transitionInteractor.transitionStepsToState(KeyguardState.AOD)
- ) { burnInOffsets, transitionStepsToAod ->
- val dozeAmount = transitionStepsToAod.value
+ combine(nonAnimatedBurnInOffsets, dozeAmount) { burnInOffsets, dozeAmount ->
BurnInOffsets(
intEvaluator.evaluate(dozeAmount, 0, burnInOffsets.x),
intEvaluator.evaluate(dozeAmount, 0, burnInOffsets.y),
@@ -120,22 +136,35 @@
val burnInOffsets: Flow<BurnInOffsets> =
deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolled ->
if (udfpsEnrolled) {
- toAodFromState.flatMapLatest { fromState ->
- when (fromState) {
- KeyguardState.AOD,
- KeyguardState.GONE,
- KeyguardState.OCCLUDED,
- KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
- KeyguardState.OFF,
- KeyguardState.DOZING,
- KeyguardState.DREAMING,
- KeyguardState.PRIMARY_BOUNCER -> nonAnimatedBurnInOffsets
- KeyguardState.ALTERNATE_BOUNCER -> animatedBurnInOffsets
- KeyguardState.LOCKSCREEN ->
- shadeDependentFlows.transitionFlow(
- flowWhenShadeIsExpanded = nonAnimatedBurnInOffsets,
- flowWhenShadeIsNotExpanded = animatedBurnInOffsets,
- )
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep.sample(
+ shadeInteractor.isAnyFullyExpanded,
+ ::Pair
+ ),
+ animatedBurnInOffsets,
+ nonAnimatedBurnInOffsets,
+ ) {
+ (startedTransitionStep, shadeExpanded),
+ animatedBurnInOffsets,
+ nonAnimatedBurnInOffsets ->
+ if (startedTransitionStep.to == KeyguardState.AOD) {
+ when (startedTransitionStep.from) {
+ KeyguardState.ALTERNATE_BOUNCER -> animatedBurnInOffsets
+ KeyguardState.LOCKSCREEN ->
+ if (shadeExpanded) {
+ nonAnimatedBurnInOffsets
+ } else {
+ animatedBurnInOffsets
+ }
+ else -> nonAnimatedBurnInOffsets
+ }
+ } else if (startedTransitionStep.from == KeyguardState.AOD) {
+ when (startedTransitionStep.to) {
+ KeyguardState.LOCKSCREEN -> animatedBurnInOffsets
+ else -> BurnInOffsets(x = 0, y = 0, progress = 0f)
+ }
+ } else {
+ BurnInOffsets(x = 0, y = 0, progress = 0f)
}
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index a99c51c2..be93936 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -405,12 +405,15 @@
}
widgetGroupIds.forEach { id ->
squishedViewState.widgetStates.get(id)?.let { state ->
- state.alpha =
- calculateAlpha(
- squishFraction,
- startPosition / nonsquishedHeight,
- endPosition / nonsquishedHeight
- )
+ // Don't modify alpha for elements that should be invisible (e.g. disabled seekbar)
+ if (state.alpha != 0f) {
+ state.alpha =
+ calculateAlpha(
+ squishFraction,
+ startPosition / nonsquishedHeight,
+ endPosition / nonsquishedHeight
+ )
+ }
}
}
return groupTop // used for the widget group above this group
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt
index 898298c..77279f2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaInSceneContainerFlag.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media.controls.util
import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
/** Helper for reading or using the media_in_scene_container flag state. */
@@ -25,6 +26,10 @@
/** The aconfig flag name */
const val FLAG_NAME = Flags.FLAG_MEDIA_IN_SCENE_CONTAINER
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
/** Is the flag enabled? */
@JvmStatic
inline val isEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
index ca5302e..c932cee 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
@@ -16,11 +16,15 @@
package com.android.systemui.qs.tiles.impl.custom.data.repository
+import android.content.pm.PackageManager
+import android.content.pm.ServiceInfo
import android.graphics.drawable.Icon
import android.os.UserHandle
import android.service.quicksettings.Tile
+import android.service.quicksettings.TileService
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.external.CustomTileStatePersister
+import com.android.systemui.qs.external.PackageManagerAdapter
import com.android.systemui.qs.external.TileServiceKey
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.commons.copy
@@ -63,6 +67,12 @@
*/
fun getTile(user: UserHandle): Tile?
+ /** @see [com.android.systemui.qs.external.TileLifecycleManager.isActiveTile] */
+ suspend fun isTileActive(): Boolean
+
+ /** @see [com.android.systemui.qs.external.TileLifecycleManager.isToggleableTile] */
+ suspend fun isTileToggleable(): Boolean
+
/**
* Updates tile with the non-null values from [newTile]. Overwrites the current cache when
* [user] differs from the cached one. [isPersistable] tile will be persisted to be possibly
@@ -92,6 +102,7 @@
constructor(
private val tileSpec: TileSpec.CustomTileSpec,
private val customTileStatePersister: CustomTileStatePersister,
+ private val packageManagerAdapter: PackageManagerAdapter,
@Background private val backgroundContext: CoroutineContext,
) : CustomTileRepository {
@@ -149,6 +160,34 @@
}
}
+ override suspend fun isTileActive(): Boolean =
+ withContext(backgroundContext) {
+ try {
+ val info: ServiceInfo =
+ packageManagerAdapter.getServiceInfo(
+ tileSpec.componentName,
+ META_DATA_QUERY_FLAGS
+ )
+ info.metaData?.getBoolean(TileService.META_DATA_ACTIVE_TILE, false) == true
+ } catch (e: PackageManager.NameNotFoundException) {
+ false
+ }
+ }
+
+ override suspend fun isTileToggleable(): Boolean =
+ withContext(backgroundContext) {
+ try {
+ val info: ServiceInfo =
+ packageManagerAdapter.getServiceInfo(
+ tileSpec.componentName,
+ META_DATA_QUERY_FLAGS
+ )
+ info.metaData?.getBoolean(TileService.META_DATA_TOGGLEABLE_TILE, false) == true
+ } catch (e: PackageManager.NameNotFoundException) {
+ false
+ }
+ }
+
private suspend fun updateTile(
user: UserHandle,
isPersistable: Boolean,
@@ -193,4 +232,12 @@
private fun UserHandle.getKey() = TileServiceKey(tileSpec.componentName, this.identifier)
private data class TileWithUser(val user: UserHandle, val tile: Tile)
+
+ private companion object {
+ const val META_DATA_QUERY_FLAGS =
+ (PackageManager.GET_META_DATA or
+ PackageManager.MATCH_UNINSTALLED_PACKAGES or
+ PackageManager.MATCH_DIRECT_BOOT_UNAWARE or
+ PackageManager.MATCH_DIRECT_BOOT_AWARE)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
index 351bba5..10b012d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
@@ -19,10 +19,9 @@
import android.os.UserHandle
import android.service.quicksettings.Tile
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.qs.external.TileServiceManager
import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository
import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepository
-import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundScope
+import com.android.systemui.qs.tiles.impl.di.QSTileScope
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
@@ -35,15 +34,13 @@
import kotlinx.coroutines.flow.onEach
/** Manages updates of the [Tile] assigned for the current custom tile. */
-@CustomTileBoundScope
+@QSTileScope
class CustomTileInteractor
@Inject
constructor(
- private val user: UserHandle,
private val defaultsRepository: CustomTileDefaultsRepository,
private val customTileRepository: CustomTileRepository,
- private val tileServiceManager: TileServiceManager,
- @CustomTileBoundScope private val boundScope: CoroutineScope,
+ @QSTileScope private val tileScope: CoroutineScope,
@Background private val backgroundContext: CoroutineContext,
) {
@@ -51,8 +48,7 @@
MutableSharedFlow<Tile>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
/** [Tile] updates. [updateTile] to emit a new one. */
- val tiles: Flow<Tile>
- get() = customTileRepository.getTiles(user)
+ fun getTiles(user: UserHandle): Flow<Tile> = customTileRepository.getTiles(user)
/**
* Current [Tile]
@@ -61,10 +57,14 @@
* the tile hasn't been updated for the current user. Can happen when this is accessed before
* [init] returns.
*/
- val tile: Tile
- get() =
- customTileRepository.getTile(user)
- ?: throw IllegalStateException("Attempt to get a tile for a wrong user")
+ fun getTile(user: UserHandle): Tile =
+ customTileRepository.getTile(user)
+ ?: throw IllegalStateException("Attempt to get a tile for a wrong user")
+
+ /**
+ * True if the tile is toggleable like a switch and false if it operates as a clickable button.
+ */
+ suspend fun isTileToggleable(): Boolean = customTileRepository.isTileToggleable()
/**
* Initializes the repository for the current user. Suspends until it's safe to call [tile]
@@ -73,36 +73,36 @@
* - receive tile update in [updateTile];
* - restoration happened for a persisted tile.
*/
- suspend fun init() {
- launchUpdates()
- customTileRepository.restoreForTheUserIfNeeded(user, tileServiceManager.isActiveTile)
+ suspend fun initForUser(user: UserHandle) {
+ launchUpdates(user)
+ customTileRepository.restoreForTheUserIfNeeded(user, customTileRepository.isTileActive())
// Suspend to make sure it gets the tile from one of the sources: restoration, defaults, or
// tile update.
customTileRepository.getTiles(user).firstOrNull()
}
- private fun launchUpdates() {
+ private fun launchUpdates(user: UserHandle) {
tileUpdates
.onEach {
customTileRepository.updateWithTile(
user,
it,
- tileServiceManager.isActiveTile,
+ customTileRepository.isTileActive(),
)
}
.flowOn(backgroundContext)
- .launchIn(boundScope)
+ .launchIn(tileScope)
defaultsRepository
.defaults(user)
.onEach {
customTileRepository.updateWithDefaults(
user,
it,
- tileServiceManager.isActiveTile,
+ customTileRepository.isTileActive(),
)
}
.flowOn(backgroundContext)
- .launchIn(boundScope)
+ .launchIn(tileScope)
}
/** Updates current [Tile]. Emits a new event in [tiles]. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
index 580c421..ef3df48 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
@@ -22,8 +22,8 @@
/**
* Represents tiles behaviour logic. This ViewModel is a connection between tile view and data
- * layers. All direct inheritors must be added to the [QSTileViewModelInterfaceComplianceTest] class
- * to pass compliance tests.
+ * layers. All direct inheritors must be added to the
+ * [com.android.systemui.qs.tiles.viewmodel.QSTileViewModelTest] class to pass interface tests.
*
* All methods of this view model should be considered running on the main thread. This means no
* synchronous long running operations are permitted in any method.
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index 1156250..8c3e4a5 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -14,30 +14,94 @@
* limitations under the License.
*/
+@file:Suppress("NOTHING_TO_INLINE")
+
package com.android.systemui.scene.shared.flag
-import android.content.Context
-import androidx.annotation.VisibleForTesting
-import com.android.systemui.Flags as AConfigFlags
+import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.Flags.sceneContainer
import com.android.systemui.compose.ComposeFacade
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flag
-import com.android.systemui.flags.Flags
-import com.android.systemui.flags.ReleasedFlag
-import com.android.systemui.flags.ResourceBooleanFlag
-import com.android.systemui.flags.UnreleasedFlag
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.Flags.SCENE_CONTAINER_ENABLED
+import com.android.systemui.flags.RefactorFlagUtils
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
-import com.android.systemui.res.R
import dagger.Module
import dagger.Provides
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+
+/** Helper for reading or using the scene container flag state. */
+object SceneContainerFlag {
+ /** The flag description -- not an aconfig flag name */
+ const val DESCRIPTION = "SceneContainerFlag"
+
+ inline val isEnabled
+ get() =
+ SCENE_CONTAINER_ENABLED && // mainStaticFlag
+ sceneContainer() && // mainAconfigFlag
+ keyguardBottomAreaRefactor() &&
+ KeyguardShadeMigrationNssl.isEnabled &&
+ MediaInSceneContainerFlag.isEnabled &&
+ ComposeFacade.isComposeAvailable()
+
+ /**
+ * The main static flag, SCENE_CONTAINER_ENABLED. This is an explicit static flag check that
+ * helps with downstream optimizations (like unused code stripping) in builds where aconfig
+ * flags are still writable. Do not remove!
+ */
+ inline fun getMainStaticFlag() =
+ FlagToken("Flags.SCENE_CONTAINER_ENABLED", SCENE_CONTAINER_ENABLED)
+
+ /** The main aconfig flag. */
+ inline fun getMainAconfigFlag() = FlagToken(FLAG_SCENE_CONTAINER, sceneContainer())
+
+ /** The set of secondary flags which must be enabled for scene container to work properly */
+ inline fun getSecondaryFlags(): Sequence<FlagToken> =
+ sequenceOf(
+ FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor()),
+ KeyguardShadeMigrationNssl.token,
+ MediaInSceneContainerFlag.token,
+ )
+
+ /** The full set of requirements for SceneContainer */
+ inline fun getAllRequirements(): Sequence<FlagToken> {
+ val composeRequirement =
+ FlagToken("ComposeFacade.isComposeAvailable()", ComposeFacade.isComposeAvailable())
+ return sequenceOf(getMainStaticFlag(), getMainAconfigFlag()) +
+ getSecondaryFlags() +
+ composeRequirement
+ }
+
+ /** Return all dependencies of this flag in pairs where [Pair.first] depends on [Pair.second] */
+ inline fun getFlagDependencies(): Sequence<Pair<FlagToken, FlagToken>> {
+ val mainStaticFlag = getMainStaticFlag()
+ val mainAconfigFlag = getMainAconfigFlag()
+ return sequence {
+ // The static and aconfig flags should be equal; make them co-dependent
+ yield(mainAconfigFlag to mainStaticFlag)
+ yield(mainStaticFlag to mainAconfigFlag)
+ // all other flags depend on the static flag for brevity
+ } + getSecondaryFlags().map { mainStaticFlag to it }
+ }
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, DESCRIPTION)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, DESCRIPTION)
+}
/**
* Defines interface for classes that can check whether the scene container framework feature is
@@ -52,133 +116,25 @@
fun requirementDescription(): String
}
-class SceneContainerFlagsImpl
-@AssistedInject
-constructor(
- @Application private val context: Context,
- private val featureFlagsClassic: FeatureFlagsClassic,
- @Assisted private val isComposeAvailable: Boolean,
-) : SceneContainerFlags {
-
- companion object {
- @VisibleForTesting
- val classicFlagTokens: List<Flag<Boolean>> =
- listOf(
- Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW,
- )
- }
-
- /** The list of requirements, all must be met for the feature to be enabled. */
- private val requirements =
- listOf(
- AconfigFlagMustBeEnabled(
- flagName = AConfigFlags.FLAG_SCENE_CONTAINER,
- flagValue = sceneContainer(),
- ),
- AconfigFlagMustBeEnabled(
- flagName = AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
- flagValue = keyguardBottomAreaRefactor(),
- ),
- AconfigFlagMustBeEnabled(
- flagName = KeyguardShadeMigrationNssl.FLAG_NAME,
- flagValue = KeyguardShadeMigrationNssl.isEnabled,
- ),
- AconfigFlagMustBeEnabled(
- flagName = MediaInSceneContainerFlag.FLAG_NAME,
- flagValue = MediaInSceneContainerFlag.isEnabled,
- ),
- ) +
- classicFlagTokens.map { flagToken -> FlagMustBeEnabled(flagToken) } +
- listOf(
- ComposeMustBeAvailable(),
- CompileTimeFlagMustBeEnabled(),
- ResourceConfigMustBeEnabled()
- )
+class SceneContainerFlagsImpl : SceneContainerFlags {
override fun isEnabled(): Boolean {
- // SCENE_CONTAINER_ENABLED is an explicit static flag check that helps with downstream
- // optimizations, e.g., unused code stripping. Do not remove!
- return Flags.SCENE_CONTAINER_ENABLED && requirements.all { it.isMet() }
+ return SceneContainerFlag.isEnabled
}
override fun requirementDescription(): String {
return buildString {
- requirements.forEach { requirement ->
+ SceneContainerFlag.getAllRequirements().forEach { requirement ->
append('\n')
- append(if (requirement.isMet()) " [MET]" else "[NOT MET]")
+ append(if (requirement.isEnabled) " [MET]" else "[NOT MET]")
append(" ${requirement.name}")
}
}
}
-
- private interface Requirement {
- val name: String
-
- fun isMet(): Boolean
- }
-
- private inner class ComposeMustBeAvailable : Requirement {
- override val name = "Jetpack Compose must be available"
-
- override fun isMet(): Boolean {
- return isComposeAvailable
- }
- }
-
- private inner class CompileTimeFlagMustBeEnabled : Requirement {
- override val name = "Flags.SCENE_CONTAINER_ENABLED must be enabled in code"
-
- override fun isMet(): Boolean {
- return Flags.SCENE_CONTAINER_ENABLED
- }
- }
-
- private inner class FlagMustBeEnabled<FlagType : Flag<*>>(
- private val flag: FlagType,
- ) : Requirement {
- override val name = "Flag ${flag.name} must be enabled"
-
- override fun isMet(): Boolean {
- return when (flag) {
- is ResourceBooleanFlag -> featureFlagsClassic.isEnabled(flag)
- is ReleasedFlag -> featureFlagsClassic.isEnabled(flag)
- is UnreleasedFlag -> featureFlagsClassic.isEnabled(flag)
- else -> error("Unsupported flag type ${flag.javaClass}")
- }
- }
- }
-
- private inner class AconfigFlagMustBeEnabled(
- flagName: String,
- private val flagValue: Boolean,
- ) : Requirement {
- override val name: String = "Aconfig flag $flagName must be enabled"
-
- override fun isMet(): Boolean {
- return flagValue
- }
- }
-
- private inner class ResourceConfigMustBeEnabled : Requirement {
- override val name: String = "R.bool.config_sceneContainerFrameworkEnabled must be true"
-
- override fun isMet(): Boolean {
- return context.resources.getBoolean(R.bool.config_sceneContainerFrameworkEnabled)
- }
- }
-
- @AssistedFactory
- interface Factory {
- fun create(isComposeAvailable: Boolean): SceneContainerFlagsImpl
- }
}
@Module
object SceneContainerFlagsModule {
- @Provides
- @SysUISingleton
- fun impl(factory: SceneContainerFlagsImpl.Factory): SceneContainerFlags {
- return factory.create(ComposeFacade.isComposeAvailable())
- }
+ @Provides @SysUISingleton fun impl(): SceneContainerFlags = SceneContainerFlagsImpl()
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 95f7c94a..878e6fa 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1096,7 +1096,7 @@
}
@Override
- public void onPulseExpansionChanged(boolean expandingChanged) {
+ public void onPulseExpansionAmountChanged(boolean expandingChanged) {
if (mKeyguardBypassController.getBypassEnabled()) {
// Position the notifications while dragging down while pulsing
requestScrollerTopPaddingUpdate(false /* animate */);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 07ce577..3cf468f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -54,8 +54,13 @@
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
+import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler;
+import com.android.systemui.keyguard.ui.binder.AlternateBouncerViewBinder;
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.log.BouncerLogger;
+import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.res.R;
import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
import com.android.systemui.statusbar.DragDownHelper;
@@ -64,6 +69,7 @@
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.gesture.TapGestureDetector;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -76,14 +82,19 @@
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.SystemClock;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.util.Optional;
import java.util.function.Consumer;
import javax.inject.Inject;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
/**
* Controller for {@link NotificationShadeWindowView}.
*/
@@ -149,6 +160,7 @@
};
private final SystemClock mClock;
+ @ExperimentalCoroutinesApi
@Inject
public NotificationShadeWindowViewController(
LockscreenShadeTransitionController transitionController,
@@ -190,7 +202,13 @@
QuickSettingsController quickSettingsController,
PrimaryBouncerInteractor primaryBouncerInteractor,
AlternateBouncerInteractor alternateBouncerInteractor,
- SelectedUserInteractor selectedUserInteractor) {
+ SelectedUserInteractor selectedUserInteractor,
+ Lazy<JavaAdapter> javaAdapter,
+ Lazy<AlternateBouncerViewModel> alternateBouncerViewModel,
+ Lazy<FalsingManager> falsingManager,
+ Lazy<SwipeUpAnywhereGestureHandler> swipeUpAnywhereGestureHandler,
+ Lazy<TapGestureDetector> tapGestureDetector,
+ Lazy<AlternateBouncerUdfpsIconViewModel> alternateBouncerUdfpsIconViewModel) {
mLockscreenShadeTransitionController = transitionController;
mFalsingCollector = falsingCollector;
mStatusBarStateController = statusBarStateController;
@@ -235,6 +253,25 @@
featureFlagsClassic,
selectedUserInteractor);
+ if (DeviceEntryUdfpsRefactor.isEnabled()) {
+ AlternateBouncerViewBinder.bind(
+ mView.findViewById(R.id.alternate_bouncer),
+ alternateBouncerViewModel.get(),
+ falsingManager.get(),
+ swipeUpAnywhereGestureHandler.get(),
+ tapGestureDetector.get(),
+ alternateBouncerUdfpsIconViewModel.get()
+ );
+ javaAdapter.get().alwaysCollectFlow(
+ alternateBouncerViewModel.get().getForcePluginOpen(),
+ forcePluginOpen ->
+ mNotificationShadeWindowController.setForcePluginOpen(
+ forcePluginOpen,
+ alternateBouncerViewModel.get()
+ )
+ );
+ }
+
collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
mLockscreenToDreamingTransition);
collectFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 843a454..984fcad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -27,7 +27,6 @@
import android.app.Notification;
import android.content.Context;
import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -59,6 +58,7 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.notification.NotificationContentDescription;
import com.android.systemui.statusbar.notification.NotificationDozeHelper;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
@@ -350,9 +350,22 @@
}
public void setNotification(StatusBarNotification notification) {
- mNotification = notification;
+ CharSequence contentDescription = null;
if (notification != null) {
- setContentDescription(notification.getNotification());
+ contentDescription = NotificationContentDescription
+ .contentDescForNotification(mContext, notification.getNotification());
+ }
+ setNotification(notification, contentDescription);
+ }
+
+ /**
+ * Sets the notification with a pre-set content description.
+ */
+ public void setNotification(@Nullable StatusBarNotification notification,
+ @Nullable CharSequence notificationContentDescription) {
+ mNotification = notification;
+ if (!TextUtils.isEmpty(notificationContentDescription)) {
+ setContentDescription(notificationContentDescription);
}
maybeUpdateIconScaleDimens();
}
@@ -621,15 +634,6 @@
mNumberBackground.setBounds(w-dw, h-dh, w, h);
}
- private void setContentDescription(Notification notification) {
- if (notification != null) {
- String d = contentDescForNotification(mContext, notification);
- if (!TextUtils.isEmpty(d)) {
- setContentDescription(d);
- }
- }
- }
-
@Override
public String toString() {
return "StatusBarIconView("
@@ -647,35 +651,6 @@
return mSlot;
}
-
- public static String contentDescForNotification(Context c, Notification n) {
- String appName = "";
- try {
- Notification.Builder builder = Notification.Builder.recoverBuilder(c, n);
- appName = builder.loadHeaderAppName();
- } catch (RuntimeException e) {
- Log.e(TAG, "Unable to recover builder", e);
- // Trying to get the app name from the app info instead.
- ApplicationInfo appInfo = n.extras.getParcelable(
- Notification.EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class);
- if (appInfo != null) {
- appName = String.valueOf(appInfo.loadLabel(c.getPackageManager()));
- }
- }
-
- CharSequence title = n.extras.getCharSequence(Notification.EXTRA_TITLE);
- CharSequence text = n.extras.getCharSequence(Notification.EXTRA_TEXT);
- CharSequence ticker = n.tickerText;
-
- // Some apps just put the app name into the title
- CharSequence titleOrText = TextUtils.equals(title, appName) ? text : title;
-
- CharSequence desc = !TextUtils.isEmpty(titleOrText) ? titleOrText
- : !TextUtils.isEmpty(ticker) ? ticker : "";
-
- return c.getString(R.string.accessibility_desc_notification_icon, appName, desc);
- }
-
/**
* Set the color that is used to draw decoration like the overflow dot. This will not be applied
* to the drawable.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt
new file mode 100644
index 0000000..e7012ea51
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationContentDescription.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+@file:JvmName("NotificationContentDescription")
+
+package com.android.systemui.statusbar.notification
+
+import android.app.Notification
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.text.TextUtils
+import android.util.Log
+import androidx.annotation.MainThread
+import com.android.systemui.res.R
+
+/**
+ * Returns accessibility content description for a given notification.
+ *
+ * NOTE: This is a relatively slow call.
+ */
+@MainThread
+fun contentDescForNotification(c: Context, n: Notification?): CharSequence {
+ var appName = ""
+ try {
+ val builder = Notification.Builder.recoverBuilder(c, n)
+ appName = builder.loadHeaderAppName()
+ } catch (e: RuntimeException) {
+ Log.e("ContentDescription", "Unable to recover builder", e)
+ // Trying to get the app name from the app info instead.
+ val appInfo =
+ n?.extras?.getParcelable(
+ Notification.EXTRA_BUILDER_APPLICATION_INFO,
+ ApplicationInfo::class.java
+ )
+ if (appInfo != null) {
+ appName = appInfo.loadLabel(c.packageManager).toString()
+ }
+ }
+ val title = n?.extras?.getCharSequence(Notification.EXTRA_TITLE)
+ val text = n?.extras?.getCharSequence(Notification.EXTRA_TEXT)
+ val ticker = n?.tickerText
+
+ // Some apps just put the app name into the title
+ val titleOrText = if (TextUtils.equals(title, appName)) text else title
+ val desc =
+ if (!TextUtils.isEmpty(titleOrText)) titleOrText
+ else if (!TextUtils.isEmpty(ticker)) ticker else ""
+ return c.getString(R.string.accessibility_desc_notification_icon, appName, desc)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 8d1e8d0..0c67279 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -20,9 +20,9 @@
import android.view.animation.Interpolator
import androidx.annotation.VisibleForTesting
import androidx.core.animation.ObjectAnimator
-import com.android.systemui.Dumpable
import com.android.app.animation.Interpolators
import com.android.app.animation.InterpolatorsAndroidX
+import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -31,6 +31,7 @@
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.phone.DozeParameters
@@ -206,8 +207,15 @@
val nowExpanding = isPulseExpanding()
val changed = nowExpanding != pulseExpanding
pulseExpanding = nowExpanding
- for (listener in wakeUpListeners) {
- listener.onPulseExpansionChanged(changed)
+ if (!NotificationIconContainerRefactor.isEnabled) {
+ for (listener in wakeUpListeners) {
+ listener.onPulseExpansionAmountChanged(changed)
+ }
+ }
+ if (changed) {
+ for (listener in wakeUpListeners) {
+ listener.onPulseExpandingChanged(pulseExpanding)
+ }
}
}
}
@@ -620,13 +628,20 @@
*
* @param expandingChanged if the user has started or stopped expanding
*/
- fun onPulseExpansionChanged(expandingChanged: Boolean) {}
+ @Deprecated(
+ message = "Use onPulseExpandedChanged instead.",
+ replaceWith = ReplaceWith("onPulseExpandedChanged"),
+ )
+ fun onPulseExpansionAmountChanged(expandingChanged: Boolean) {}
/**
* Called when the animator started by [scheduleDelayedDozeAmountAnimation] begins running
* after the start delay, or after it ends/is cancelled.
*/
fun onDelayedDozeAmountAnimationRunning(running: Boolean) {}
+
+ /** Called whenever a pulse has started or stopped expanding. */
+ fun onPulseExpandingChanged(isPulseExpanding: Boolean) {}
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt
index cf03d1c..2cc1403 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepository.kt
@@ -62,8 +62,8 @@
override val isPulseExpanding: Flow<Boolean> = conflatedCallbackFlow {
val listener =
object : NotificationWakeUpCoordinator.WakeUpListener {
- override fun onPulseExpansionChanged(expandingChanged: Boolean) {
- trySend(expandingChanged)
+ override fun onPulseExpandingChanged(isPulseExpanding: Boolean) {
+ trySend(isPulseExpanding)
}
}
trySend(wakeUpCoordinator.isPulseExpanding())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
index afc123f..319b499 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
@@ -20,6 +20,7 @@
import android.content.Context
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.contentDescForNotification
import javax.inject.Inject
/**
@@ -36,6 +37,6 @@
}
fun getIconContentDescription(n: Notification): CharSequence {
- return StatusBarIconView.contentDescForNotification(context, n)
+ return contentDescForNotification(context, n)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index 9e8654a..76e5fd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -26,15 +26,15 @@
import android.util.Log
import android.view.View
import android.widget.ImageView
+import com.android.app.tracing.traceSection
import com.android.internal.statusbar.StatusBarIcon
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.InflationException
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
-import com.android.app.tracing.traceSection
import javax.inject.Inject
/**
@@ -112,15 +112,6 @@
aodIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
aodIcon.setIncreasedSize(true)
- // Construct the centered icon view.
- val centeredIcon = if (entry.sbn.notification.isMediaNotification) {
- iconBuilder.createIconView(entry).apply {
- scaleType = ImageView.ScaleType.CENTER_INSIDE
- }
- } else {
- null
- }
-
// Set the icon views' icons
val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
@@ -128,10 +119,7 @@
setIcon(entry, normalIconDescriptor, sbIcon)
setIcon(entry, sensitiveIconDescriptor, shelfIcon)
setIcon(entry, sensitiveIconDescriptor, aodIcon)
- if (centeredIcon != null) {
- setIcon(entry, normalIconDescriptor, centeredIcon)
- }
- entry.icons = IconPack.buildPack(sbIcon, shelfIcon, aodIcon, centeredIcon, entry.icons)
+ entry.icons = IconPack.buildPack(sbIcon, shelfIcon, aodIcon, entry.icons)
} catch (e: InflationException) {
entry.icons = IconPack.buildEmptyPack(entry.icons)
throw e
@@ -153,24 +141,22 @@
entry.icons.peopleAvatarDescriptor = null
val (normalIconDescriptor, sensitiveIconDescriptor) = getIconDescriptors(entry)
+ val notificationContentDescription = entry.sbn.notification?.let {
+ iconBuilder.getIconContentDescription(it)
+ }
entry.icons.statusBarIcon?.let {
- it.notification = entry.sbn
+ it.setNotification(entry.sbn, notificationContentDescription)
setIcon(entry, normalIconDescriptor, it)
}
entry.icons.shelfIcon?.let {
- it.notification = entry.sbn
+ it.setNotification(entry.sbn, notificationContentDescription)
setIcon(entry, normalIconDescriptor, it)
}
entry.icons.aodIcon?.let {
- it.notification = entry.sbn
- setIcon(entry, sensitiveIconDescriptor, it)
- }
-
- entry.icons.centeredIcon?.let {
- it.notification = entry.sbn
+ it.setNotification(entry.sbn, notificationContentDescription)
setIcon(entry, sensitiveIconDescriptor, it)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
index 054e381..442c097 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
@@ -31,7 +31,6 @@
@Nullable private final StatusBarIconView mStatusBarIcon;
@Nullable private final StatusBarIconView mShelfIcon;
@Nullable private final StatusBarIconView mAodIcon;
- @Nullable private final StatusBarIconView mCenteredIcon;
@Nullable private StatusBarIcon mSmallIconDescriptor;
@Nullable private StatusBarIcon mPeopleAvatarDescriptor;
@@ -43,7 +42,7 @@
* haven't been inflated yet or there was an error while inflating them).
*/
public static IconPack buildEmptyPack(@Nullable IconPack fromSource) {
- return new IconPack(false, null, null, null, null, fromSource);
+ return new IconPack(false, null, null, null, fromSource);
}
/**
@@ -53,9 +52,8 @@
@NonNull StatusBarIconView statusBarIcon,
@NonNull StatusBarIconView shelfIcon,
@NonNull StatusBarIconView aodIcon,
- @Nullable StatusBarIconView centeredIcon,
@Nullable IconPack source) {
- return new IconPack(true, statusBarIcon, shelfIcon, aodIcon, centeredIcon, source);
+ return new IconPack(true, statusBarIcon, shelfIcon, aodIcon, source);
}
private IconPack(
@@ -63,12 +61,10 @@
@Nullable StatusBarIconView statusBarIcon,
@Nullable StatusBarIconView shelfIcon,
@Nullable StatusBarIconView aodIcon,
- @Nullable StatusBarIconView centeredIcon,
@Nullable IconPack source) {
mAreIconsAvailable = areIconsAvailable;
mStatusBarIcon = statusBarIcon;
mShelfIcon = shelfIcon;
- mCenteredIcon = centeredIcon;
mAodIcon = aodIcon;
if (source != null) {
mIsImportantConversation = source.mIsImportantConversation;
@@ -91,11 +87,6 @@
return mShelfIcon;
}
- @Nullable
- public StatusBarIconView getCenteredIcon() {
- return mCenteredIcon;
- }
-
/** The version of the icon that's shown when pulsing (in AOD). */
@Nullable
public StatusBarIconView getAodIcon() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionRefactor.kt
index 2624363..db33f92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionRefactor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionRefactor.kt
@@ -17,12 +17,17 @@
package com.android.systemui.statusbar.notification.interruption
import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
/** Helper for reading or using the visual interruptions refactor flag state. */
object VisualInterruptionRefactor {
const val FLAG_NAME = Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
/** Whether the refactor is enabled */
@JvmStatic
inline val isEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncHybridViewInflation.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncHybridViewInflation.kt
index 24e7f05..9556c2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncHybridViewInflation.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncHybridViewInflation.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.row.shared
import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
/** Helper for reading or using the async hybrid view inflation flag state. */
@@ -24,6 +25,10 @@
object AsyncHybridViewInflation {
const val FLAG_NAME = Flags.FLAG_NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
/** Is async hybrid (single-line) view inflation enabled */
@JvmStatic
inline val isEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 3bbdfd1..85e63e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -94,9 +94,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.res.R;
-import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.TouchLogger;
-import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
@@ -109,7 +107,6 @@
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
-import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -146,8 +143,6 @@
private static final String TAG = "StackScroller";
private static final boolean SPEW = Log.isLoggable(TAG, Log.VERBOSE);
- // Delay in milli-seconds before shade closes for clear all.
- private static final int DELAY_BEFORE_SHADE_CLOSE = 200;
private boolean mShadeNeedsToClose = false;
@VisibleForTesting
@@ -319,7 +314,7 @@
}
};
private NotificationStackScrollLogger mLogger;
- private NotificationsController mNotificationsController;
+ private Runnable mResetUserExpandedStatesRunnable;
private ActivityStarter mActivityStarter;
private final int[] mTempInt2 = new int[2];
private final HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
@@ -482,7 +477,7 @@
private final Rect mTmpRect = new Rect();
private ClearAllListener mClearAllListener;
private ClearAllAnimationListener mClearAllAnimationListener;
- private ShadeController mShadeController;
+ private Runnable mClearAllFinishedWhilePanelExpandedRunnable;
private Consumer<Boolean> mOnStackYChanged;
private Interpolator mHideXInterpolator = Interpolators.FAST_OUT_SLOW_IN;
@@ -4114,7 +4109,7 @@
mAmbientState.setExpansionChanging(false);
if (!mIsExpanded) {
resetScrollPosition();
- mNotificationsController.resetUserExpandedStates();
+ mResetUserExpandedStatesRunnable.run();
clearTemporaryViews();
clearUserLockedViews();
resetAllSwipeState();
@@ -4316,21 +4311,12 @@
if (mShadeNeedsToClose) {
mShadeNeedsToClose = false;
if (mIsExpanded) {
- collapseShadeDelayed();
+ mClearAllFinishedWhilePanelExpandedRunnable.run();
}
}
}
}
- private void collapseShadeDelayed() {
- postDelayed(
- () -> {
- mShadeController.animateCollapseShade(
- CommandQueue.FLAG_EXCLUDE_NONE);
- },
- DELAY_BEFORE_SHADE_CLOSE /* delayMillis */);
- }
-
private void clearHeadsUpDisappearRunning() {
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
@@ -4747,8 +4733,8 @@
return max + getStackTranslation();
}
- public void setNotificationsController(NotificationsController notificationsController) {
- this.mNotificationsController = notificationsController;
+ public void setResetUserExpandedStatesRunnable(Runnable runnable) {
+ this.mResetUserExpandedStatesRunnable = runnable;
}
public void setActivityStarter(ActivityStarter activityStarter) {
@@ -5681,8 +5667,8 @@
mFooterClearAllListener = listener;
}
- void setShadeController(ShadeController shadeController) {
- mShadeController = shadeController;
+ void setClearAllFinishedWhilePanelExpandedRunnable(Runnable runnable) {
+ mClearAllFinishedWhilePanelExpandedRunnable = runnable;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index e6315fd..d2fca8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -81,6 +81,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
@@ -152,6 +153,8 @@
private static final String TAG = "StackScrollerController";
private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
private static final String HIGH_PRIORITY = "high_priority";
+ /** Delay in milli-seconds before shade closes for clear all. */
+ private static final int DELAY_BEFORE_SHADE_CLOSE = 200;
private final boolean mAllowLongPress;
private final NotificationGutsManager mNotificationGutsManager;
@@ -754,7 +757,7 @@
mView.setController(this);
mView.setLogger(mLogger);
mView.setTouchHandler(new TouchHandler());
- mView.setNotificationsController(mNotificationsController);
+ mView.setResetUserExpandedStatesRunnable(mNotificationsController::resetUserExpandedStates);
mView.setActivityStarter(mActivityStarter);
mView.setClearAllAnimationListener(this::onAnimationEnd);
mView.setClearAllListener((selection) -> mUiEventLogger.log(
@@ -770,7 +773,11 @@
mView.setIsRemoteInputActive(active);
}
});
- mView.setShadeController(mShadeController);
+ mView.setClearAllFinishedWhilePanelExpandedRunnable(()-> {
+ final Runnable doCollapseRunnable = () ->
+ mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
+ mView.postDelayed(doCollapseRunnable, /* delayMillis = */ DELAY_BEFORE_SHADE_CLOSE);
+ });
mDumpManager.registerDumpable(mView);
mKeyguardBypassController.registerOnBypassStateChangedListener(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt
index 4de3a7f..3a650aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractor.kt
@@ -18,7 +18,7 @@
import android.graphics.Rect
import android.util.Log
import com.android.app.tracing.FlowTracing.traceEach
-import com.android.systemui.common.domain.interactor.ConfigurationInteractor
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_ON
@@ -28,6 +28,7 @@
import com.android.systemui.util.kotlin.area
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.kotlin.race
+import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.flow.Flow
@@ -38,7 +39,6 @@
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withTimeout
-import javax.inject.Inject
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/DisplaySwitchNotificationsHiderFlag.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/DisplaySwitchNotificationsHiderFlag.kt
index 98c1734..3cd9a8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/DisplaySwitchNotificationsHiderFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/DisplaySwitchNotificationsHiderFlag.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.stack.shared
import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
/** Helper for reading or using the DisplaySwitchNotificationsHider flag state. */
@@ -24,6 +25,10 @@
object DisplaySwitchNotificationsHiderFlag {
const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_HIDE_ON_DISPLAY_SWITCH
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
/** Is the hiding enabled? */
@JvmStatic
inline val isEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
index a62a1ed..e79f3ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
@@ -608,7 +608,7 @@
}
@Override
- public void onPulseExpansionChanged(boolean expandingChanged) {
+ public void onPulseExpansionAmountChanged(boolean expandingChanged) {
if (expandingChanged) {
updateAodIconsVisibility(true /* animate */, false /* force */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 0dabafb..f34a44a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -284,11 +284,22 @@
@Override
public String toString() {
- return "NotificationIconContainer("
- + "dozing=" + mDozing + " onLockScreen=" + mOnLockScreen
- + " overrideIconColor=" + mOverrideIconColor
- + " speedBumpIndex=" + mSpeedBumpIndex
- + " themedTextColorPrimary=#" + Integer.toHexString(mThemedTextColorPrimary) + ')';
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ return super.toString()
+ + " {"
+ + " overrideIconColor=" + mOverrideIconColor
+ + ", maxIcons=" + mMaxIcons
+ + ", isStaticLayout=" + mIsStaticLayout
+ + ", themedTextColorPrimary=#" + Integer.toHexString(mThemedTextColorPrimary)
+ + " }";
+ } else {
+ return "NotificationIconContainer("
+ + "dozing=" + mDozing + " onLockScreen=" + mOnLockScreen
+ + " overrideIconColor=" + mOverrideIconColor
+ + " speedBumpIndex=" + mSpeedBumpIndex
+ + " themedTextColorPrimary=#" + Integer.toHexString(mThemedTextColorPrimary)
+ + ')';
+ }
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index fa0cb5c..66bf527 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.policy;
import android.app.AlarmManager;
+import android.app.Flags;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -191,7 +192,11 @@
@Override
public void setZen(int zen, Uri conditionId, String reason) {
- mNoMan.setZenMode(zen, conditionId, reason);
+ if (Flags.modesApi()) {
+ mNoMan.setZenMode(zen, conditionId, reason, /* fromUser= */ true);
+ } else {
+ mNoMan.setZenMode(zen, conditionId, reason);
+ }
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 91219f0..0ee0939 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -151,6 +151,7 @@
)
udfpsOverlayInteractor =
UdfpsOverlayInteractor(
+ context,
authController,
selectedUserInteractor,
testScope.backgroundScope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
index bbf471c..6a68672 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
@@ -108,6 +108,7 @@
private fun createUdpfsOverlayInteractor() {
underTest =
UdfpsOverlayInteractor(
+ context,
authController,
selectedUserInteractor,
testScope.backgroundScope
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
index 863d9eb..fd5a584 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt
@@ -18,6 +18,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -64,7 +65,8 @@
init {
kosmos.deviceEntryIconViewModelTransitionsMock.add(testDeviceEntryIconTransition)
}
- val systemUIDialogManager = kosmos.systemUIDialogManager
+ private val systemUIDialogManager = kosmos.systemUIDialogManager
+ private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
private val underTest = kosmos.deviceEntryUdfpsTouchOverlayViewModel
@Captor
@@ -116,4 +118,16 @@
assertThat(shouldHandleTouches).isTrue()
}
+
+ @Test
+ fun deviceEntryViewAlphaZero_alternateBouncerVisible_shouldHandleTouchesTrue() =
+ testScope.runTest {
+ val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches)
+
+ testDeviceEntryIconTransitionAlpha.value = 0f
+ runCurrent()
+
+ bouncerRepository.setAlternateVisible(true)
+ assertThat(shouldHandleTouches).isTrue()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 7475235..6170e0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -128,6 +128,7 @@
)
udfpsOverlayInteractor =
UdfpsOverlayInteractor(
+ context,
authController,
selectedUserInteractor,
testScope.backgroundScope
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorTest.kt
deleted file mode 100644
index bfa3641..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/common/domain/interactor/ConfigurationInteractorTest.kt
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package com.android.systemui.common.domain.interactor
-
-import android.content.res.Configuration
-import android.graphics.Rect
-import android.testing.AndroidTestingRunner
-import android.view.Surface.ROTATION_0
-import android.view.Surface.ROTATION_90
-import android.view.Surface.Rotation
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
-import com.android.systemui.coroutines.collectValues
-import com.android.systemui.statusbar.policy.FakeConfigurationController
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-open class ConfigurationInteractorTest : SysuiTestCase() {
-
- private val testScope = TestScope()
-
- private val configurationController = FakeConfigurationController()
- private val configurationRepository =
- ConfigurationRepositoryImpl(
- configurationController,
- context,
- testScope.backgroundScope,
- mock()
- )
-
- private lateinit var configuration: Configuration
- private lateinit var underTest: ConfigurationInteractor
-
- @Before
- fun setUp() {
- configuration = context.resources.configuration
-
- val testableResources = context.getOrCreateTestableResources()
- testableResources.overrideConfiguration(configuration)
-
- underTest = ConfigurationInteractorImpl(configurationRepository)
- }
-
- @Test
- fun maxBoundsChange_emitsMaxBoundsChange() =
- testScope.runTest {
- val values by collectValues(underTest.naturalMaxBounds)
-
- updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
- runCurrent()
- updateDisplay(width = DISPLAY_WIDTH * 2, height = DISPLAY_HEIGHT * 3)
- runCurrent()
-
- assertThat(values)
- .containsExactly(
- Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT),
- Rect(0, 0, DISPLAY_WIDTH * 2, DISPLAY_HEIGHT * 3),
- )
- .inOrder()
- }
-
- @Test
- fun maxBoundsSameOnConfigChange_doesNotEmitMaxBoundsChange() =
- testScope.runTest {
- val values by collectValues(underTest.naturalMaxBounds)
-
- updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
- runCurrent()
- updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
- runCurrent()
-
- assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
- }
-
- @Test
- fun firstMaxBoundsChange_emitsMaxBoundsChange() =
- testScope.runTest {
- val values by collectValues(underTest.naturalMaxBounds)
-
- updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
- runCurrent()
-
- assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
- }
-
- @Test
- fun displayRotatedButMaxBoundsTheSame_doesNotEmitNewMaxBoundsChange() =
- testScope.runTest {
- val values by collectValues(underTest.naturalMaxBounds)
-
- updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
- runCurrent()
- updateDisplay(width = DISPLAY_HEIGHT, height = DISPLAY_WIDTH, rotation = ROTATION_90)
- runCurrent()
-
- assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
- }
-
- private fun updateDisplay(
- width: Int = DISPLAY_WIDTH,
- height: Int = DISPLAY_HEIGHT,
- @Rotation rotation: Int = ROTATION_0
- ) {
- configuration.windowConfiguration.maxBounds.set(Rect(0, 0, width, height))
- configuration.windowConfiguration.displayRotation = rotation
-
- configurationController.onConfigurationChanged(configuration)
- }
-
- private companion object {
- private const val DISPLAY_WIDTH = 100
- private const val DISPLAY_HEIGHT = 200
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
index c5c0208..9e007e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
@@ -16,14 +16,19 @@
package com.android.systemui.common.ui.domain.interactor
+import android.content.res.Configuration
+import android.graphics.Rect
+import android.view.Surface
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -35,13 +40,16 @@
@RunWith(AndroidJUnit4::class)
class ConfigurationInteractorTest : SysuiTestCase() {
private lateinit var testScope: TestScope
+ private lateinit var configuration: Configuration
private lateinit var underTest: ConfigurationInteractor
private lateinit var configurationRepository: FakeConfigurationRepository
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
-
+ configuration = context.resources.configuration
+ val testableResources = context.getOrCreateTestableResources()
+ testableResources.overrideConfiguration(configuration)
configurationRepository = FakeConfigurationRepository()
testScope = TestScope()
underTest = ConfigurationInteractor(configurationRepository)
@@ -79,4 +87,79 @@
assertThat(dimensionPixelSizes!![resourceId1]).isEqualTo(pixelSize1)
assertThat(dimensionPixelSizes!![resourceId2]).isEqualTo(pixelSize2)
}
+
+ @Test
+ fun maxBoundsChange_emitsMaxBoundsChange() =
+ testScope.runTest {
+ val values by collectValues(underTest.naturalMaxBounds)
+
+ updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
+ runCurrent()
+ updateDisplay(width = DISPLAY_WIDTH * 2, height = DISPLAY_HEIGHT * 3)
+ runCurrent()
+
+ assertThat(values)
+ .containsExactly(
+ Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT),
+ Rect(0, 0, DISPLAY_WIDTH * 2, DISPLAY_HEIGHT * 3),
+ )
+ .inOrder()
+ }
+
+ @Test
+ fun maxBoundsSameOnConfigChange_doesNotEmitMaxBoundsChange() =
+ testScope.runTest {
+ val values by collectValues(underTest.naturalMaxBounds)
+
+ updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
+ runCurrent()
+ updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
+ runCurrent()
+
+ assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
+ }
+
+ @Test
+ fun firstMaxBoundsChange_emitsMaxBoundsChange() =
+ testScope.runTest {
+ val values by collectValues(underTest.naturalMaxBounds)
+
+ updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
+ runCurrent()
+
+ assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
+ }
+
+ @Test
+ fun displayRotatedButMaxBoundsTheSame_doesNotEmitNewMaxBoundsChange() =
+ testScope.runTest {
+ val values by collectValues(underTest.naturalMaxBounds)
+
+ updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT)
+ runCurrent()
+ updateDisplay(
+ width = DISPLAY_HEIGHT,
+ height = DISPLAY_WIDTH,
+ rotation = Surface.ROTATION_90
+ )
+ runCurrent()
+
+ assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT))
+ }
+
+ private fun updateDisplay(
+ width: Int = DISPLAY_WIDTH,
+ height: Int = DISPLAY_HEIGHT,
+ @Surface.Rotation rotation: Int = Surface.ROTATION_0
+ ) {
+ configuration.windowConfiguration.maxBounds.set(Rect(0, 0, width, height))
+ configuration.windowConfiguration.displayRotation = rotation
+
+ configurationRepository.onConfigurationChange(configuration)
+ }
+
+ private companion object {
+ private const val DISPLAY_WIDTH = 100
+ private const val DISPLAY_HEIGHT = 200
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index 22569e2..c864704 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -30,20 +30,15 @@
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
-import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
-import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.statusbar.gesture.TapGestureDetector
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -87,11 +82,6 @@
{ mock(DeviceEntryForegroundViewModel::class.java) },
{ mock(DeviceEntryBackgroundViewModel::class.java) },
{ falsingManager },
- { mock(AlternateBouncerViewModel::class.java) },
- { mock(NotificationShadeWindowController::class.java) },
- TestScope().backgroundScope,
- { mock(SwipeUpAnywhereGestureHandler::class.java) },
- { mock(TapGestureDetector::class.java) },
{ mock(VibratorHelper::class.java) },
)
}
@@ -177,21 +167,4 @@
assertThat(constraint.layout.topMargin).isEqualTo(5)
assertThat(constraint.layout.startMargin).isEqualTo(4)
}
-
- @Test
- fun deviceEntryIconViewIsAboveAlternateBouncerView() {
- mSetFlagsRule.enableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
-
- val constraintLayout = ConstraintLayout(context, null)
- underTest.addViews(constraintLayout)
- assertThat(constraintLayout.childCount).isGreaterThan(0)
- val deviceEntryIconView = constraintLayout.getViewById(R.id.device_entry_icon_view)
- val alternateBouncerView = constraintLayout.getViewById(R.id.alternate_bouncer)
- assertThat(deviceEntryIconView).isNotNull()
- assertThat(alternateBouncerView).isNotNull()
-
- // device entry icon is above the alternate bouncer
- assertThat(constraintLayout.indexOfChild(deviceEntryIconView))
- .isGreaterThan(constraintLayout.indexOfChild(alternateBouncerView))
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index 1f99303..0a464e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -22,11 +22,11 @@
import android.testing.TestableLooper
import android.view.View
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.models.player.MediaViewHolder
import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
import com.android.systemui.media.controls.util.MediaFlags
+import com.android.systemui.res.R
import com.android.systemui.util.animation.MeasurementInput
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.animation.TransitionViewState
@@ -37,6 +37,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.floatThat
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
@@ -183,10 +184,12 @@
// detail widgets occupy [90, 100]
whenever(detailWidgetState.y).thenReturn(90F)
whenever(detailWidgetState.height).thenReturn(10)
+ whenever(detailWidgetState.alpha).thenReturn(1F)
// control widgets occupy [150, 170]
whenever(controlWidgetState.y).thenReturn(150F)
whenever(controlWidgetState.height).thenReturn(20)
- // in current beizer, when the progress reach 0.38, the result will be 0.5
+ whenever(controlWidgetState.alpha).thenReturn(1F)
+ // in current bezier, when the progress reach 0.38, the result will be 0.5
mediaViewController.squishViewState(mockViewState, 181.4F / 200F)
verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
@@ -196,6 +199,34 @@
}
@Test
+ fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_invisibleElements() {
+ whenever(mockViewState.copy()).thenReturn(mockCopiedState)
+ whenever(mockCopiedState.widgetStates)
+ .thenReturn(
+ mutableMapOf(
+ R.id.media_progress_bar to controlWidgetState,
+ R.id.header_artist to detailWidgetState
+ )
+ )
+ whenever(mockCopiedState.measureHeight).thenReturn(200)
+ // detail widgets occupy [90, 100]
+ whenever(detailWidgetState.y).thenReturn(90F)
+ whenever(detailWidgetState.height).thenReturn(10)
+ whenever(detailWidgetState.alpha).thenReturn(0F)
+ // control widgets occupy [150, 170]
+ whenever(controlWidgetState.y).thenReturn(150F)
+ whenever(controlWidgetState.height).thenReturn(20)
+ whenever(controlWidgetState.alpha).thenReturn(0F)
+ // Verify that alpha remains 0 throughout squishing
+ mediaViewController.squishViewState(mockViewState, 181.4F / 200F)
+ verify(controlWidgetState, never()).alpha = floatThat { it > 0 }
+ verify(detailWidgetState, never()).alpha = floatThat { it > 0 }
+ mediaViewController.squishViewState(mockViewState, 200F / 200F)
+ verify(controlWidgetState, never()).alpha = floatThat { it > 0 }
+ verify(detailWidgetState, never()).alpha = floatThat { it > 0 }
+ }
+
+ @Test
fun testSquishViewState_applySquishFraction_toTransitionViewState_alpha_forRecommendation() {
whenever(mockViewState.copy()).thenReturn(mockCopiedState)
whenever(mockCopiedState.widgetStates)
@@ -210,12 +241,15 @@
// media container widgets occupy [20, 300]
whenever(mediaContainerWidgetState.y).thenReturn(20F)
whenever(mediaContainerWidgetState.height).thenReturn(280)
+ whenever(mediaContainerWidgetState.alpha).thenReturn(1F)
// media title widgets occupy [320, 330]
whenever(mediaTitleWidgetState.y).thenReturn(320F)
whenever(mediaTitleWidgetState.height).thenReturn(10)
+ whenever(mediaTitleWidgetState.alpha).thenReturn(1F)
// media subtitle widgets occupy [340, 350]
whenever(mediaSubTitleWidgetState.y).thenReturn(340F)
whenever(mediaSubTitleWidgetState.height).thenReturn(10)
+ whenever(mediaSubTitleWidgetState.alpha).thenReturn(1F)
// in current beizer, when the progress reach 0.38, the result will be 0.5
mediaViewController.squishViewState(mockViewState, 307.6F / 360F)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
index b90ccc0..9941661 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,35 +16,23 @@
package com.android.systemui.scene.shared.flag
-import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.FakeFeatureFlagsImpl
-import com.android.systemui.Flags as AconfigFlags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.compose.ComposeFacade
import com.android.systemui.flags.Flags
-import com.android.systemui.flags.ReleasedFlag
-import com.android.systemui.flags.ResourceBooleanFlag
-import com.android.systemui.flags.UnreleasedFlag
+import com.android.systemui.flags.setFlagValue
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
-import com.android.systemui.res.R
-import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth
+import org.junit.Assume
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
@SmallTest
-@RunWith(Parameterized::class)
-internal class SceneContainerFlagsTest(
- private val testCase: TestCase,
-) : SysuiTestCase() {
-
- @Rule @JvmField val setFlagsRule: SetFlagsRule = SetFlagsRule()
-
- private lateinit var underTest: SceneContainerFlags
+@RunWith(AndroidJUnit4::class)
+internal class SceneContainerFlagsTest : SysuiTestCase() {
@Before
fun setUp() {
@@ -52,83 +40,39 @@
// Flags.SCENE_CONTAINER_ENABLED is no longer needed.
val field = Flags::class.java.getField("SCENE_CONTAINER_ENABLED")
field.isAccessible = true
- field.set(null, true)
+ field.set(null, true) // note: this does not work with multivalent tests
+ }
- val featureFlags =
- FakeFeatureFlagsClassic().apply {
- SceneContainerFlagsImpl.classicFlagTokens.forEach { flagToken ->
- when (flagToken) {
- is ResourceBooleanFlag -> set(flagToken, testCase.areAllFlagsSet)
- is ReleasedFlag -> set(flagToken, testCase.areAllFlagsSet)
- is UnreleasedFlag -> set(flagToken, testCase.areAllFlagsSet)
- else -> error("Unsupported flag type ${flagToken.javaClass}")
- }
- }
- }
- // TODO(b/306421592): get the aconfig FeatureFlags from the SetFlagsRule.
- val aconfigFlags = FakeFeatureFlagsImpl()
-
+ private fun setAconfigFlagsEnabled(enabled: Boolean) {
listOf(
- AconfigFlags.FLAG_SCENE_CONTAINER,
- AconfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
+ com.android.systemui.Flags.FLAG_SCENE_CONTAINER,
+ com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
KeyguardShadeMigrationNssl.FLAG_NAME,
MediaInSceneContainerFlag.FLAG_NAME,
)
- .forEach { flagToken ->
- setFlagsRule.enableFlags(flagToken)
- aconfigFlags.setFlag(flagToken, testCase.areAllFlagsSet)
- overrideResource(
- R.bool.config_sceneContainerFrameworkEnabled,
- testCase.isResourceConfigEnabled
- )
- }
-
- underTest =
- SceneContainerFlagsImpl(
- context = context,
- featureFlagsClassic = featureFlags,
- isComposeAvailable = testCase.isComposeAvailable,
- )
+ .forEach { flagName -> mSetFlagsRule.setFlagValue(flagName, enabled) }
}
@Test
- fun isEnabled() {
- assertThat(underTest.isEnabled()).isEqualTo(testCase.expectedEnabled)
+ fun isNotEnabled_withoutAconfigFlags() {
+ setAconfigFlagsEnabled(false)
+ Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(false)
+ Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(false)
}
- internal data class TestCase(
- val isComposeAvailable: Boolean,
- val areAllFlagsSet: Boolean,
- val isResourceConfigEnabled: Boolean,
- val expectedEnabled: Boolean,
- ) {
- override fun toString(): String {
- return "(compose=$isComposeAvailable + flags=$areAllFlagsSet) + XML" +
- " config=$isResourceConfigEnabled -> expected=$expectedEnabled"
- }
+ @Test
+ fun isEnabled_withAconfigFlags_withCompose() {
+ Assume.assumeTrue(ComposeFacade.isComposeAvailable())
+ setAconfigFlagsEnabled(true)
+ Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(true)
+ Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(true)
}
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun testCases() = buildList {
- repeat(8) { combination ->
- val isComposeAvailable = combination and 0b100 != 0
- val areAllFlagsSet = combination and 0b010 != 0
- val isResourceConfigEnabled = combination and 0b001 != 0
-
- val expectedEnabled =
- isComposeAvailable && areAllFlagsSet && isResourceConfigEnabled
-
- add(
- TestCase(
- isComposeAvailable = isComposeAvailable,
- areAllFlagsSet = areAllFlagsSet,
- expectedEnabled = expectedEnabled,
- isResourceConfigEnabled = isResourceConfigEnabled,
- )
- )
- }
- }
+ @Test
+ fun isNotEnabled_withAconfigFlags_withoutCompose() {
+ Assume.assumeFalse(ComposeFacade.isComposeAvailable())
+ setAconfigFlagsEnabled(true)
+ Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(false)
+ Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(false)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 9d997da..86d8d54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -33,7 +33,6 @@
import com.android.keyguard.dagger.KeyguardBouncerComponent
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
-import com.android.systemui.back.domain.interactor.BackActionInteractor
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
import com.android.systemui.bouncer.data.repository.BouncerMessageRepositoryImpl
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
@@ -68,9 +67,12 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.log.BouncerLogger
-import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.DragDownHelper
@@ -79,6 +81,7 @@
import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.gesture.TapGestureDetector
import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
import com.android.systemui.statusbar.notification.stack.AmbientState
@@ -93,6 +96,7 @@
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
@@ -126,8 +130,6 @@
@Mock private lateinit var centralSurfaces: CentralSurfaces
@Mock private lateinit var dozeServiceHost: DozeServiceHost
@Mock private lateinit var dozeScrimController: DozeScrimController
- @Mock private lateinit var backActionInteractor: BackActionInteractor
- @Mock private lateinit var powerInteractor: PowerInteractor
@Mock private lateinit var dockManager: DockManager
@Mock private lateinit var notificationPanelViewController: NotificationPanelViewController
@Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
@@ -275,6 +277,12 @@
primaryBouncerInteractor,
alternateBouncerInteractor,
mSelectedUserInteractor,
+ { mock (JavaAdapter::class.java )},
+ { mock(AlternateBouncerViewModel::class.java) },
+ { mock(FalsingManager::class.java) },
+ { mock(SwipeUpAnywhereGestureHandler::class.java) },
+ { mock(TapGestureDetector::class.java) },
+ { mock(AlternateBouncerUdfpsIconViewModel::class.java) },
)
underTest.setupExpandedStatusBar()
underTest.setDragDownHelper(dragDownHelper)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 9750f60..d9ff892 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -55,8 +55,12 @@
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.log.BouncerLogger
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.DragDownHelper
@@ -65,6 +69,7 @@
import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.gesture.TapGestureDetector
import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
import com.android.systemui.statusbar.notification.stack.AmbientState
@@ -79,12 +84,14 @@
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
@@ -99,6 +106,7 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@ExperimentalCoroutinesApi
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
@@ -259,6 +267,12 @@
primaryBouncerInteractor,
alternateBouncerInteractor,
mSelectedUserInteractor,
+ { Mockito.mock(JavaAdapter::class.java) },
+ { Mockito.mock(AlternateBouncerViewModel::class.java) },
+ { Mockito.mock(FalsingManager::class.java) },
+ { Mockito.mock(SwipeUpAnywhereGestureHandler::class.java) },
+ { Mockito.mock(TapGestureDetector::class.java) },
+ { Mockito.mock(AlternateBouncerUdfpsIconViewModel::class.java) },
)
controller.setupExpandedStatusBar()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
index a6180ec..f178046 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconViewTest.java
@@ -54,6 +54,7 @@
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.NotificationContentDescription;
import org.junit.Before;
import org.junit.Rule;
@@ -183,7 +184,7 @@
.build();
// should be ApplicationInfo
n.extras.putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, new Bundle());
- StatusBarIconView.contentDescForNotification(mContext, n);
+ NotificationContentDescription.contentDescForNotification(mContext, n);
// no crash, good
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt
index f3094cd..170f651 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationsKeyguardViewStateRepositoryTest.kt
@@ -80,7 +80,7 @@
assertThat(isPulseExpanding).isFalse()
withArgCaptor { verify(mockWakeUpCoordinator).addListener(capture()) }
- .onPulseExpansionChanged(true)
+ .onPulseExpandingChanged(true)
runCurrent()
assertThat(isPulseExpanding).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index ba5ba2c..ad7dee3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -82,7 +82,6 @@
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
-import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -116,7 +115,6 @@
private AmbientState mAmbientState;
private TestableResources mTestableResources;
@Rule public MockitoRule mockito = MockitoJUnit.rule();
- @Mock private NotificationsController mNotificationsController;
@Mock private SysuiStatusBarStateController mBarState;
@Mock private GroupMembershipManager mGroupMembershipManger;
@Mock private GroupExpansionManager mGroupExpansionManager;
@@ -193,7 +191,7 @@
mStackScrollerInternal.initView(getContext(), mNotificationSwipeHelper,
mNotificationStackSizeCalculator);
mStackScroller = spy(mStackScrollerInternal);
- mStackScroller.setNotificationsController(mNotificationsController);
+ mStackScroller.setResetUserExpandedStatesRunnable(()->{});
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true);
when(mStackScrollLayoutController.getNotificationRoundnessManager())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
index ac20683..4bfd7e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
@@ -23,8 +23,8 @@
import android.view.Surface.ROTATION_90
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.domain.interactor.ConfigurationInteractorImpl
import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.coroutines.collectValues
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -81,7 +81,7 @@
testScope.backgroundScope,
mock()
)
- private val configurationInteractor = ConfigurationInteractorImpl(configurationRepository)
+ private val configurationInteractor = ConfigurationInteractor(configurationRepository)
private lateinit var configuration: Configuration
private lateinit var underTest: HideNotificationsInteractor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index 21774aa..c17a8ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -28,7 +28,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.TestMocksModule
import com.android.systemui.collectLastValue
-import com.android.systemui.common.domain.CommonDomainLayerModule
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FakeFeatureFlagsClassicModule
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -69,7 +68,6 @@
[
SysUITestModule::class,
ActivatableNotificationViewModelModule::class,
- CommonDomainLayerModule::class,
FooterViewModelModule::class,
HeadlessSystemUserModeModule::class,
UnfoldTransitionModule.Bindings::class,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index 0f779d9..44fa132 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -16,7 +16,8 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
-import android.platform.test.flag.junit.SetFlagsRule
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
@@ -73,8 +74,6 @@
class MobileIconViewModelTest : SysuiTestCase() {
private var connectivityRepository = FakeConnectivityRepository()
- private val setFlagsRule = SetFlagsRule()
-
private lateinit var underTest: MobileIconViewModel
private lateinit var interactor: MobileIconInteractorImpl
private lateinit var iconsInteractor: MobileIconsInteractorImpl
@@ -561,11 +560,9 @@
}
@Test
+ @DisableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
fun dataActivity_configOn_testIndicators_staticFlagOff() =
testScope.runTest {
- // GIVEN STATUS_BAR_STATIC_NETWORK_INDICATORS flag is off
- setFlagsRule.disableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
-
// Create a new view model here so the constants are properly read
whenever(constants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
@@ -618,11 +615,9 @@
}
@Test
+ @EnableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
fun dataActivity_configOn_testIndicators_staticFlagOn() =
testScope.runTest {
- // GIVEN STATUS_BAR_STATIC_NETWORK_INDICATORS flag is on
- setFlagsRule.enableFlags(FLAG_STATUS_BAR_STATIC_INOUT_INDICATORS)
-
// Create a new view model here so the constants are properly read
whenever(constants.shouldShowActivityConfig).thenReturn(true)
createAndSetViewModel()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt
index 5475659..8b1a1d9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelKosmos.kt
@@ -16,14 +16,18 @@
package com.android.systemui.biometrics.ui.viewmodel
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.keyguard.ui.viewmodel.deviceEntryIconViewModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.statusbar.phone.systemUIDialogManager
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+@ExperimentalCoroutinesApi
val Kosmos.deviceEntryUdfpsTouchOverlayViewModel by Fixture {
DeviceEntryUdfpsTouchOverlayViewModel(
deviceEntryIconViewModel = deviceEntryIconViewModel,
+ alternateBouncerInteractor = alternateBouncerInteractor,
systemUIDialogManager = systemUIDialogManager,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
new file mode 100644
index 0000000..86a4509
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.domain.interactor
+
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.statusbar.policy.KeyguardStateControllerImpl
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.fakeSystemClock
+
+var Kosmos.alternateBouncerInteractor by
+ Kosmos.Fixture {
+ AlternateBouncerInteractor(
+ statusBarStateController = statusBarStateController,
+ keyguardStateController = mock<KeyguardStateControllerImpl>(),
+ bouncerRepository = keyguardBouncerRepository,
+ fingerprintPropertyRepository = fingerprintPropertyRepository,
+ biometricSettingsRepository = biometricSettingsRepository,
+ systemClock = fakeSystemClock,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
+ scope = testScope.backgroundScope,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
index 6b38d6e..050c2c9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
@@ -37,7 +37,8 @@
MutableSharedFlow<Unit>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
override val onConfigurationChange: Flow<Unit> = _onConfigurationChange.asSharedFlow()
- private val _configurationChangeValues = MutableSharedFlow<Configuration>()
+ private val _configurationChangeValues =
+ MutableSharedFlow<Configuration>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
override val configurationValues: Flow<Configuration> =
_configurationChangeValues.asSharedFlow()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..d9c6e4f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.alternateBouncerToAodTransitionViewModel by Fixture {
+ AlternateBouncerToAodTransitionViewModel(
+ interactor = keyguardTransitionInteractor,
+ deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt
new file mode 100644
index 0000000..e4821b0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModelKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.alternateBouncerToGoneTransitionViewModel by Fixture {
+ AlternateBouncerToGoneTransitionViewModel(
+ interactor = keyguardTransitionInteractor,
+ bouncerToGoneFlows = bouncerToGoneFlows,
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
index 6557bcf..67e9289 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
@@ -41,7 +41,6 @@
transitionInteractor = keyguardTransitionInteractor,
keyguardInteractor = keyguardInteractor,
viewModel = aodToLockscreenTransitionViewModel,
- shadeDependentFlows = shadeDependentFlows,
sceneContainerFlags = sceneContainerFlags,
keyguardViewController = { statusBarKeyguardViewManager },
deviceEntryInteractor = deviceEntryInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
new file mode 100644
index 0000000..d705248
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.custom
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileDefaultsRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTilePackageUpdatesRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.FakePackageManagerAdapterFacade
+
+var Kosmos.tileSpec: TileSpec.CustomTileSpec by Kosmos.Fixture()
+
+val Kosmos.customTileStatePersister: FakeCustomTileStatePersister by
+ Kosmos.Fixture { FakeCustomTileStatePersister() }
+
+val Kosmos.customTileRepository: FakeCustomTileRepository by
+ Kosmos.Fixture {
+ FakeCustomTileRepository(
+ customTileStatePersister,
+ packageManagerAdapterFacade,
+ testScope.testScheduler,
+ )
+ }
+
+val Kosmos.customTileDefaultsRepository: FakeCustomTileDefaultsRepository by
+ Kosmos.Fixture { FakeCustomTileDefaultsRepository() }
+
+val Kosmos.customTilePackagesUpdatesRepository: FakeCustomTilePackageUpdatesRepository by
+ Kosmos.Fixture { FakeCustomTilePackageUpdatesRepository() }
+
+val Kosmos.packageManagerAdapterFacade: FakePackageManagerAdapterFacade by
+ Kosmos.Fixture { FakePackageManagerAdapterFacade(tileSpec) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt
index ccf0391..ba803d8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt
@@ -19,21 +19,21 @@
import android.os.UserHandle
import android.service.quicksettings.Tile
import com.android.systemui.qs.external.FakeCustomTileStatePersister
-import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.flow.Flow
class FakeCustomTileRepository(
- tileSpec: TileSpec.CustomTileSpec,
customTileStatePersister: FakeCustomTileStatePersister,
+ private val packageManagerAdapterFacade: FakePackageManagerAdapterFacade,
testBackgroundContext: CoroutineContext,
) : CustomTileRepository {
private val realDelegate: CustomTileRepository =
CustomTileRepositoryImpl(
- tileSpec,
+ packageManagerAdapterFacade.tileSpec,
customTileStatePersister,
+ packageManagerAdapterFacade.packageManagerAdapter,
testBackgroundContext,
)
@@ -44,6 +44,10 @@
override fun getTile(user: UserHandle): Tile? = realDelegate.getTile(user)
+ override suspend fun isTileActive(): Boolean = realDelegate.isTileActive()
+
+ override suspend fun isTileToggleable(): Boolean = realDelegate.isTileToggleable()
+
override suspend fun updateWithTile(
user: UserHandle,
newTile: Tile,
@@ -55,4 +59,8 @@
defaults: CustomTileDefaults,
isPersistable: Boolean,
) = realDelegate.updateWithDefaults(user, defaults, isPersistable)
+
+ fun setTileActive(isActive: Boolean) = packageManagerAdapterFacade.setIsActive(isActive)
+
+ fun setTileToggleable(isActive: Boolean) = packageManagerAdapterFacade.setIsToggleable(isActive)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
new file mode 100644
index 0000000..c9a7655
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.custom.data.repository
+
+import android.content.pm.ServiceInfo
+import android.os.Bundle
+import com.android.systemui.qs.external.PackageManagerAdapter
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+class FakePackageManagerAdapterFacade(
+ val tileSpec: TileSpec.CustomTileSpec,
+ val packageManagerAdapter: PackageManagerAdapter = mock {},
+) {
+
+ private var isToggleable: Boolean = false
+ private var isActive: Boolean = false
+
+ init {
+ whenever(packageManagerAdapter.getServiceInfo(eq(tileSpec.componentName), any()))
+ .thenAnswer {
+ ServiceInfo().apply {
+ metaData =
+ Bundle().apply {
+ putBoolean(
+ android.service.quicksettings.TileService.META_DATA_TOGGLEABLE_TILE,
+ isToggleable
+ )
+ putBoolean(
+ android.service.quicksettings.TileService.META_DATA_ACTIVE_TILE,
+ isActive
+ )
+ }
+ }
+ }
+ }
+
+ fun setIsActive(isActive: Boolean) {
+ this.isActive = isActive
+ }
+
+ fun setIsToggleable(isToggleable: Boolean) {
+ this.isToggleable = isToggleable
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorKosmos.kt
index baca8b2..4232b27 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorKosmos.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.stack.domain.interactor
-import com.android.systemui.common.domain.interactor.configurationInteractor
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.power.domain.interactor.powerInteractor
diff --git a/services/core/Android.bp b/services/core/Android.bp
index b5c9ffd..5111b08 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -103,7 +103,6 @@
"android.hardware.power-java_shared",
],
srcs: [
- ":android.hardware.biometrics.face-V4-java-source",
":android.hardware.tv.hdmi.connection-V1-java-source",
":android.hardware.tv.hdmi.earc-V1-java-source",
":statslog-art-java-gen",
diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
index 2ba3a1d..1d1e2d9 100644
--- a/services/core/java/com/android/server/VpnManagerService.java
+++ b/services/core/java/com/android/server/VpnManagerService.java
@@ -30,7 +30,6 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
-import android.net.ConnectivityManager;
import android.net.INetd;
import android.net.IVpnManager;
import android.net.Network;
@@ -89,8 +88,6 @@
private final Context mUserAllContext;
private final Dependencies mDeps;
-
- private final ConnectivityManager mCm;
private final VpnProfileStore mVpnProfileStore;
private final INetworkManagementService mNMS;
private final INetd mNetd;
@@ -164,7 +161,6 @@
mHandler = mHandlerThread.getThreadHandler();
mVpnProfileStore = mDeps.getVpnProfileStore();
mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
- mCm = mContext.getSystemService(ConnectivityManager.class);
mNMS = mDeps.getINetworkManagementService();
mNetd = mDeps.getNetd();
mUserManager = mContext.getSystemService(UserManager.class);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index df8f17a..3ae55271 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -70,7 +70,6 @@
import static android.os.PowerExemptionManager.REASON_OPT_OUT_REQUESTED;
import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_PLATFORM_VPN;
import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_VPN;
-import static android.os.PowerExemptionManager.REASON_OTHER;
import static android.os.PowerExemptionManager.REASON_PACKAGE_INSTALLER;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
@@ -127,7 +126,6 @@
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import android.Manifest;
-import android.Manifest.permission;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1085,7 +1083,7 @@
// Use that as a shortcut if possible to avoid having to recheck all the conditions.
final boolean whileInUseAllowsUiJobScheduling =
ActivityManagerService.doesReasonCodeAllowSchedulingUserInitiatedJobs(
- r.getFgsAllowWIU());
+ r.getFgsAllowWiu_forStart());
r.updateAllowUiJobScheduling(whileInUseAllowsUiJobScheduling
|| mAm.canScheduleUserInitiatedJobs(callingUid, callingPid, callingPackage));
} else {
@@ -2320,7 +2318,7 @@
// If the foreground service is not started from TOP process, do not allow it to
// have while-in-use location/camera/microphone access.
- if (!r.isFgsAllowedWIU()) {
+ if (!r.isFgsAllowedWiu_forCapabilities()) {
Slog.w(TAG,
"Foreground service started from background can not have "
+ "location/camera/microphone access: service "
@@ -2436,7 +2434,7 @@
// mAllowWhileInUsePermissionInFgs.
r.mAllowStartForegroundAtEntering = r.getFgsAllowStart();
r.mAllowWhileInUsePermissionInFgsAtEntering =
- r.isFgsAllowedWIU();
+ r.isFgsAllowedWiu_forCapabilities();
r.mStartForegroundCount++;
r.mFgsEnterTime = SystemClock.uptimeMillis();
if (!stopProcStatsOp) {
@@ -2632,7 +2630,7 @@
policy.getForegroundServiceTypePolicyInfo(type, defaultToType);
final @ForegroundServicePolicyCheckCode int code = policy.checkForegroundServiceTypePolicy(
mAm.mContext, r.packageName, r.app.uid, r.app.getPid(),
- r.isFgsAllowedWIU(), policyInfo);
+ r.isFgsAllowedWiu_forStart(), policyInfo);
RuntimeException exception = null;
switch (code) {
case FGS_TYPE_POLICY_CHECK_DEPRECATED: {
@@ -7580,78 +7578,76 @@
* @param callingUid caller app's uid.
* @param intent intent to start/bind service.
* @param r the service to start.
- * @param isBindService True if it's called from bindService().
+ * @param inBindService True if it's called from bindService().
* @param forBoundFgs set to true if it's called from Service.startForeground() for a
* service that's not started but bound.
- * @return true if allow, false otherwise.
*/
private void setFgsRestrictionLocked(String callingPackage,
int callingPid, int callingUid, Intent intent, ServiceRecord r, int userId,
- BackgroundStartPrivileges backgroundStartPrivileges, boolean isBindService,
+ BackgroundStartPrivileges backgroundStartPrivileges, boolean inBindService,
boolean forBoundFgs) {
- @ReasonCode int allowWIU;
+ @ReasonCode int allowWiu;
@ReasonCode int allowStart;
// If called from bindService(), do not update the actual fields, but instead
// keep it in a separate set of fields.
- if (isBindService) {
- allowWIU = r.mAllowWIUInBindService;
- allowStart = r.mAllowStartInBindService;
+ if (inBindService) {
+ allowWiu = r.mAllowWiu_inBindService;
+ allowStart = r.mAllowStart_inBindService;
} else {
- allowWIU = r.mAllowWhileInUsePermissionInFgsReasonNoBinding;
- allowStart = r.mAllowStartForegroundNoBinding;
+ allowWiu = r.mAllowWiu_noBinding;
+ allowStart = r.mAllowStart_noBinding;
}
- // Check DeviceConfig flag.
- if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
- if (allowWIU == REASON_DENIED) {
- // BGFGS start restrictions are disabled. We're allowing while-in-use permissions.
- // Note REASON_OTHER since there's no other suitable reason.
- allowWIU = REASON_OTHER;
- }
- }
-
- if ((allowWIU == REASON_DENIED)
- || (allowStart == REASON_DENIED)) {
+ if ((allowWiu == REASON_DENIED) || (allowStart == REASON_DENIED)) {
@ReasonCode final int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges);
// We store them to compare the old and new while-in-use logics to each other.
// (They're not used for any other purposes.)
- if (allowWIU == REASON_DENIED) {
- allowWIU = allowWhileInUse;
+ if (allowWiu == REASON_DENIED) {
+ allowWiu = allowWhileInUse;
}
if (allowStart == REASON_DENIED) {
allowStart = shouldAllowFgsStartForegroundWithBindingCheckLocked(
allowWhileInUse, callingPackage, callingPid, callingUid, intent, r,
- backgroundStartPrivileges, isBindService);
+ backgroundStartPrivileges, inBindService);
}
}
- if (isBindService) {
- r.mAllowWIUInBindService = allowWIU;
- r.mAllowStartInBindService = allowStart;
+ if (inBindService) {
+ r.mAllowWiu_inBindService = allowWiu;
+ r.mAllowStart_inBindService = allowStart;
} else {
if (!forBoundFgs) {
- // This is for "normal" situation.
- r.mAllowWhileInUsePermissionInFgsReasonNoBinding = allowWIU;
- r.mAllowStartForegroundNoBinding = allowStart;
+ // This is for "normal" situation -- either:
+ // - in Context.start[Foreground]Service()
+ // - or, in Service.startForeground() on a started service.
+ r.mAllowWiu_noBinding = allowWiu;
+ r.mAllowStart_noBinding = allowStart;
} else {
- // This logic is only for logging, so we only update the "by-binding" fields.
- if (r.mAllowWIUByBindings == REASON_DENIED) {
- r.mAllowWIUByBindings = allowWIU;
+ // Service.startForeground() is called on a service that's not started, but bound.
+ // In this case, we set them to "byBindings", not "noBinding", because
+ // we don't want to use them when we calculate the "legacy" code.
+ //
+ // We don't want to set them to "no binding" codes, because on U-QPR1 and below,
+ // we didn't call setFgsRestrictionLocked() in the code path which sets
+ // forBoundFgs to true, and we wanted to preserve the original behavior in other
+ // places to compare the legacy and new logic.
+ if (r.mAllowWiu_byBindings == REASON_DENIED) {
+ r.mAllowWiu_byBindings = allowWiu;
}
- if (r.mAllowStartByBindings == REASON_DENIED) {
- r.mAllowStartByBindings = allowStart;
+ if (r.mAllowStart_byBindings == REASON_DENIED) {
+ r.mAllowStart_byBindings = allowStart;
}
}
// Also do a binding client check, unless called from bindService().
- if (r.mAllowWIUByBindings == REASON_DENIED) {
- r.mAllowWIUByBindings =
+ if (r.mAllowWiu_byBindings == REASON_DENIED) {
+ r.mAllowWiu_byBindings =
shouldAllowFgsWhileInUsePermissionByBindingsLocked(callingUid);
}
- if (r.mAllowStartByBindings == REASON_DENIED) {
- r.mAllowStartByBindings = r.mAllowWIUByBindings;
+ if (r.mAllowStart_byBindings == REASON_DENIED) {
+ r.mAllowStart_byBindings = r.mAllowWiu_byBindings;
}
}
}
@@ -7660,13 +7656,13 @@
* Reset various while-in-use and BFSL related information.
*/
void resetFgsRestrictionLocked(ServiceRecord r) {
- r.clearFgsAllowWIU();
+ r.clearFgsAllowWiu();
r.clearFgsAllowStart();
r.mInfoAllowStartForeground = null;
r.mInfoTempFgsAllowListReason = null;
r.mLoggedInfoAllowStartForeground = false;
- r.updateAllowUiJobScheduling(r.isFgsAllowedWIU());
+ r.updateAllowUiJobScheduling(r.isFgsAllowedWiu_forStart());
}
boolean canStartForegroundServiceLocked(int callingPid, int callingUid, String callingPackage) {
@@ -8284,7 +8280,8 @@
allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgsAtEntering;
fgsStartReasonCode = r.mAllowStartForegroundAtEntering;
} else {
- allowWhileInUsePermissionInFgs = r.isFgsAllowedWIU();
+ // TODO: Also log "forStart"
+ allowWhileInUsePermissionInFgs = r.isFgsAllowedWiu_forCapabilities();
fgsStartReasonCode = r.getFgsAllowStart();
}
final int callerTargetSdkVersion = r.mRecentCallerApplicationInfo != null
@@ -8323,12 +8320,12 @@
mAm.getUidProcessCapabilityLocked(r.mRecentCallingUid),
0,
0,
- r.mAllowWhileInUsePermissionInFgsReasonNoBinding,
- r.mAllowWIUInBindService,
- r.mAllowWIUByBindings,
- r.mAllowStartForegroundNoBinding,
- r.mAllowStartInBindService,
- r.mAllowStartByBindings,
+ r.mAllowWiu_noBinding,
+ r.mAllowWiu_inBindService,
+ r.mAllowWiu_byBindings,
+ r.mAllowStart_noBinding,
+ r.mAllowStart_inBindService,
+ r.mAllowStart_byBindings,
fgsStartApi,
fgsRestrictionRecalculated);
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index d0ab287..626b70b 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -275,7 +275,7 @@
@VisibleForTesting static final String DEFAULT_COMPACT_PROC_STATE_THROTTLE =
String.valueOf(ActivityManager.PROCESS_STATE_RECEIVER);
@VisibleForTesting static final long DEFAULT_FREEZER_DEBOUNCE_TIMEOUT = 10_000L;
- @VisibleForTesting static final boolean DEFAULT_FREEZER_EXEMPT_INST_PKG = true;
+ @VisibleForTesting static final boolean DEFAULT_FREEZER_EXEMPT_INST_PKG = false;
@VisibleForTesting static final boolean DEFAULT_FREEZER_BINDER_ENABLED = true;
@VisibleForTesting static final long DEFAULT_FREEZER_BINDER_DIVISOR = 4;
@VisibleForTesting static final int DEFAULT_FREEZER_BINDER_OFFSET = 500;
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index fc8ad6bc..05303f6f 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -507,7 +507,8 @@
r.appInfo.uid,
r.shortInstanceName,
fgsState, // FGS State
- r.isFgsAllowedWIU(), // allowWhileInUsePermissionInFgs
+ // TODO: Also log "forStart"
+ r.isFgsAllowedWiu_forCapabilities(), // allowWhileInUsePermissionInFgs
r.getFgsAllowStart(), // fgsStartReasonCode
r.appInfo.targetSdkVersion,
r.mRecentCallingUid,
@@ -535,12 +536,12 @@
ActivityManager.PROCESS_CAPABILITY_NONE,
apiDurationBeforeFgsStart,
apiDurationAfterFgsEnd,
- r.mAllowWhileInUsePermissionInFgsReasonNoBinding,
- r.mAllowWIUInBindService,
- r.mAllowWIUByBindings,
- r.mAllowStartForegroundNoBinding,
- r.mAllowStartInBindService,
- r.mAllowStartByBindings,
+ r.mAllowWiu_noBinding,
+ r.mAllowWiu_inBindService,
+ r.mAllowWiu_byBindings,
+ r.mAllowStart_noBinding,
+ r.mAllowStart_inBindService,
+ r.mAllowStart_byBindings,
FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA,
false
);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 3424578..b507a60 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2254,7 +2254,7 @@
if (s.isForeground) {
final int fgsType = s.foregroundServiceType;
- if (s.isFgsAllowedWIU()) {
+ if (s.isFgsAllowedWiu_forCapabilities()) {
capabilityFromFGS |=
(fgsType & FOREGROUND_SERVICE_TYPE_LOCATION)
!= 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 0fba739..08b129e 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -37,6 +37,9 @@
import android.app.Notification;
import android.app.PendingIntent;
import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -81,6 +84,37 @@
// Maximum number of times it can fail during execution before giving up.
static final int MAX_DONE_EXECUTING_COUNT = 6;
+
+ // Compat IDs for the new FGS logic. For now, we just disable all of them.
+ // TODO: Enable them at some point, but only for V+ builds.
+
+ /**
+ * Compat ID to enable the new FGS start logic, for permission calculation used for
+ * per-FGS-type eligibility calculation.
+ * (See also android.app.ForegroundServiceTypePolicy)
+ */
+ @ChangeId
+ // @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Disabled
+ static final long USE_NEW_WIU_LOGIC_FOR_START = 311208629L;
+
+ /**
+ * Compat ID to enable the new FGS start logic, for capability calculation.
+ */
+ @ChangeId
+ // Always enabled
+ @Disabled
+ static final long USE_NEW_WIU_LOGIC_FOR_CAPABILITIES = 313677553L;
+
+ /**
+ * Compat ID to enable the new FGS start logic, for deciding whether to allow FGS start from
+ * the background.
+ */
+ @ChangeId
+ // @EnabledAfter(targetSdkVersion = VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Disabled
+ static final long USE_NEW_BFSL_LOGIC = 311208749L;
+
final ActivityManagerService ams;
final ComponentName name; // service component.
final ComponentName instanceName; // service component's per-instance name.
@@ -178,7 +212,7 @@
// If it's not DENIED, while-in-use permissions are allowed.
// while-in-use permissions in FGS started from background might be restricted.
@PowerExemptionManager.ReasonCode
- int mAllowWhileInUsePermissionInFgsReasonNoBinding = REASON_DENIED;
+ int mAllowWiu_noBinding = REASON_DENIED;
// A copy of mAllowWhileInUsePermissionInFgs's value when the service is entering FGS state.
boolean mAllowWhileInUsePermissionInFgsAtEntering;
@@ -208,7 +242,7 @@
// allow the service becomes foreground service? Service started from background may not be
// allowed to become a foreground service.
@PowerExemptionManager.ReasonCode
- int mAllowStartForegroundNoBinding = REASON_DENIED;
+ int mAllowStart_noBinding = REASON_DENIED;
// A copy of mAllowStartForeground's value when the service is entering FGS state.
@PowerExemptionManager.ReasonCode
int mAllowStartForegroundAtEntering = REASON_DENIED;
@@ -220,72 +254,172 @@
boolean mLoggedInfoAllowStartForeground;
@PowerExemptionManager.ReasonCode
- int mAllowWIUInBindService = REASON_DENIED;
+ int mAllowWiu_inBindService = REASON_DENIED;
@PowerExemptionManager.ReasonCode
- int mAllowWIUByBindings = REASON_DENIED;
+ int mAllowWiu_byBindings = REASON_DENIED;
@PowerExemptionManager.ReasonCode
- int mAllowStartInBindService = REASON_DENIED;
+ int mAllowStart_inBindService = REASON_DENIED;
@PowerExemptionManager.ReasonCode
- int mAllowStartByBindings = REASON_DENIED;
+ int mAllowStart_byBindings = REASON_DENIED;
- @PowerExemptionManager.ReasonCode
- int getFgsAllowWIU() {
- return mAllowWhileInUsePermissionInFgsReasonNoBinding != REASON_DENIED
- ? mAllowWhileInUsePermissionInFgsReasonNoBinding
- : mAllowWIUInBindService;
+ /**
+ * Whether to use the new "while-in-use permission" logic for FGS start
+ */
+ private boolean useNewWiuLogic_forStart() {
+ return Flags.newFgsRestrictionLogic() // This flag should only be set on V+
+ && CompatChanges.isChangeEnabled(USE_NEW_WIU_LOGIC_FOR_START, appInfo.uid);
}
- boolean isFgsAllowedWIU() {
- return getFgsAllowWIU() != REASON_DENIED;
+ /**
+ * Whether to use the new "while-in-use permission" logic for capabilities
+ */
+ private boolean useNewWiuLogic_forCapabilities() {
+ return Flags.newFgsRestrictionLogic() // This flag should only be set on V+
+ && CompatChanges.isChangeEnabled(USE_NEW_WIU_LOGIC_FOR_CAPABILITIES, appInfo.uid);
}
+ /**
+ * Whether to use the new "FGS BG start exemption" logic.
+ */
+ private boolean useNewBfslLogic() {
+ return Flags.newFgsRestrictionLogic() // This flag should only be set on V+
+ && CompatChanges.isChangeEnabled(USE_NEW_BFSL_LOGIC, appInfo.uid);
+ }
+
+
+ @PowerExemptionManager.ReasonCode
+ private int getFgsAllowWiu_legacy() {
+ // In the legacy mode (U-), we use mAllowWiu_inBindService and mAllowWiu_noBinding.
+ return reasonOr(
+ mAllowWiu_noBinding,
+ mAllowWiu_inBindService
+ );
+ }
+
+ @PowerExemptionManager.ReasonCode
+ private int getFgsAllowWiu_new() {
+ // In the new mode, use by-bindings instead of in-bind-service
+ return reasonOr(
+ mAllowWiu_noBinding,
+ mAllowWiu_byBindings
+ );
+ }
+
+ /**
+ * We use this logic for ForegroundServiceTypePolicy and UIDT eligibility check.
+ */
+ @PowerExemptionManager.ReasonCode
+ int getFgsAllowWiu_forStart() {
+ if (useNewWiuLogic_forStart()) {
+ return getFgsAllowWiu_new();
+ } else {
+ return getFgsAllowWiu_legacy();
+ }
+ }
+
+ /**
+ * We use this logic for the capability calculation in oom-adjuster.
+ */
+ @PowerExemptionManager.ReasonCode
+ int getFgsAllowWiu_forCapabilities() {
+ if (useNewWiuLogic_forCapabilities()) {
+ return getFgsAllowWiu_new();
+ } else {
+ // If alwaysUseNewLogicForWiuCapabilities() isn't set, just use the same logic as
+ // getFgsAllowWiu_forStart().
+ return getFgsAllowWiu_forStart();
+ }
+ }
+
+ /**
+ * We use this logic for ForegroundServiceTypePolicy and UIDT eligibility check.
+ */
+ boolean isFgsAllowedWiu_forStart() {
+ return getFgsAllowWiu_forStart() != REASON_DENIED;
+ }
+
+ /**
+ * We use this logic for the capability calculation in oom-adjuster.
+ */
+ boolean isFgsAllowedWiu_forCapabilities() {
+ return getFgsAllowWiu_forCapabilities() != REASON_DENIED;
+ }
+
+ @PowerExemptionManager.ReasonCode
+ private int getFgsAllowStart_legacy() {
+ // This is used for BFSL (background FGS launch) exemption.
+ // Originally -- on U-QPR1 and before -- we only used [in-bind-service] + [no-binding].
+ // This would exclude certain "valid" situations, so in U-QPR2, we started
+ // using [by-bindings] too.
+ return reasonOr(
+ mAllowStart_noBinding,
+ mAllowStart_inBindService,
+ mAllowStart_byBindings
+ );
+ }
+
+ @PowerExemptionManager.ReasonCode
+ private int getFgsAllowStart_new() {
+ // In the new mode, we don't use [in-bind-service].
+ return reasonOr(
+ mAllowStart_noBinding,
+ mAllowStart_byBindings
+ );
+ }
+
+ /**
+ * Calculate a BFSL exemption code.
+ */
@PowerExemptionManager.ReasonCode
int getFgsAllowStart() {
- return mAllowStartForegroundNoBinding != REASON_DENIED
- ? mAllowStartForegroundNoBinding
- : (mAllowStartByBindings != REASON_DENIED
- ? mAllowStartByBindings
- : mAllowStartInBindService);
+ if (useNewBfslLogic()) {
+ return getFgsAllowStart_new();
+ } else {
+ return getFgsAllowStart_legacy();
+ }
}
+ /**
+ * Return whether BFSL is allowed or not.
+ */
boolean isFgsAllowedStart() {
return getFgsAllowStart() != REASON_DENIED;
}
- void clearFgsAllowWIU() {
- mAllowWhileInUsePermissionInFgsReasonNoBinding = REASON_DENIED;
- mAllowWIUInBindService = REASON_DENIED;
- mAllowWIUByBindings = REASON_DENIED;
+ void clearFgsAllowWiu() {
+ mAllowWiu_noBinding = REASON_DENIED;
+ mAllowWiu_inBindService = REASON_DENIED;
+ mAllowWiu_byBindings = REASON_DENIED;
}
void clearFgsAllowStart() {
- mAllowStartForegroundNoBinding = REASON_DENIED;
- mAllowStartInBindService = REASON_DENIED;
- mAllowStartByBindings = REASON_DENIED;
+ mAllowStart_noBinding = REASON_DENIED;
+ mAllowStart_inBindService = REASON_DENIED;
+ mAllowStart_byBindings = REASON_DENIED;
}
@PowerExemptionManager.ReasonCode
- int reasonOr(@PowerExemptionManager.ReasonCode int first,
+ static int reasonOr(
+ @PowerExemptionManager.ReasonCode int first,
@PowerExemptionManager.ReasonCode int second) {
return first != REASON_DENIED ? first : second;
}
- boolean allowedChanged(@PowerExemptionManager.ReasonCode int first,
- @PowerExemptionManager.ReasonCode int second) {
- return (first == REASON_DENIED) != (second == REASON_DENIED);
+ @PowerExemptionManager.ReasonCode
+ static int reasonOr(
+ @PowerExemptionManager.ReasonCode int first,
+ @PowerExemptionManager.ReasonCode int second,
+ @PowerExemptionManager.ReasonCode int third) {
+ return first != REASON_DENIED ? first : reasonOr(second, third);
}
- String changeMessage(@PowerExemptionManager.ReasonCode int first,
- @PowerExemptionManager.ReasonCode int second) {
- return reasonOr(first, second) == REASON_DENIED ? "DENIED"
- : ("ALLOWED ("
- + reasonCodeToString(first)
- + "+"
- + reasonCodeToString(second)
- + ")");
+ boolean allowedChanged(
+ @PowerExemptionManager.ReasonCode int legacyCode,
+ @PowerExemptionManager.ReasonCode int newCode) {
+ return (legacyCode == REASON_DENIED) != (newCode == REASON_DENIED);
}
private String getFgsInfoForWtf() {
@@ -295,15 +429,14 @@
}
void maybeLogFgsLogicChange() {
- final int origWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding,
- mAllowWIUInBindService);
- final int newWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding,
- mAllowWIUByBindings);
- final int origStart = reasonOr(mAllowStartForegroundNoBinding, mAllowStartInBindService);
- final int newStart = reasonOr(mAllowStartForegroundNoBinding, mAllowStartByBindings);
+ final int wiuLegacy = getFgsAllowWiu_legacy();
+ final int wiuNew = getFgsAllowWiu_new();
- final boolean wiuChanged = allowedChanged(origWiu, newWiu);
- final boolean startChanged = allowedChanged(origStart, newStart);
+ final int startLegacy = getFgsAllowStart_legacy();
+ final int startNew = getFgsAllowStart_new();
+
+ final boolean wiuChanged = allowedChanged(wiuLegacy, wiuNew);
+ final boolean startChanged = allowedChanged(startLegacy, startNew);
if (!wiuChanged && !startChanged) {
return;
@@ -311,15 +444,14 @@
final String message = "FGS logic changed:"
+ (wiuChanged ? " [WIU changed]" : "")
+ (startChanged ? " [BFSL changed]" : "")
- + " OW:" // Orig-WIU
- + changeMessage(mAllowWhileInUsePermissionInFgsReasonNoBinding,
- mAllowWIUInBindService)
- + " NW:" // New-WIU
- + changeMessage(mAllowWhileInUsePermissionInFgsReasonNoBinding, mAllowWIUByBindings)
- + " OS:" // Orig-start
- + changeMessage(mAllowStartForegroundNoBinding, mAllowStartInBindService)
- + " NS:" // New-start
- + changeMessage(mAllowStartForegroundNoBinding, mAllowStartByBindings)
+ + " Orig WIU:"
+ + reasonCodeToString(wiuLegacy)
+ + " New WIU:"
+ + reasonCodeToString(wiuNew)
+ + " Orig BFSL:"
+ + reasonCodeToString(startLegacy)
+ + " New BFSL:"
+ + reasonCodeToString(startNew)
+ getFgsInfoForWtf();
Slog.wtf(TAG_SERVICE, message);
}
@@ -587,6 +719,7 @@
proto.write(ServiceRecordProto.AppInfo.RES_DIR, appInfo.publicSourceDir);
}
proto.write(ServiceRecordProto.AppInfo.DATA_DIR, appInfo.dataDir);
+ proto.write(ServiceRecordProto.AppInfo.TARGET_SDK_VERSION, appInfo.targetSdkVersion);
proto.end(appInfoToken);
}
if (app != null) {
@@ -611,8 +744,10 @@
ProtoUtils.toDuration(proto, ServiceRecordProto.LAST_ACTIVITY_TIME, lastActivity, now);
ProtoUtils.toDuration(proto, ServiceRecordProto.RESTART_TIME, restartTime, now);
proto.write(ServiceRecordProto.CREATED_FROM_FG, createdFromFg);
+
+ // TODO: Log "forStart" too.
proto.write(ServiceRecordProto.ALLOW_WHILE_IN_USE_PERMISSION_IN_FGS,
- isFgsAllowedWIU());
+ isFgsAllowedWiu_forCapabilities());
if (startRequested || delayedStop || lastStartId != 0) {
long startToken = proto.start(ServiceRecordProto.START);
@@ -691,15 +826,25 @@
proto.end(shortFgsToken);
}
+ // TODO: Dump all the mAllowWiu* and mAllowStart* fields as needed.
+
proto.end(token);
}
+ void dumpReasonCode(PrintWriter pw, String prefix, String fieldName, int code) {
+ pw.print(prefix);
+ pw.print(fieldName);
+ pw.print("=");
+ pw.println(PowerExemptionManager.reasonCodeToString(code));
+ }
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("intent={");
pw.print(intent.getIntent().toShortString(false, true, false, false));
pw.println('}');
pw.print(prefix); pw.print("packageName="); pw.println(packageName);
pw.print(prefix); pw.print("processName="); pw.println(processName);
+ pw.print(prefix); pw.print("targetSdkVersion="); pw.println(appInfo.targetSdkVersion);
if (permission != null) {
pw.print(prefix); pw.print("permission="); pw.println(permission);
}
@@ -727,26 +872,38 @@
pw.print(prefix); pw.print("mIsAllowedBgActivityStartsByStart=");
pw.println(mBackgroundStartPrivilegesByStartMerged);
}
- pw.print(prefix); pw.print("mAllowWhileInUsePermissionInFgsReason=");
- pw.println(PowerExemptionManager.reasonCodeToString(
- mAllowWhileInUsePermissionInFgsReasonNoBinding));
- pw.print(prefix); pw.print("mAllowWIUInBindService=");
- pw.println(PowerExemptionManager.reasonCodeToString(mAllowWIUInBindService));
- pw.print(prefix); pw.print("mAllowWIUByBindings=");
- pw.println(PowerExemptionManager.reasonCodeToString(mAllowWIUByBindings));
+ pw.print(prefix); pw.print("useNewWiuLogic_forCapabilities()=");
+ pw.println(useNewWiuLogic_forCapabilities());
+ pw.print(prefix); pw.print("useNewWiuLogic_forStart()=");
+ pw.println(useNewWiuLogic_forStart());
+ pw.print(prefix); pw.print("useNewBfslLogic()=");
+ pw.println(useNewBfslLogic());
+
+ dumpReasonCode(pw, prefix, "mAllowWiu_noBinding", mAllowWiu_noBinding);
+ dumpReasonCode(pw, prefix, "mAllowWiu_inBindService", mAllowWiu_inBindService);
+ dumpReasonCode(pw, prefix, "mAllowWiu_byBindings", mAllowWiu_byBindings);
+
+ dumpReasonCode(pw, prefix, "getFgsAllowWiu_legacy", getFgsAllowWiu_legacy());
+ dumpReasonCode(pw, prefix, "getFgsAllowWiu_new", getFgsAllowWiu_new());
+
+ dumpReasonCode(pw, prefix, "getFgsAllowWiu_forStart", getFgsAllowWiu_forStart());
+ dumpReasonCode(pw, prefix, "getFgsAllowWiu_forCapabilities",
+ getFgsAllowWiu_forCapabilities());
pw.print(prefix); pw.print("allowUiJobScheduling="); pw.println(mAllowUiJobScheduling);
pw.print(prefix); pw.print("recentCallingPackage=");
pw.println(mRecentCallingPackage);
pw.print(prefix); pw.print("recentCallingUid=");
pw.println(mRecentCallingUid);
- pw.print(prefix); pw.print("allowStartForeground=");
- pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartForegroundNoBinding));
- pw.print(prefix); pw.print("mAllowStartInBindService=");
- pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartInBindService));
- pw.print(prefix); pw.print("mAllowStartByBindings=");
- pw.println(PowerExemptionManager.reasonCodeToString(mAllowStartByBindings));
+
+ dumpReasonCode(pw, prefix, "mAllowStart_noBinding", mAllowStart_noBinding);
+ dumpReasonCode(pw, prefix, "mAllowStart_inBindService", mAllowStart_inBindService);
+ dumpReasonCode(pw, prefix, "mAllowStart_byBindings", mAllowStart_byBindings);
+
+ dumpReasonCode(pw, prefix, "getFgsAllowStart_legacy", getFgsAllowStart_legacy());
+ dumpReasonCode(pw, prefix, "getFgsAllowStart_new", getFgsAllowStart_new());
+ dumpReasonCode(pw, prefix, "getFgsAllowStart", getFgsAllowStart());
pw.print(prefix); pw.print("startForegroundCount=");
pw.println(mStartForegroundCount);
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index d9e8ddd..654aebd 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -28,3 +28,10 @@
description: "Restrict network access for certain applications in BFGS process state"
bug: "304347838"
}
+# Whether to use the new while-in-use / BG-FGS-start logic
+flag {
+ namespace: "backstage_power"
+ name: "new_fgs_restriction_logic"
+ description: "Enable the new FGS restriction logic"
+ bug: "276963716"
+}
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index dafea9a..d5d8fd2 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -51,9 +51,13 @@
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.biometrics.SensorPropertiesInternal;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.face.FaceSensorConfigurations;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceService;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintService;
@@ -122,8 +126,6 @@
/**
* Allows to test with various device sensor configurations.
- * @param context
- * @return
*/
@VisibleForTesting
public String[] getConfiguration(Context context) {
@@ -131,6 +133,30 @@
}
/**
+ * Allows to test with various device sensor configurations.
+ */
+ @VisibleForTesting
+ public String[] getFingerprintConfiguration(Context context) {
+ return getConfiguration(context);
+ }
+
+ /**
+ * Allows to test with various device sensor configurations.
+ */
+ @VisibleForTesting
+ public String[] getFaceConfiguration(Context context) {
+ return getConfiguration(context);
+ }
+
+ /**
+ * Allows to test with various device sensor configurations.
+ */
+ @VisibleForTesting
+ public String[] getIrisConfiguration(Context context) {
+ return getConfiguration(context);
+ }
+
+ /**
* Allows us to mock FingerprintService for testing
*/
@VisibleForTesting
@@ -173,6 +199,22 @@
}
return false;
}
+
+ /**
+ * Allows to test with various fingerprint aidl instances.
+ */
+ @VisibleForTesting
+ public String[] getFingerprintAidlInstances() {
+ return ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR);
+ }
+
+ /**
+ * Allows to test with various face aidl instances.
+ */
+ @VisibleForTesting
+ public String[] getFaceAidlInstances() {
+ return ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
+ }
}
private final class AuthServiceImpl extends IAuthService.Stub {
@@ -717,12 +759,129 @@
hidlConfigs = null;
}
- // Registers HIDL and AIDL authenticators, but only HIDL configs need to be provided.
- registerAuthenticators(hidlConfigs);
+ if (com.android.server.biometrics.Flags.deHidl()) {
+ Slog.d(TAG, "deHidl flag is on.");
+ registerAuthenticators();
+ } else {
+ // Registers HIDL and AIDL authenticators, but only HIDL configs need to be provided.
+ registerAuthenticators(hidlConfigs);
+ }
mInjector.publishBinderService(this, mImpl);
}
+ private void registerAuthenticators() {
+ registerFingerprintSensors(mInjector.getFingerprintAidlInstances(),
+ mInjector.getFingerprintConfiguration(getContext()));
+ registerFaceSensors(mInjector.getFaceAidlInstances(),
+ mInjector.getFaceConfiguration(getContext()));
+ registerIrisSensors(mInjector.getIrisConfiguration(getContext()));
+ }
+
+ private void registerIrisSensors(String[] hidlConfigStrings) {
+ final SensorConfig[] hidlConfigs;
+ if (!mInjector.isHidlDisabled(getContext())) {
+ final int firstApiLevel = SystemProperties.getInt(SYSPROP_FIRST_API_LEVEL, 0);
+ final int apiLevel = SystemProperties.getInt(SYSPROP_API_LEVEL, firstApiLevel);
+ if (hidlConfigStrings.length == 0 && apiLevel == Build.VERSION_CODES.R) {
+ // For backwards compatibility with R where biometrics could work without being
+ // configured in config_biometric_sensors. In the absence of a vendor provided
+ // configuration, we assume the weakest biometric strength (i.e. convenience).
+ Slog.w(TAG, "Found R vendor partition without config_biometric_sensors");
+ hidlConfigStrings = generateRSdkCompatibleConfiguration();
+ }
+ hidlConfigs = new SensorConfig[hidlConfigStrings.length];
+ for (int i = 0; i < hidlConfigStrings.length; ++i) {
+ hidlConfigs[i] = new SensorConfig(hidlConfigStrings[i]);
+ }
+ } else {
+ hidlConfigs = null;
+ }
+
+ final List<SensorPropertiesInternal> hidlIrisSensors = new ArrayList<>();
+
+ if (hidlConfigs != null) {
+ for (SensorConfig sensor : hidlConfigs) {
+ switch (sensor.modality) {
+ case TYPE_IRIS:
+ hidlIrisSensors.add(getHidlIrisSensorProps(sensor.id, sensor.strength));
+ break;
+
+ default:
+ Slog.e(TAG, "Unknown modality: " + sensor.modality);
+ }
+ }
+ }
+
+ final IIrisService irisService = mInjector.getIrisService();
+ if (irisService != null) {
+ try {
+ irisService.registerAuthenticators(hidlIrisSensors);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when registering iris authenticators", e);
+ }
+ } else if (hidlIrisSensors.size() > 0) {
+ Slog.e(TAG, "HIDL iris configuration exists, but IrisService is null.");
+ }
+ }
+
+ private void registerFaceSensors(final String[] faceAidlInstances,
+ final String[] hidlConfigStrings) {
+ final FaceSensorConfigurations mFaceSensorConfigurations =
+ new FaceSensorConfigurations(hidlConfigStrings != null
+ && hidlConfigStrings.length > 0);
+
+ if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
+ mFaceSensorConfigurations.addHidlConfigs(
+ hidlConfigStrings, getContext());
+ }
+
+ if (faceAidlInstances != null && faceAidlInstances.length > 0) {
+ mFaceSensorConfigurations.addAidlConfigs(faceAidlInstances,
+ name -> IFace.Stub.asInterface(Binder.allowBlocking(
+ ServiceManager.waitForDeclaredService(name))));
+ }
+
+ final IFaceService faceService = mInjector.getFaceService();
+ if (faceService != null) {
+ try {
+ faceService.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when registering face authenticators", e);
+ }
+ } else if (mFaceSensorConfigurations.hasSensorConfigurations()) {
+ Slog.e(TAG, "Face configuration exists, but FaceService is null.");
+ }
+ }
+
+ private void registerFingerprintSensors(final String[] fingerprintAidlInstances,
+ final String[] hidlConfigStrings) {
+ final FingerprintSensorConfigurations mFingerprintSensorConfigurations =
+ new FingerprintSensorConfigurations(!(hidlConfigStrings != null
+ && hidlConfigStrings.length > 0));
+
+ if (hidlConfigStrings != null && hidlConfigStrings.length > 0) {
+ mFingerprintSensorConfigurations.addHidlSensors(hidlConfigStrings, getContext());
+ }
+
+ if (fingerprintAidlInstances != null && fingerprintAidlInstances.length > 0) {
+ mFingerprintSensorConfigurations.addAidlSensors(fingerprintAidlInstances,
+ name -> IFingerprint.Stub.asInterface(Binder.allowBlocking(
+ ServiceManager.waitForDeclaredService(name))));
+ }
+
+ final IFingerprintService fingerprintService = mInjector.getFingerprintService();
+ if (fingerprintService != null) {
+ try {
+ fingerprintService.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e);
+ }
+ } else if (mFingerprintSensorConfigurations.hasSensorConfigurations()) {
+ Slog.e(TAG, "Fingerprint configuration exists, but FingerprintService is null.");
+ }
+ }
+
/**
* Generates an array of string configs with entries that correspond to the biometric features
* declared on the device. Returns an empty array if no biometric features are declared.
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 037ea38a..89b638b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -202,7 +202,7 @@
};
@VisibleForTesting
- BiometricScheduler(@NonNull String tag,
+ public BiometricScheduler(@NonNull String tag,
@NonNull Handler handler,
@SensorType int sensorType,
@Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
index 57feedc..0c3dfa7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
@@ -387,6 +387,11 @@
return isAuthentication || isDetection;
}
+ /** If this operation is {@link StartUserClient}. */
+ public boolean isStartUserOperation() {
+ return mClientMonitor instanceof StartUserClient<?, ?>;
+ }
+
/** If this operation performs acquisition {@link AcquisitionClient}. */
public boolean isAcquisitionOperation() {
return mClientMonitor instanceof AcquisitionClient;
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
index 7f8f38f..6daaad1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java
@@ -54,8 +54,6 @@
// is all done internally.
super(context, lazyDaemon, token, null /* ClientMonitorCallbackConverter */, userId, owner,
0 /* cookie */, sensorId, logger, biometricContext);
- //, BiometricsProtoEnums.ACTION_ENUMERATE,
- // BiometricsProtoEnums.CLIENT_UNKNOWN);
mEnrolledList = enrolledList;
mUtils = utils;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
index 8075470..3753bbd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -135,7 +135,7 @@
final int currentUserId = mCurrentUserRetriever.getCurrentUserId();
final int nextUserId = mPendingOperations.getFirst().getTargetUserId();
- if (nextUserId == currentUserId) {
+ if (nextUserId == currentUserId || mPendingOperations.getFirst().isStartUserOperation()) {
super.startNextOperationIfIdle();
} else if (currentUserId == UserHandle.USER_NULL) {
final BaseClientMonitor startClient =
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 578d9dc..6af223b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -36,6 +36,7 @@
import android.hardware.biometrics.face.SensorProps;
import android.hardware.face.Face;
import android.hardware.face.FaceAuthenticateOptions;
+import android.hardware.face.FaceSensorConfigurations;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.FaceServiceReceiver;
import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
@@ -55,6 +56,7 @@
import android.util.proto.ProtoOutputStream;
import android.view.Surface;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.SystemService;
@@ -76,6 +78,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.function.Supplier;
/**
* A service to manage multiple clients that want to access the face HAL API.
@@ -86,7 +89,7 @@
protected static final String TAG = "FaceService";
- private final FaceServiceWrapper mServiceWrapper;
+ @VisibleForTesting final FaceServiceWrapper mServiceWrapper;
private final LockoutResetDispatcher mLockoutResetDispatcher;
private final LockPatternUtils mLockPatternUtils;
@NonNull
@@ -94,11 +97,18 @@
@NonNull
private final BiometricStateCallback<ServiceProvider, FaceSensorPropertiesInternal>
mBiometricStateCallback;
+ @NonNull
+ private final FaceProviderFunction mFaceProviderFunction;
+
+ interface FaceProviderFunction {
+ FaceProvider getFaceProvider(Pair<String, SensorProps[]> filteredSensorProps,
+ boolean resetLockoutRequiresChallenge);
+ }
/**
* Receives the incoming binder calls from FaceManager.
*/
- private final class FaceServiceWrapper extends IFaceService.Stub {
+ @VisibleForTesting final class FaceServiceWrapper extends IFaceService.Stub {
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@@ -672,7 +682,8 @@
final SensorProps[] props = face.getSensorProps();
final FaceProvider provider = new FaceProvider(getContext(),
mBiometricStateCallback, props, instance, mLockoutResetDispatcher,
- BiometricContext.getInstance(getContext()));
+ BiometricContext.getInstance(getContext()),
+ false /* resetLockoutRequiresChallenge */);
providers.add(provider);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
@@ -704,6 +715,55 @@
});
}
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ public void registerAuthenticatorsLegacy(
+ FaceSensorConfigurations faceSensorConfigurations) {
+ super.registerAuthenticatorsLegacy_enforcePermission();
+
+ if (!faceSensorConfigurations.hasSensorConfigurations()) {
+ Slog.d(TAG, "No face sensors to register.");
+ return;
+ }
+ mRegistry.registerAll(() -> getProviders(faceSensorConfigurations));
+ }
+
+ private List<ServiceProvider> getProviders(
+ FaceSensorConfigurations faceSensorConfigurations) {
+ final List<ServiceProvider> providers = new ArrayList<>();
+ final Pair<String, SensorProps[]> filteredSensorProps =
+ filterAvailableHalInstances(faceSensorConfigurations);
+ providers.add(mFaceProviderFunction.getFaceProvider(filteredSensorProps,
+ faceSensorConfigurations.getResetLockoutRequiresChallenge()));
+ return providers;
+ }
+
+ @NonNull
+ private Pair<String, SensorProps[]> filterAvailableHalInstances(
+ FaceSensorConfigurations faceSensorConfigurations) {
+ Pair<String, SensorProps[]> finalSensorPair = faceSensorConfigurations.getSensorPair();
+
+ if (faceSensorConfigurations.isSingleSensorConfigurationPresent()) {
+ return finalSensorPair;
+ }
+
+ final Pair<String, SensorProps[]> virtualSensorProps = faceSensorConfigurations
+ .getSensorPairForInstance("virtual");
+
+ if (Utils.isVirtualEnabled(getContext())) {
+ if (virtualSensorProps != null) {
+ return virtualSensorProps;
+ } else {
+ Slog.e(TAG, "Could not find virtual interface while it is enabled");
+ return finalSensorPair;
+ }
+ } else {
+ if (virtualSensorProps != null) {
+ return faceSensorConfigurations.getSensorPairNotForInstance("virtual");
+ }
+ }
+ return finalSensorPair;
+ }
+
private Pair<List<FaceSensorPropertiesInternal>, List<String>>
filterAvailableHalInstances(
@NonNull List<FaceSensorPropertiesInternal> hidlInstances,
@@ -752,20 +812,36 @@
}
public FaceService(Context context) {
+ this(context, null /* faceProviderFunction */, () -> IBiometricService.Stub.asInterface(
+ ServiceManager.getService(Context.BIOMETRIC_SERVICE)));
+ }
+
+ @VisibleForTesting FaceService(Context context, FaceProviderFunction faceProviderFunction,
+ Supplier<IBiometricService> biometricServiceSupplier) {
super(context);
mServiceWrapper = new FaceServiceWrapper();
mLockoutResetDispatcher = new LockoutResetDispatcher(context);
mLockPatternUtils = new LockPatternUtils(context);
mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
- mRegistry = new FaceServiceRegistry(mServiceWrapper,
- () -> IBiometricService.Stub.asInterface(
- ServiceManager.getService(Context.BIOMETRIC_SERVICE)));
+ mRegistry = new FaceServiceRegistry(mServiceWrapper, biometricServiceSupplier);
mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
@Override
public void onAllAuthenticatorsRegistered(List<FaceSensorPropertiesInternal> sensors) {
mBiometricStateCallback.start(mRegistry.getProviders());
}
});
+
+ if (Flags.deHidl()) {
+ mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction :
+ ((filteredSensorProps, resetLockoutRequiresChallenge) -> new FaceProvider(
+ getContext(), mBiometricStateCallback,
+ filteredSensorProps.second,
+ filteredSensorProps.first, mLockoutResetDispatcher,
+ BiometricContext.getInstance(getContext()),
+ resetLockoutRequiresChallenge));
+ } else {
+ mFaceProviderFunction = ((filteredSensorProps, resetLockoutRequiresChallenge) -> null);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
index e5d4a63..ef2be79 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java
@@ -24,7 +24,8 @@
* the user changes.
*/
public class LockoutHalImpl implements LockoutTracker {
- private @LockoutMode int mCurrentUserLockoutMode;
+ @LockoutMode
+ private int mCurrentUserLockoutMode;
@Override
public int getLockoutModeForUser(int userId) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
index 57f5b34..098be21 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
@@ -27,6 +27,7 @@
import android.hardware.keymaster.HardwareAuthToken;
import android.util.Slog;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.AcquisitionClient;
@@ -59,6 +60,20 @@
void onHardwareUnavailable();
}
+ /**
+ * Interface to send results to the AidlResponseHandler's owner.
+ */
+ public interface AidlResponseHandlerCallback {
+ /**
+ * Invoked when enrollment is successful.
+ */
+ void onEnrollSuccess();
+ /**
+ * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+ */
+ void onHardwareUnavailable();
+ }
+
private static final String TAG = "AidlResponseHandler";
@NonNull
@@ -68,7 +83,7 @@
private final int mSensorId;
private final int mUserId;
@NonNull
- private final LockoutTracker mLockoutCache;
+ private final LockoutTracker mLockoutTracker;
@NonNull
private final LockoutResetDispatcher mLockoutResetDispatcher;
@@ -76,6 +91,8 @@
private final AuthSessionCoordinator mAuthSessionCoordinator;
@NonNull
private final HardwareUnavailableCallback mHardwareUnavailableCallback;
+ @NonNull
+ private final AidlResponseHandlerCallback mAidlResponseHandlerCallback;
public AidlResponseHandler(@NonNull Context context,
@NonNull BiometricScheduler scheduler, int sensorId, int userId,
@@ -83,14 +100,33 @@
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull AuthSessionCoordinator authSessionCoordinator,
@NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
+ this(context, scheduler, sensorId, userId, lockoutTracker, lockoutResetDispatcher,
+ authSessionCoordinator, hardwareUnavailableCallback,
+ new AidlResponseHandlerCallback() {
+ @Override
+ public void onEnrollSuccess() {}
+
+ @Override
+ public void onHardwareUnavailable() {}
+ });
+ }
+
+ public AidlResponseHandler(@NonNull Context context,
+ @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+ @NonNull LockoutTracker lockoutTracker,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull AuthSessionCoordinator authSessionCoordinator,
+ @NonNull HardwareUnavailableCallback hardwareUnavailableCallback,
+ @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) {
mContext = context;
mScheduler = scheduler;
mSensorId = sensorId;
mUserId = userId;
- mLockoutCache = lockoutTracker;
+ mLockoutTracker = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
mAuthSessionCoordinator = authSessionCoordinator;
mHardwareUnavailableCallback = hardwareUnavailableCallback;
+ mAidlResponseHandlerCallback = aidlResponseHandlerCallback;
}
@Override
@@ -106,13 +142,13 @@
@Override
public void onChallengeGenerated(long challenge) {
handleResponse(FaceGenerateChallengeClient.class, (c) -> c.onChallengeGenerated(mSensorId,
- mUserId, challenge), null);
+ mUserId, challenge));
}
@Override
public void onChallengeRevoked(long challenge) {
handleResponse(FaceRevokeChallengeClient.class, (c) -> c.onChallengeRevoked(mSensorId,
- mUserId, challenge), null);
+ mUserId, challenge));
}
@Override
@@ -123,7 +159,7 @@
return;
}
c.onAuthenticationFrame(AidlConversionUtils.toFrameworkAuthenticationFrame(frame));
- }, null);
+ });
}
@Override
@@ -134,7 +170,7 @@
return;
}
c.onEnrollmentFrame(AidlConversionUtils.toFrameworkEnrollmentFrame(frame));
- }, null);
+ });
}
@Override
@@ -149,9 +185,13 @@
handleResponse(ErrorConsumer.class, (c) -> {
c.onError(error, vendorCode);
if (error == Error.HW_UNAVAILABLE) {
- mHardwareUnavailableCallback.onHardwareUnavailable();
+ if (Flags.deHidl()) {
+ mAidlResponseHandlerCallback.onHardwareUnavailable();
+ } else {
+ mHardwareUnavailableCallback.onHardwareUnavailable();
+ }
}
- }, null);
+ });
}
@Override
@@ -167,7 +207,12 @@
.getUniqueName(mContext, currentUserId);
final Face face = new Face(name, enrollmentId, mSensorId);
- handleResponse(FaceEnrollClient.class, (c) -> c.onEnrollResult(face, remaining), null);
+ handleResponse(FaceEnrollClient.class, (c) -> {
+ c.onEnrollResult(face, remaining);
+ if (remaining == 0) {
+ mAidlResponseHandlerCallback.onEnrollSuccess();
+ }
+ });
}
@Override
@@ -179,37 +224,37 @@
byteList.add(b);
}
handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face,
- true /* authenticated */, byteList), null);
+ true /* authenticated */, byteList));
}
@Override
public void onAuthenticationFailed() {
final Face face = new Face("" /* name */, 0 /* faceId */, mSensorId);
handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face,
- false /* authenticated */, null /* hat */), null);
+ false /* authenticated */, null /* hat */));
}
@Override
public void onLockoutTimed(long durationMillis) {
- handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis), null);
+ handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis));
}
@Override
public void onLockoutPermanent() {
- handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null);
+ handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent);
}
@Override
public void onLockoutCleared() {
handleResponse(FaceResetLockoutClient.class, FaceResetLockoutClient::onLockoutCleared,
(c) -> FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId,
- mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
- Utils.getCurrentStrength(mSensorId), -1 /* requestId */));
+ mLockoutTracker, mLockoutResetDispatcher, mAuthSessionCoordinator,
+ Utils.getCurrentStrength(mSensorId), -1 /* requestId */));
}
@Override
public void onInteractionDetected() {
- handleResponse(FaceDetectClient.class, FaceDetectClient::onInteractionDetected, null);
+ handleResponse(FaceDetectClient.class, FaceDetectClient::onInteractionDetected);
}
@Override
@@ -219,23 +264,23 @@
final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
final int finalI = i;
handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(face,
- enrollmentIds.length - finalI - 1), null);
+ enrollmentIds.length - finalI - 1 /* remaining */));
}
} else {
handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(
- null /* identifier */, 0 /* remaining */), null);
+ null /* identifier */, 0 /* remaining */));
}
}
@Override
public void onFeaturesRetrieved(byte[] features) {
handleResponse(FaceGetFeatureClient.class, (c) -> c.onFeatureGet(true /* success */,
- features), null);
+ features));
}
@Override
public void onFeatureSet(byte feature) {
- handleResponse(FaceSetFeatureClient.class, (c) -> c.onFeatureSet(true /* success */), null);
+ handleResponse(FaceSetFeatureClient.class, (c) -> c.onFeatureSet(true /* success */));
}
@Override
@@ -245,33 +290,32 @@
final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId);
final int finalI = i;
handleResponse(RemovalConsumer.class,
- (c) -> c.onRemoved(face, enrollmentIds.length - finalI - 1),
- null);
+ (c) -> c.onRemoved(face,
+ enrollmentIds.length - finalI - 1 /* remaining */));
}
} else {
handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null /* identifier */,
- 0 /* remaining */), null);
+ 0 /* remaining */));
}
}
@Override
public void onAuthenticatorIdRetrieved(long authenticatorId) {
handleResponse(FaceGetAuthenticatorIdClient.class, (c) -> c.onAuthenticatorIdRetrieved(
- authenticatorId), null);
+ authenticatorId));
}
@Override
public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
handleResponse(FaceInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated(
- newAuthenticatorId), null);
+ newAuthenticatorId));
}
/**
* Handles acquired messages sent by the HAL (specifically for HIDL HAL).
*/
public void onAcquired(int acquiredInfo, int vendorCode) {
- handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode),
- null);
+ handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode));
}
/**
@@ -288,7 +332,7 @@
lockoutMode = LockoutTracker.LOCKOUT_TIMED;
}
- mLockoutCache.setLockoutModeForUser(mUserId, lockoutMode);
+ mLockoutTracker.setLockoutModeForUser(mUserId, lockoutMode);
if (duration == 0) {
mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId);
@@ -296,6 +340,20 @@
});
}
+ /**
+ * Handle clients which are not supported in HIDL HAL. For face, FaceInvalidationClient
+ * is the only AIDL client which is not supported in HIDL.
+ */
+ public void onUnsupportedClientScheduled() {
+ Slog.e(TAG, "FaceInvalidationClient is not supported in the HAL.");
+ handleResponse(FaceInvalidationClient.class, BaseClientMonitor::cancel);
+ }
+
+ private <T> void handleResponse(@NonNull Class<T> className,
+ @NonNull Consumer<T> action) {
+ handleResponse(className, action, null /* alternateAction */);
+ }
+
private <T> void handleResponse(@NonNull Class<T> className,
@NonNull Consumer<T> actionIfClassMatchesClient,
@Nullable Consumer<BaseClientMonitor> alternateAction) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
index e70e25a..af46f44 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java
@@ -21,7 +21,7 @@
import android.hardware.biometrics.face.ISession;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter;
+import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter;
import java.util.function.Supplier;
@@ -47,7 +47,7 @@
public AidlSession(Context context, Supplier<IBiometricsFace> session, int userId,
AidlResponseHandler aidlResponseHandler) {
- mSession = new AidlToHidlAdapter(context, session, userId, aidlResponseHandler);
+ mSession = new HidlToAidlSessionAdapter(context, session, userId, aidlResponseHandler);
mHalInterfaceVersion = 0;
mUserId = userId;
mAidlResponseHandler = aidlResponseHandler;
@@ -64,7 +64,7 @@
}
/** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */
- AidlResponseHandler getHalSessionCallback() {
+ public AidlResponseHandler getHalSessionCallback() {
return mAidlResponseHandler;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
index c15049b..c41b706 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java
@@ -34,7 +34,7 @@
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.HalClientMonitor;
-import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter;
+import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter;
import java.util.HashMap;
import java.util.Map;
@@ -75,8 +75,8 @@
protected void startHalOperation() {
try {
ISession session = getFreshDaemon().getSession();
- if (session instanceof AidlToHidlAdapter) {
- ((AidlToHidlAdapter) session).setFeature(mFeature);
+ if (session instanceof HidlToAidlSessionAdapter) {
+ ((HidlToAidlSessionAdapter) session).setFeature(mFeature);
}
session.getFeatures();
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index dd9c6d5..9fa15b8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -24,6 +24,7 @@
import android.app.TaskStackListener;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IInvalidationCallback;
@@ -51,6 +52,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -64,11 +66,13 @@
import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
import com.android.server.biometrics.sensors.InvalidationRequesterClient;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.PerformanceTracker;
import com.android.server.biometrics.sensors.SensorList;
import com.android.server.biometrics.sensors.face.FaceUtils;
import com.android.server.biometrics.sensors.face.ServiceProvider;
import com.android.server.biometrics.sensors.face.UsageStats;
+import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSensorAdapter;
import org.json.JSONArray;
import org.json.JSONException;
@@ -152,9 +156,11 @@
@NonNull SensorProps[] props,
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull BiometricContext biometricContext) {
+ @NonNull BiometricContext biometricContext,
+ boolean resetLockoutRequiresChallenge) {
this(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
- biometricContext, null /* daemon */);
+ biometricContext, null /* daemon */, resetLockoutRequiresChallenge,
+ false /* testHalEnabled */);
}
@VisibleForTesting FaceProvider(@NonNull Context context,
@@ -163,7 +169,8 @@
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull BiometricContext biometricContext,
- IFace daemon) {
+ @Nullable IFace daemon, boolean resetLockoutRequiresChallenge,
+ boolean testHalEnabled) {
mContext = context;
mBiometricStateCallback = biometricStateCallback;
mHalInstanceName = halInstanceName;
@@ -176,48 +183,118 @@
mBiometricContext = biometricContext;
mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
mDaemon = daemon;
+ mTestHalEnabled = testHalEnabled;
- AuthenticationStatsBroadcastReceiver mBroadcastReceiver =
- new AuthenticationStatsBroadcastReceiver(
- mContext,
- BiometricsProtoEnums.MODALITY_FACE,
- (AuthenticationStatsCollector collector) -> {
- Slog.d(getTag(), "Initializing AuthenticationStatsCollector");
- mAuthenticationStatsCollector = collector;
- });
+ initAuthenticationBroadcastReceiver();
+ initSensors(resetLockoutRequiresChallenge, props);
+ }
- for (SensorProps prop : props) {
- final int sensorId = prop.commonProps.sensorId;
+ private void initAuthenticationBroadcastReceiver() {
+ new AuthenticationStatsBroadcastReceiver(
+ mContext,
+ BiometricsProtoEnums.MODALITY_FACE,
+ (AuthenticationStatsCollector collector) -> {
+ Slog.d(getTag(), "Initializing AuthenticationStatsCollector");
+ mAuthenticationStatsCollector = collector;
+ });
+ }
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- if (prop.commonProps.componentInfo != null) {
- for (ComponentInfo info : prop.commonProps.componentInfo) {
- componentInfo.add(new ComponentInfoInternal(info.componentId,
- info.hardwareVersion, info.firmwareVersion, info.serialNumber,
- info.softwareVersion));
+ private void initSensors(boolean resetLockoutRequiresChallenge, SensorProps[] props) {
+ if (Flags.deHidl()) {
+ if (resetLockoutRequiresChallenge) {
+ Slog.d(getTag(), "Adding HIDL configs");
+ for (SensorProps prop : props) {
+ addHidlSensors(prop, resetLockoutRequiresChallenge);
+ }
+ } else {
+ Slog.d(getTag(), "Adding AIDL configs");
+ for (SensorProps prop : props) {
+ addAidlSensors(prop, resetLockoutRequiresChallenge);
}
}
+ } else {
+ for (SensorProps prop : props) {
+ final int sensorId = prop.commonProps.sensorId;
- final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
- prop.commonProps.sensorId, prop.commonProps.sensorStrength,
- prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
- prop.supportsDetectInteraction, prop.halControlsPreview,
- false /* resetLockoutRequiresChallenge */);
- final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
- internalProp, lockoutResetDispatcher, mBiometricContext);
- final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
- sensor.getLazySession().get().getUserId();
- mFaceSensors.addSensor(sensorId, sensor, userId,
- new SynchronousUserSwitchObserver() {
- @Override
- public void onUserSwitching(int newUserId) {
- scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
- }
- });
- Slog.d(getTag(), "Added: " + internalProp);
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ if (prop.commonProps.componentInfo != null) {
+ for (ComponentInfo info : prop.commonProps.componentInfo) {
+ componentInfo.add(new ComponentInfoInternal(info.componentId,
+ info.hardwareVersion, info.firmwareVersion, info.serialNumber,
+ info.softwareVersion));
+ }
+ }
+
+ final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
+ prop.commonProps.sensorId, prop.commonProps.sensorStrength,
+ prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
+ prop.supportsDetectInteraction, prop.halControlsPreview,
+ false /* resetLockoutRequiresChallenge */);
+ final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this,
+ mContext, mHandler, internalProp, mLockoutResetDispatcher,
+ mBiometricContext);
+ sensor.init(mLockoutResetDispatcher, this);
+ final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+ sensor.getLazySession().get().getUserId();
+ mFaceSensors.addSensor(sensorId, sensor, userId,
+ new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+ }
+ });
+ Slog.d(getTag(), "Added: " + internalProp);
+ }
}
}
+ private void addHidlSensors(SensorProps prop, boolean resetLockoutRequiresChallenge) {
+ final int sensorId = prop.commonProps.sensorId;
+ final Sensor sensor = new HidlToAidlSensorAdapter(getTag() + "/" + sensorId, this,
+ mContext, mHandler, prop, mLockoutResetDispatcher,
+ mBiometricContext, resetLockoutRequiresChallenge,
+ () -> {
+ //TODO: update to make this testable
+ scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(),
+ null /* callback */);
+ scheduleGetFeature(sensorId, new Binder(), ActivityManager.getCurrentUser(),
+ BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION, null,
+ mContext.getOpPackageName());
+ });
+ sensor.init(mLockoutResetDispatcher, this);
+ final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+ sensor.getLazySession().get().getUserId();
+ mFaceSensors.addSensor(sensorId, sensor, userId,
+ new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+ scheduleGetFeature(sensorId, new Binder(), newUserId,
+ BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION,
+ null, mContext.getOpPackageName());
+ }
+ });
+ Slog.d(getTag(), "Added: " + mFaceSensors.get(sensorId));
+ }
+
+ private void addAidlSensors(SensorProps prop, boolean resetLockoutRequiresChallenge) {
+ final int sensorId = prop.commonProps.sensorId;
+ final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext,
+ mHandler, prop, mLockoutResetDispatcher, mBiometricContext,
+ resetLockoutRequiresChallenge);
+ sensor.init(mLockoutResetDispatcher, this);
+ final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+ sensor.getLazySession().get().getUserId();
+ mFaceSensors.addSensor(sensorId, sensor, userId,
+ new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+ }
+ });
+ Slog.d(getTag(), "Added: " + mFaceSensors.get(sensorId));
+ }
+
private String getTag() {
return "FaceProvider/" + mHalInstanceName;
}
@@ -290,7 +367,10 @@
}
}
- private void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
+ /**
+ * Schedules FaceGetAuthenticatorIdClient for specific sensor and user.
+ */
+ protected void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
mHandler.post(() -> {
final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient(
mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
@@ -365,8 +445,12 @@
@Override
public int getLockoutModeForUser(int sensorId, int userId) {
- return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
- Utils.getCurrentStrength(sensorId));
+ if (Flags.deHidl()) {
+ return mFaceSensors.get(sensorId).getLockoutModeForUser(userId);
+ } else {
+ return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+ Utils.getCurrentStrength(sensorId));
+ }
}
@Override
@@ -376,13 +460,18 @@
@Override
public boolean isHardwareDetected(int sensorId) {
- return hasHalInstance();
+ if (Flags.deHidl()) {
+ return mFaceSensors.get(sensorId).isHardwareDetected(mHalInstanceName);
+ } else {
+ return hasHalInstance();
+ }
}
@Override
public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull IFaceServiceReceiver receiver, String opPackageName) {
mHandler.post(() -> {
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
mFaceSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId,
@@ -416,6 +505,7 @@
@Nullable Surface previewSurface, boolean debugConsent) {
final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
final int maxTemplatesPerUser = mFaceSensors.get(
sensorId).getSensorProperties().maxEnrollmentsPerUser;
final FaceEnrollClient client = new FaceEnrollClient(mContext,
@@ -427,18 +517,23 @@
BiometricsProtoEnums.CLIENT_UNKNOWN,
mAuthenticationStatsCollector),
mBiometricContext, maxTemplatesPerUser, debugConsent);
- scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
- mBiometricStateCallback, new ClientMonitorCallback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- ClientMonitorCallback.super.onClientFinished(clientMonitor, success);
- if (success) {
- scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
- scheduleInvalidationRequest(sensorId, userId);
+ if (Flags.deHidl()) {
+ scheduleForSensor(sensorId, client, mBiometricStateCallback);
+ } else {
+ scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
+ mBiometricStateCallback, new ClientMonitorCallback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ ClientMonitorCallback.super.onClientFinished(clientMonitor,
+ success);
+ if (success) {
+ scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
+ scheduleInvalidationRequest(sensorId, userId);
+ }
}
- }
- }));
+ }));
+ }
});
return id;
}
@@ -486,6 +581,13 @@
final int userId = options.getUserId();
final int sensorId = options.getSensorId();
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
+ final LockoutTracker lockoutTracker;
+ if (Flags.deHidl()) {
+ lockoutTracker = mFaceSensors.get(sensorId).getLockoutTracker(true /* forAuth */);
+ } else {
+ lockoutTracker = null;
+ }
final FaceAuthenticationClient client = new FaceAuthenticationClient(
mContext, mFaceSensors.get(sensorId).getLazySession(), token, requestId,
callback, operationId, restricted, options, cookie,
@@ -493,7 +595,7 @@
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric,
- mUsageStats, null /* lockoutTracker */,
+ mUsageStats, lockoutTracker,
allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@Override
@@ -555,6 +657,7 @@
private void scheduleRemoveSpecifiedIds(int sensorId, @NonNull IBinder token, int[] faceIds,
int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
mHandler.post(() -> {
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
final FaceRemovalClient client = new FaceRemovalClient(mContext,
mFaceSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), faceIds, userId,
@@ -571,6 +674,7 @@
@Override
public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) {
mHandler.post(() -> {
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
final FaceResetLockoutClient client = new FaceResetLockoutClient(
mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
@@ -578,8 +682,8 @@
BiometricsProtoEnums.CLIENT_UNKNOWN,
mAuthenticationStatsCollector),
mBiometricContext, hardwareAuthToken,
- mFaceSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
- Utils.getCurrentStrength(sensorId));
+ mFaceSensors.get(sensorId).getLockoutTracker(false/* forAuth */),
+ mLockoutResetDispatcher, Utils.getCurrentStrength(sensorId));
scheduleForSensor(sensorId, client);
});
@@ -590,6 +694,7 @@
boolean enabled, @NonNull byte[] hardwareAuthToken,
@NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
mHandler.post(() -> {
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
final List<Face> faces = FaceUtils.getInstance(sensorId)
.getBiometricsForUser(mContext, userId);
if (faces.isEmpty()) {
@@ -610,6 +715,7 @@
public void scheduleGetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName) {
mHandler.post(() -> {
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
final List<Face> faces = FaceUtils.getInstance(sensorId)
.getBiometricsForUser(mContext, userId);
if (faces.isEmpty()) {
@@ -641,6 +747,7 @@
public void scheduleInternalCleanup(int sensorId, int userId,
@Nullable ClientMonitorCallback callback, boolean favorHalEnrollments) {
mHandler.post(() -> {
+ mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
final FaceInternalCleanupClient client =
new FaceInternalCleanupClient(mContext,
mFaceSensors.get(sensorId).getLazySession(), userId,
@@ -760,4 +867,8 @@
}
biometricScheduler.startWatchdog();
}
+
+ public boolean getTestHalEnabled() {
+ return mTestHalEnabled;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
index 77b5592..d02eefa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.ISession;
import android.hardware.keymaster.HardwareAuthToken;
import android.os.RemoteException;
import android.util.Slog;
@@ -34,6 +35,7 @@
import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter;
import java.util.function.Supplier;
@@ -47,7 +49,7 @@
private static final String TAG = "FaceResetLockoutClient";
private final HardwareAuthToken mHardwareAuthToken;
- private final LockoutTracker mLockoutCache;
+ private final LockoutTracker mLockoutTracker;
private final LockoutResetDispatcher mLockoutResetDispatcher;
private final int mBiometricStrength;
@@ -60,7 +62,7 @@
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
0 /* cookie */, sensorId, logger, biometricContext);
mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
- mLockoutCache = lockoutTracker;
+ mLockoutTracker = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
mBiometricStrength = biometricStrength;
}
@@ -79,7 +81,11 @@
@Override
protected void startHalOperation() {
try {
- getFreshDaemon().getSession().resetLockout(mHardwareAuthToken);
+ final ISession session = getFreshDaemon().getSession();
+ session.resetLockout(mHardwareAuthToken);
+ if (session instanceof HidlToAidlSessionAdapter) {
+ mCallback.onClientFinished(this, true /* success */);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Unable to reset lockout", e);
mCallback.onClientFinished(this, false /* success */);
@@ -87,7 +93,7 @@
}
void onLockoutCleared() {
- resetLocalLockoutStateToNone(getSensorId(), getTargetUserId(), mLockoutCache,
+ resetLocalLockoutStateToNone(getSensorId(), getTargetUserId(), mLockoutTracker,
mLockoutResetDispatcher, getBiometricContext().getAuthSessionCoordinator(),
mBiometricStrength, getRequestId());
mCallback.onClientFinished(this, true /* success */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 54e66eb..3e5c599 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -21,16 +21,20 @@
import android.content.Context;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.biometrics.common.ComponentInfo;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.ISession;
+import android.hardware.biometrics.face.SensorProps;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
@@ -38,6 +42,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
@@ -49,12 +54,15 @@
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.StartUserClient;
import com.android.server.biometrics.sensors.StopUserClient;
import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
import com.android.server.biometrics.sensors.face.FaceUtils;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
@@ -71,25 +79,53 @@
@NonNull private final IBinder mToken;
@NonNull private final Handler mHandler;
@NonNull private final FaceSensorPropertiesInternal mSensorProperties;
- @NonNull private final UserAwareBiometricScheduler mScheduler;
- @NonNull private final LockoutCache mLockoutCache;
+ @NonNull private BiometricScheduler mScheduler;
+ @Nullable private LockoutTracker mLockoutTracker;
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
- @NonNull private final Supplier<AidlSession> mLazySession;
+ @NonNull private Supplier<AidlSession> mLazySession;
@Nullable AidlSession mCurrentSession;
+ @NonNull BiometricContext mBiometricContext;
Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
@NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull BiometricContext biometricContext, AidlSession session) {
+ @NonNull BiometricContext biometricContext, @Nullable AidlSession session) {
mTag = tag;
mProvider = provider;
mContext = context;
mToken = new Binder();
mHandler = handler;
mSensorProperties = sensorProperties;
- mScheduler = new UserAwareBiometricScheduler(tag,
+ mBiometricContext = biometricContext;
+ mAuthenticatorIds = new HashMap<>();
+ }
+
+ Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
+ @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull BiometricContext biometricContext) {
+ this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
+ biometricContext, null);
+ }
+
+ public Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
+ @NonNull Handler handler, @NonNull SensorProps prop,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull BiometricContext biometricContext,
+ boolean resetLockoutRequiresChallenge) {
+ this(tag, provider, context, handler,
+ getFaceSensorPropertiesInternal(prop, resetLockoutRequiresChallenge),
+ lockoutResetDispatcher, biometricContext, null);
+ }
+
+ /**
+ * Initialize biometric scheduler, lockout tracker and session for the sensor.
+ */
+ public void init(LockoutResetDispatcher lockoutResetDispatcher,
+ FaceProvider provider) {
+ mScheduler = new UserAwareBiometricScheduler(mTag,
BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */,
() -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
new UserAwareBiometricScheduler.UserSwitchCallback() {
@@ -98,7 +134,7 @@
public StopUserClient<?> getStopUserClient(int userId) {
return new FaceStopUserClient(mContext, mLazySession, mToken, userId,
mSensorProperties.sensorId,
- BiometricLogger.ofUnknown(mContext), biometricContext,
+ BiometricLogger.ofUnknown(mContext), mBiometricContext,
() -> mCurrentSession = null);
}
@@ -107,13 +143,36 @@
public StartUserClient<?, ?> getStartUserClient(int newUserId) {
final int sensorId = mSensorProperties.sensorId;
- final AidlResponseHandler resultController = new AidlResponseHandler(
- mContext, mScheduler, sensorId, newUserId,
- mLockoutCache, lockoutResetDispatcher,
- biometricContext.getAuthSessionCoordinator(), () -> {
- Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
- mCurrentSession = null;
- });
+ final AidlResponseHandler resultController;
+ if (Flags.deHidl()) {
+ resultController = new AidlResponseHandler(
+ mContext, mScheduler, sensorId, newUserId,
+ mLockoutTracker, lockoutResetDispatcher,
+ mBiometricContext.getAuthSessionCoordinator(), () -> {},
+ new AidlResponseHandler.AidlResponseHandlerCallback() {
+ @Override
+ public void onEnrollSuccess() {
+ mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId,
+ newUserId);
+ mProvider.scheduleInvalidationRequest(sensorId,
+ newUserId);
+ }
+
+ @Override
+ public void onHardwareUnavailable() {
+ Slog.e(mTag, "Face sensor hardware unavailable.");
+ mCurrentSession = null;
+ }
+ });
+ } else {
+ resultController = new AidlResponseHandler(
+ mContext, mScheduler, sensorId, newUserId,
+ mLockoutTracker, lockoutResetDispatcher,
+ mBiometricContext.getAuthSessionCoordinator(), () -> {
+ Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+ mCurrentSession = null;
+ });
+ }
final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
(userIdStarted, newSession, halInterfaceVersion) -> {
@@ -136,32 +195,42 @@
return new FaceStartUserClient(mContext, provider::getHalInstance,
mToken, newUserId, mSensorProperties.sensorId,
- BiometricLogger.ofUnknown(mContext), biometricContext,
+ BiometricLogger.ofUnknown(mContext), mBiometricContext,
resultController, userStartedCallback);
}
});
- mLockoutCache = new LockoutCache();
- mAuthenticatorIds = new HashMap<>();
mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
+ mLockoutTracker = new LockoutCache();
}
- Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,
- @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull BiometricContext biometricContext) {
- this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
- biometricContext, null);
+ private static FaceSensorPropertiesInternal getFaceSensorPropertiesInternal(SensorProps prop,
+ boolean resetLockoutRequiresChallenge) {
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ if (prop.commonProps.componentInfo != null) {
+ for (ComponentInfo info : prop.commonProps.componentInfo) {
+ componentInfo.add(new ComponentInfoInternal(info.componentId,
+ info.hardwareVersion, info.firmwareVersion, info.serialNumber,
+ info.softwareVersion));
+ }
+ }
+ final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
+ prop.commonProps.sensorId, prop.commonProps.sensorStrength,
+ prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
+ prop.supportsDetectInteraction, prop.halControlsPreview,
+ resetLockoutRequiresChallenge);
+
+ return internalProp;
}
- @NonNull Supplier<AidlSession> getLazySession() {
+ @NonNull public Supplier<AidlSession> getLazySession() {
return mLazySession;
}
- @NonNull FaceSensorPropertiesInternal getSensorProperties() {
+ @NonNull protected FaceSensorPropertiesInternal getSensorProperties() {
return mSensorProperties;
}
- @VisibleForTesting @Nullable AidlSession getSessionForUser(int userId) {
+ @VisibleForTesting @Nullable protected AidlSession getSessionForUser(int userId) {
if (mCurrentSession != null && mCurrentSession.getUserId() == userId) {
return mCurrentSession;
} else {
@@ -174,15 +243,18 @@
mProvider, this);
}
- @NonNull BiometricScheduler getScheduler() {
+ @NonNull public BiometricScheduler getScheduler() {
return mScheduler;
}
- @NonNull LockoutCache getLockoutCache() {
- return mLockoutCache;
+ @NonNull protected LockoutTracker getLockoutTracker(boolean forAuth) {
+ if (forAuth) {
+ return null;
+ }
+ return mLockoutTracker;
}
- @NonNull Map<Integer, Long> getAuthenticatorIds() {
+ @NonNull protected Map<Integer, Long> getAuthenticatorIds() {
return mAuthenticatorIds;
}
@@ -253,4 +325,49 @@
mScheduler.reset();
mCurrentSession = null;
}
+
+ protected BiometricContext getBiometricContext() {
+ return mBiometricContext;
+ }
+
+ protected Handler getHandler() {
+ return mHandler;
+ }
+
+ protected Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Schedules FaceUpdateActiveUserClient for user id.
+ */
+ public void scheduleFaceUpdateActiveUserClient(int userId) {}
+
+ /**
+ * Returns true if the sensor hardware is detected.
+ */
+ public boolean isHardwareDetected(String halInstanceName) {
+ if (mTestHalEnabled) {
+ return true;
+ }
+ return ServiceManager.checkService(IFace.DESCRIPTOR + "/" + halInstanceName) != null;
+ }
+
+ /**
+ * Returns lockout mode of this sensor.
+ */
+ @LockoutTracker.LockoutMode
+ public int getLockoutModeForUser(int userId) {
+ return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+ Utils.getCurrentStrength(mSensorProperties.sensorId));
+ }
+
+ public void setScheduler(BiometricScheduler scheduler) {
+ mScheduler = scheduler;
+ }
+
+ public void setLazySession(
+ Supplier<AidlSession> lazySession) {
+ mLazySession = lazySession;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
index 8385c3f..0c34d70 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java
@@ -27,13 +27,14 @@
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.HalClientMonitor;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.face.aidl.AidlSession;
import java.io.File;
import java.util.Map;
import java.util.function.Supplier;
-public class FaceUpdateActiveUserClient extends HalClientMonitor<IBiometricsFace> {
+public class FaceUpdateActiveUserClient extends StartUserClient<IBiometricsFace, AidlSession> {
private static final String TAG = "FaceUpdateActiveUserClient";
private static final String FACE_DATA_DIR = "facedata";
@@ -45,8 +46,18 @@
int sensorId, @NonNull BiometricLogger logger,
@NonNull BiometricContext biometricContext, boolean hasEnrolledBiometrics,
@NonNull Map<Integer, Long> authenticatorIds) {
- super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, logger, biometricContext);
+ this(context, lazyDaemon, (newUserId, newUser, halInterfaceVersion) -> {},
+ userId, owner, sensorId, logger, biometricContext, hasEnrolledBiometrics,
+ authenticatorIds);
+ }
+
+ FaceUpdateActiveUserClient(@NonNull Context context,
+ @NonNull Supplier<IBiometricsFace> lazyDaemon, UserStartedCallback userStartedCallback,
+ int userId, @NonNull String owner, int sensorId, @NonNull BiometricLogger logger,
+ @NonNull BiometricContext biometricContext, boolean hasEnrolledBiometrics,
+ @NonNull Map<Integer, Long> authenticatorIds) {
+ super(context, lazyDaemon, null /* token */, userId, sensorId, logger, biometricContext,
+ userStartedCallback);
mHasEnrolledBiometrics = hasEnrolledBiometrics;
mAuthenticatorIds = authenticatorIds;
}
@@ -77,6 +88,7 @@
daemon.setActiveUser(getTargetUserId(), storePath.getAbsolutePath());
mAuthenticatorIds.put(getTargetUserId(),
mHasEnrolledBiometrics ? daemon.getAuthenticatorId().value : 0L);
+ mUserStartedCallback.onUserStarted(getTargetUserId(), null, 0);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to setActiveUser: " + e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java
index 36a9790..7a574ce 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java
@@ -112,4 +112,8 @@
void onAuthenticatorIdRetrieved(long authenticatorId) {
mAidlResponseHandler.onAuthenticatorIdRetrieved(authenticatorId);
}
+
+ void onUnsupportedClientScheduled() {
+ mAidlResponseHandler.onUnsupportedClientScheduled();
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
new file mode 100644
index 0000000..6355cb5
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.hidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.face.SensorProps;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.os.Handler;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.face.FaceUtils;
+import com.android.server.biometrics.sensors.face.LockoutHalImpl;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.face.aidl.AidlSession;
+import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
+import com.android.server.biometrics.sensors.face.aidl.Sensor;
+
+/**
+ * Convert HIDL sensor configurations to an AIDL Sensor.
+ */
+public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRecipient{
+
+ private static final String TAG = "HidlToAidlSensorAdapter";
+
+ private IBiometricsFace mDaemon;
+ private AidlSession mSession;
+ private int mCurrentUserId = UserHandle.USER_NULL;
+ private final Runnable mInternalCleanupAndGetFeatureRunnable;
+ private final FaceProvider mFaceProvider;
+ private final LockoutResetDispatcher mLockoutResetDispatcher;
+ private final AuthSessionCoordinator mAuthSessionCoordinator;
+ private final AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+ private final StartUserClient.UserStartedCallback<AidlSession> mUserStartedCallback =
+ (newUserId, newUser, halInterfaceVersion) -> {
+ if (newUserId != mCurrentUserId) {
+ handleUserChanged(newUserId);
+ }
+ };
+ private LockoutHalImpl mLockoutTracker;
+
+ public HidlToAidlSensorAdapter(@NonNull String tag,
+ @NonNull FaceProvider provider,
+ @NonNull Context context,
+ @NonNull Handler handler,
+ @NonNull SensorProps prop,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull BiometricContext biometricContext,
+ boolean resetLockoutRequiresChallenge,
+ @NonNull Runnable internalCleanupAndGetFeatureRunnable) {
+ this(tag, provider, context, handler, prop, lockoutResetDispatcher, biometricContext,
+ resetLockoutRequiresChallenge, internalCleanupAndGetFeatureRunnable,
+ new AuthSessionCoordinator(), null /* daemon */,
+ null /* onEnrollSuccessCallback */);
+ }
+
+ @VisibleForTesting
+ HidlToAidlSensorAdapter(@NonNull String tag,
+ @NonNull FaceProvider provider,
+ @NonNull Context context,
+ @NonNull Handler handler,
+ @NonNull SensorProps prop,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull BiometricContext biometricContext,
+ boolean resetLockoutRequiresChallenge,
+ @NonNull Runnable internalCleanupAndGetFeatureRunnable,
+ @NonNull AuthSessionCoordinator authSessionCoordinator,
+ @Nullable IBiometricsFace daemon,
+ @Nullable AidlResponseHandler.AidlResponseHandlerCallback aidlResponseHandlerCallback) {
+ super(tag, provider, context, handler, prop, lockoutResetDispatcher, biometricContext,
+ resetLockoutRequiresChallenge);
+ mInternalCleanupAndGetFeatureRunnable = internalCleanupAndGetFeatureRunnable;
+ mFaceProvider = provider;
+ mLockoutResetDispatcher = lockoutResetDispatcher;
+ mAuthSessionCoordinator = authSessionCoordinator;
+ mDaemon = daemon;
+ mAidlResponseHandlerCallback = aidlResponseHandlerCallback == null
+ ? new AidlResponseHandler.AidlResponseHandlerCallback() {
+ @Override
+ public void onEnrollSuccess() {
+ scheduleFaceUpdateActiveUserClient(mCurrentUserId);
+ }
+
+ @Override
+ public void onHardwareUnavailable() {
+ mDaemon = null;
+ mCurrentUserId = UserHandle.USER_NULL;
+ }
+ } : aidlResponseHandlerCallback;
+ }
+
+ @Override
+ public void scheduleFaceUpdateActiveUserClient(int userId) {
+ getScheduler().scheduleClientMonitor(getFaceUpdateActiveUserClient(userId));
+ }
+
+ @Override
+ public void serviceDied(long cookie) {
+ Slog.d(TAG, "HAL died.");
+ mDaemon = null;
+ }
+
+ @Override
+ public boolean isHardwareDetected(String halInstanceName) {
+ return getIBiometricsFace() != null;
+ }
+
+ @Override
+ @LockoutTracker.LockoutMode
+ public int getLockoutModeForUser(int userId) {
+ return mLockoutTracker.getLockoutModeForUser(userId);
+ }
+
+ @Override
+ public void init(LockoutResetDispatcher lockoutResetDispatcher,
+ FaceProvider provider) {
+ setScheduler(new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
+ null /* gestureAvailabilityTracker */));
+ setLazySession(this::getSession);
+ mLockoutTracker = new LockoutHalImpl();
+ }
+
+ @Override
+ @VisibleForTesting @Nullable protected AidlSession getSessionForUser(int userId) {
+ if (mSession != null && mSession.getUserId() == userId) {
+ return mSession;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ protected LockoutTracker getLockoutTracker(boolean forAuth) {
+ return mLockoutTracker;
+ }
+
+ @NonNull AidlSession getSession() {
+ if (mDaemon != null && mSession != null) {
+ return mSession;
+ } else {
+ return mSession = new AidlSession(getContext(), this::getIBiometricsFace,
+ mCurrentUserId, getAidlResponseHandler());
+ }
+ }
+
+ private AidlResponseHandler getAidlResponseHandler() {
+ return new AidlResponseHandler(getContext(), getScheduler(), getSensorProperties().sensorId,
+ mCurrentUserId, mLockoutTracker, mLockoutResetDispatcher,
+ mAuthSessionCoordinator, () -> {}, mAidlResponseHandlerCallback);
+ }
+
+ private IBiometricsFace getIBiometricsFace() {
+ if (mFaceProvider.getTestHalEnabled()) {
+ final TestHal testHal = new TestHal(getContext(), getSensorProperties().sensorId);
+ testHal.setCallback(new HidlToAidlCallbackConverter(getAidlResponseHandler()));
+ return testHal;
+ }
+
+ if (mDaemon != null) {
+ return mDaemon;
+ }
+
+ Slog.d(TAG, "Daemon was null, reconnecting, current operation: "
+ + getScheduler().getCurrentClient());
+
+ try {
+ mDaemon = IBiometricsFace.getService();
+ } catch (java.util.NoSuchElementException e) {
+ // Service doesn't exist or cannot be opened.
+ Slog.w(TAG, "NoSuchElementException", e);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get face HAL", e);
+ }
+
+ if (mDaemon == null) {
+ Slog.w(TAG, "Face HAL not available");
+ return null;
+ }
+
+ mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
+
+ scheduleLoadAuthenticatorIds();
+ mInternalCleanupAndGetFeatureRunnable.run();
+ return mDaemon;
+ }
+
+ @VisibleForTesting void handleUserChanged(int newUserId) {
+ Slog.d(TAG, "User changed. Current user is " + newUserId);
+ mSession = null;
+ mCurrentUserId = newUserId;
+ }
+
+ private void scheduleLoadAuthenticatorIds() {
+ // Note that this can be performed on the scheduler (as opposed to being done immediately
+ // when the HAL is (re)loaded, since
+ // 1) If this is truly the first time it's being performed (e.g. system has just started),
+ // this will be run very early and way before any applications need to generate keys.
+ // 2) If this is being performed to refresh the authenticatorIds (e.g. HAL crashed and has
+ // just been reloaded), the framework already has a cache of the authenticatorIds. This
+ // is safe because authenticatorIds only change when A) new template has been enrolled,
+ // or B) all templates are removed.
+ getHandler().post(() -> {
+ for (UserInfo user : UserManager.get(getContext()).getAliveUsers()) {
+ final int targetUserId = user.id;
+ if (!getAuthenticatorIds().containsKey(targetUserId)) {
+ scheduleFaceUpdateActiveUserClient(targetUserId);
+ }
+ }
+ });
+ }
+
+ private FaceUpdateActiveUserClient getFaceUpdateActiveUserClient(int userId) {
+ return new FaceUpdateActiveUserClient(getContext(), this::getIBiometricsFace,
+ mUserStartedCallback, userId, TAG, getSensorProperties().sensorId,
+ BiometricLogger.ofUnknown(getContext()), getBiometricContext(),
+ !FaceUtils.getInstance(getSensorProperties().sensorId).getBiometricsForUser(
+ getContext(), userId).isEmpty(),
+ getAuthenticatorIds());
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
similarity index 88%
rename from services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java
rename to services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
index 489b213..5daf2d4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
@@ -47,34 +47,37 @@
import java.util.function.Supplier;
/**
- * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation.
+ * Adapter to convert HIDL methods into AIDL interface {@link ISession}.
*/
-public class AidlToHidlAdapter implements ISession {
+public class HidlToAidlSessionAdapter implements ISession {
- private final String TAG = "AidlToHidlAdapter";
+ private static final String TAG = "HidlToAidlSessionAdapter";
+
private static final int CHALLENGE_TIMEOUT_SEC = 600;
@DurationMillisLong
private static final int GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS = 60 * 1000;
@DurationMillisLong
private static final int GENERATE_CHALLENGE_COUNTER_TTL_MILLIS = CHALLENGE_TIMEOUT_SEC * 1000;
private static final int INVALID_VALUE = -1;
+ @VisibleForTesting static final int ENROLL_TIMEOUT_SEC = 75;
+
private final Clock mClock;
private final List<Long> mGeneratedChallengeCount = new ArrayList<>();
- @VisibleForTesting static final int ENROLL_TIMEOUT_SEC = 75;
+ private final int mUserId;
+ private final Context mContext;
+
private long mGenerateChallengeCreatedAt = INVALID_VALUE;
private long mGenerateChallengeResult = INVALID_VALUE;
@NonNull private Supplier<IBiometricsFace> mSession;
- private final int mUserId;
private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter;
- private final Context mContext;
private int mFeature = INVALID_VALUE;
- public AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session,
+ public HidlToAidlSessionAdapter(Context context, Supplier<IBiometricsFace> session,
int userId, AidlResponseHandler aidlResponseHandler) {
this(context, session, userId, aidlResponseHandler, Clock.systemUTC());
}
- AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session, int userId,
+ HidlToAidlSessionAdapter(Context context, Supplier<IBiometricsFace> session, int userId,
AidlResponseHandler aidlResponseHandler, Clock clock) {
mSession = session;
mUserId = userId;
@@ -83,42 +86,11 @@
setCallback(aidlResponseHandler);
}
- private void setCallback(AidlResponseHandler aidlResponseHandler) {
- mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
- try {
- mSession.get().setCallback(mHidlToAidlCallbackConverter);
- } catch (RemoteException e) {
- Slog.d(TAG, "Failed to set callback");
- }
- }
-
@Override
public IBinder asBinder() {
return null;
}
- private boolean isGeneratedChallengeCacheValid() {
- return mGenerateChallengeCreatedAt != INVALID_VALUE
- && mGenerateChallengeResult != INVALID_VALUE
- && mClock.millis() - mGenerateChallengeCreatedAt
- < GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS;
- }
-
- private void incrementChallengeCount() {
- mGeneratedChallengeCount.add(0, mClock.millis());
- }
-
- private int decrementChallengeCount() {
- final long now = mClock.millis();
- // ignore values that are old in case generate/revoke calls are not matched
- // this doesn't ensure revoke if calls are mismatched but it keeps the list from growing
- mGeneratedChallengeCount.removeIf(x -> now - x > GENERATE_CHALLENGE_COUNTER_TTL_MILLIS);
- if (!mGeneratedChallengeCount.isEmpty()) {
- mGeneratedChallengeCount.remove(0);
- }
- return mGeneratedChallengeCount.size();
- }
-
@Override
public void generateChallenge() throws RemoteException {
incrementChallengeCount();
@@ -150,7 +122,7 @@
@Override
public EnrollmentStageConfig[] getEnrollmentConfig(byte enrollmentType) throws RemoteException {
- //unsupported in HIDL
+ Slog.e(TAG, "getEnrollmentConfig unsupported in HIDL");
return null;
}
@@ -244,19 +216,6 @@
}
}
- private int getFaceId() {
- FaceManager faceManager = mContext.getSystemService(FaceManager.class);
- List<Face> faces = faceManager.getEnrolledFaces(mUserId);
- if (faces.isEmpty()) {
- Slog.d(TAG, "No faces to get feature from.");
- mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
- BiometricFaceConstants.FACE_ERROR_NOT_ENROLLED, 0 /* vendorCode */);
- return INVALID_VALUE;
- }
-
- return faces.get(0).getBiometricId();
- }
-
@Override
public void getAuthenticatorId() throws RemoteException {
long authenticatorId = mSession.get().getAuthenticatorId().value;
@@ -265,7 +224,8 @@
@Override
public void invalidateAuthenticatorId() throws RemoteException {
- //unsupported in HIDL
+ Slog.e(TAG, "invalidateAuthenticatorId unsupported in HIDL");
+ mHidlToAidlCallbackConverter.onUnsupportedClientScheduled();
}
@Override
@@ -279,47 +239,105 @@
@Override
public void close() throws RemoteException {
- //Unsupported in HIDL
+ Slog.e(TAG, "close unsupported in HIDL");
}
@Override
public ICancellationSignal authenticateWithContext(long operationId, OperationContext context)
throws RemoteException {
- //Unsupported in HIDL
- return null;
+ Slog.e(TAG, "authenticateWithContext unsupported in HIDL");
+ return authenticate(operationId);
}
@Override
public ICancellationSignal enrollWithContext(HardwareAuthToken hat, byte type, byte[] features,
NativeHandle previewSurface, OperationContext context) throws RemoteException {
- //Unsupported in HIDL
- return null;
+ Slog.e(TAG, "enrollWithContext unsupported in HIDL");
+ return enroll(hat, type, features, previewSurface);
}
@Override
public ICancellationSignal detectInteractionWithContext(OperationContext context)
throws RemoteException {
- //Unsupported in HIDL
- return null;
+ Slog.e(TAG, "detectInteractionWithContext unsupported in HIDL");
+ return detectInteraction();
}
@Override
public void onContextChanged(OperationContext context) throws RemoteException {
- //Unsupported in HIDL
+ Slog.e(TAG, "onContextChanged unsupported in HIDL");
}
@Override
public int getInterfaceVersion() throws RemoteException {
- //Unsupported in HIDL
+ Slog.e(TAG, "getInterfaceVersion unsupported in HIDL");
return 0;
}
@Override
public String getInterfaceHash() throws RemoteException {
+ Slog.e(TAG, "getInterfaceHash unsupported in HIDL");
+ return null;
+ }
+
+ @Override
+ public ICancellationSignal enrollWithOptions(FaceEnrollOptions options) {
//Unsupported in HIDL
return null;
}
+ private boolean isGeneratedChallengeCacheValid() {
+ return mGenerateChallengeCreatedAt != INVALID_VALUE
+ && mGenerateChallengeResult != INVALID_VALUE
+ && mClock.millis() - mGenerateChallengeCreatedAt
+ < GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS;
+ }
+
+ private void incrementChallengeCount() {
+ mGeneratedChallengeCount.add(0, mClock.millis());
+ }
+
+ private int decrementChallengeCount() {
+ final long now = mClock.millis();
+ // ignore values that are old in case generate/revoke calls are not matched
+ // this doesn't ensure revoke if calls are mismatched but it keeps the list from growing
+ mGeneratedChallengeCount.removeIf(x -> now - x > GENERATE_CHALLENGE_COUNTER_TTL_MILLIS);
+ if (!mGeneratedChallengeCount.isEmpty()) {
+ mGeneratedChallengeCount.remove(0);
+ }
+ return mGeneratedChallengeCount.size();
+ }
+
+ private void setCallback(AidlResponseHandler aidlResponseHandler) {
+ mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
+ try {
+ if (mSession.get() != null) {
+ long halId = mSession.get().setCallback(mHidlToAidlCallbackConverter).value;
+ Slog.d(TAG, "Face HAL ready, HAL ID: " + halId);
+ if (halId == 0) {
+ Slog.d(TAG, "Unable to set HIDL callback.");
+ }
+ } else {
+ Slog.e(TAG, "Unable to set HIDL callback. HIDL daemon is null.");
+ }
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to set callback");
+ }
+ }
+
+ private int getFaceId() {
+ FaceManager faceManager = mContext.getSystemService(FaceManager.class);
+ List<Face> faces = faceManager.getEnrolledFaces(mUserId);
+ if (faces.isEmpty()) {
+ Slog.d(TAG, "No faces to get feature from.");
+ mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId,
+ BiometricFaceConstants.FACE_ERROR_NOT_ENROLLED, 0 /* vendorCode */);
+ return INVALID_VALUE;
+ }
+
+ return faces.get(0).getBiometricId();
+ }
+
/**
* Cancellation in HIDL occurs for the entire session, instead of a specific client.
*/
@@ -345,10 +363,4 @@
return null;
}
}
-
- @Override
- public ICancellationSignal enrollWithOptions(FaceEnrollOptions options) {
- //Unsupported in HIDL
- return null;
- }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 83b306b..e01d672 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -45,9 +45,11 @@
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.PointerContext;
+import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintServiceReceiver;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
@@ -80,6 +82,7 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.SystemService;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.AuthenticationStateListeners;
@@ -127,6 +130,8 @@
@NonNull
private final Function<String, FingerprintProvider> mFingerprintProvider;
@NonNull
+ private final FingerprintProviderFunction mFingerprintProviderFunction;
+ @NonNull
private final BiometricStateCallback<ServiceProvider, FingerprintSensorPropertiesInternal>
mBiometricStateCallback;
@NonNull
@@ -136,6 +141,11 @@
@NonNull
private final FingerprintServiceRegistry mRegistry;
+ interface FingerprintProviderFunction {
+ FingerprintProvider getFingerprintProvider(Pair<String, SensorProps[]> filteredSensorProp,
+ boolean resetLockoutRequiresHardwareAuthToken);
+ }
+
/** Receives the incoming binder calls from FingerprintManager. */
@VisibleForTesting
final IFingerprintService.Stub mServiceWrapper = new IFingerprintService.Stub() {
@@ -874,6 +884,18 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
+ public void registerAuthenticatorsLegacy(
+ @NonNull FingerprintSensorConfigurations fingerprintSensorConfigurations) {
+ super.registerAuthenticatorsLegacy_enforcePermission();
+ if (!fingerprintSensorConfigurations.hasSensorConfigurations()) {
+ Slog.d(TAG, "No fingerprint sensors available.");
+ return;
+ }
+ mRegistry.registerAll(() -> getProviders(fingerprintSensorConfigurations));
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ @Override // Binder call
public void registerAuthenticators(
@NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
super.registerAuthenticators_enforcePermission();
@@ -1021,7 +1043,8 @@
() -> IBiometricService.Stub.asInterface(
ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
() -> ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR),
- null /* fingerprintProvider */);
+ null /* fingerprintProvider */,
+ null /* fingerprintProviderFunction */);
}
@VisibleForTesting
@@ -1029,7 +1052,8 @@
BiometricContext biometricContext,
Supplier<IBiometricService> biometricServiceSupplier,
Supplier<String[]> aidlInstanceNameSupplier,
- Function<String, FingerprintProvider> fingerprintProvider) {
+ Function<String, FingerprintProvider> fingerprintProvider,
+ FingerprintProviderFunction fingerprintProviderFunction) {
super(context);
mBiometricContext = biometricContext;
mAidlInstanceNameSupplier = aidlInstanceNameSupplier;
@@ -1049,7 +1073,8 @@
return new FingerprintProvider(getContext(),
mBiometricStateCallback, mAuthenticationStateListeners,
fp.getSensorProps(), name, mLockoutResetDispatcher,
- mGestureAvailabilityDispatcher, mBiometricContext);
+ mGestureAvailabilityDispatcher, mBiometricContext,
+ true /* resetLockoutRequiresHardwareAuthToken */);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
}
@@ -1059,6 +1084,22 @@
return null;
};
+ if (Flags.deHidl()) {
+ mFingerprintProviderFunction = fingerprintProviderFunction == null
+ ? (filteredSensorProps, resetLockoutRequiresHardwareAuthToken) ->
+ new FingerprintProvider(
+ getContext(), mBiometricStateCallback,
+ mAuthenticationStateListeners,
+ filteredSensorProps.second,
+ filteredSensorProps.first, mLockoutResetDispatcher,
+ mGestureAvailabilityDispatcher,
+ mBiometricContext,
+ resetLockoutRequiresHardwareAuthToken)
+ : fingerprintProviderFunction;
+ } else {
+ mFingerprintProviderFunction =
+ (filteredSensorProps, resetLockoutRequiresHardwareAuthToken) -> null;
+ }
mHandler = new Handler(Looper.getMainLooper());
mRegistry = new FingerprintServiceRegistry(mServiceWrapper, biometricServiceSupplier);
mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@@ -1070,6 +1111,44 @@
});
}
+ @NonNull
+ private List<ServiceProvider> getProviders(@NonNull FingerprintSensorConfigurations
+ fingerprintSensorConfigurations) {
+ final List<ServiceProvider> providers = new ArrayList<>();
+ final Pair<String, SensorProps[]> filteredSensorProps = filterAvailableHalInstances(
+ fingerprintSensorConfigurations);
+ providers.add(mFingerprintProviderFunction.getFingerprintProvider(filteredSensorProps,
+ fingerprintSensorConfigurations.getResetLockoutRequiresHardwareAuthToken()));
+
+ return providers;
+ }
+
+ @NonNull
+ private Pair<String, SensorProps[]> filterAvailableHalInstances(
+ FingerprintSensorConfigurations fingerprintSensorConfigurations) {
+ Pair<String, SensorProps[]> finalSensorPair =
+ fingerprintSensorConfigurations.getSensorPair();
+ if (fingerprintSensorConfigurations.isSingleSensorConfigurationPresent()) {
+ return finalSensorPair;
+ }
+
+ final Pair<String, SensorProps[]> virtualSensorPropsPair = fingerprintSensorConfigurations
+ .getSensorPairForInstance("virtual");
+ if (Utils.isVirtualEnabled(getContext())) {
+ if (virtualSensorPropsPair != null) {
+ return virtualSensorPropsPair;
+ } else {
+ Slog.e(TAG, "Could not find virtual interface while it is enabled");
+ return finalSensorPair;
+ }
+ } else {
+ if (virtualSensorPropsPair != null) {
+ return fingerprintSensorConfigurations.getSensorPairNotForInstance("virtual");
+ }
+ }
+ return finalSensorPair;
+ }
+
private Pair<List<FingerprintSensorPropertiesInternal>, List<String>>
filterAvailableHalInstances(
@NonNull List<FingerprintSensorPropertiesInternal> hidlInstances,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
index 4a01943..bd21cf4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
@@ -25,6 +25,7 @@
import android.hardware.keymaster.HardwareAuthToken;
import android.util.Slog;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.AcquisitionClient;
@@ -34,9 +35,9 @@
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.EnumerateConsumer;
import com.android.server.biometrics.sensors.ErrorConsumer;
-import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutConsumer;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.RemovalConsumer;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
@@ -59,6 +60,21 @@
void onHardwareUnavailable();
}
+ /**
+ * Interface to send results to the AidlResponseHandler's owner.
+ */
+ public interface AidlResponseHandlerCallback {
+ /**
+ * Invoked when enrollment is successful.
+ */
+ void onEnrollSuccess();
+
+ /**
+ * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
+ */
+ void onHardwareUnavailable();
+ }
+
private static final String TAG = "AidlResponseHandler";
@NonNull
@@ -68,28 +84,49 @@
private final int mSensorId;
private final int mUserId;
@NonNull
- private final LockoutCache mLockoutCache;
+ private final LockoutTracker mLockoutTracker;
@NonNull
private final LockoutResetDispatcher mLockoutResetDispatcher;
@NonNull
private final AuthSessionCoordinator mAuthSessionCoordinator;
@NonNull
private final HardwareUnavailableCallback mHardwareUnavailableCallback;
+ @NonNull
+ private final AidlResponseHandlerCallback mAidlResponseHandlerCallback;
public AidlResponseHandler(@NonNull Context context,
@NonNull BiometricScheduler scheduler, int sensorId, int userId,
- @NonNull LockoutCache lockoutTracker,
+ @NonNull LockoutTracker lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull AuthSessionCoordinator authSessionCoordinator,
@NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
+ this(context, scheduler, sensorId, userId, lockoutTracker, lockoutResetDispatcher,
+ authSessionCoordinator, hardwareUnavailableCallback,
+ new AidlResponseHandlerCallback() {
+ @Override
+ public void onEnrollSuccess() {}
+
+ @Override
+ public void onHardwareUnavailable() {}
+ });
+ }
+
+ public AidlResponseHandler(@NonNull Context context,
+ @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+ @NonNull LockoutTracker lockoutTracker,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull AuthSessionCoordinator authSessionCoordinator,
+ @NonNull HardwareUnavailableCallback hardwareUnavailableCallback,
+ @NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) {
mContext = context;
mScheduler = scheduler;
mSensorId = sensorId;
mUserId = userId;
- mLockoutCache = lockoutTracker;
+ mLockoutTracker = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
mAuthSessionCoordinator = authSessionCoordinator;
mHardwareUnavailableCallback = hardwareUnavailableCallback;
+ mAidlResponseHandlerCallback = aidlResponseHandlerCallback;
}
@Override
@@ -105,27 +142,26 @@
@Override
public void onChallengeGenerated(long challenge) {
handleResponse(FingerprintGenerateChallengeClient.class, (c) -> c.onChallengeGenerated(
- mSensorId, mUserId, challenge), null);
+ mSensorId, mUserId, challenge));
}
@Override
public void onChallengeRevoked(long challenge) {
handleResponse(FingerprintRevokeChallengeClient.class, (c) -> c.onChallengeRevoked(
- challenge), null);
+ challenge));
}
/**
* Handles acquired messages sent by the HAL (specifically for HIDL HAL).
*/
public void onAcquired(int acquiredInfo, int vendorCode) {
- handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode),
- null);
+ handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode));
}
@Override
public void onAcquired(byte info, int vendorCode) {
handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(
- AidlConversionUtils.toFrameworkAcquiredInfo(info), vendorCode), null);
+ AidlConversionUtils.toFrameworkAcquiredInfo(info), vendorCode));
}
/**
@@ -135,9 +171,13 @@
handleResponse(ErrorConsumer.class, (c) -> {
c.onError(error, vendorCode);
if (error == Error.HW_UNAVAILABLE) {
- mHardwareUnavailableCallback.onHardwareUnavailable();
+ if (Flags.deHidl()) {
+ mAidlResponseHandlerCallback.onHardwareUnavailable();
+ } else {
+ mHardwareUnavailableCallback.onHardwareUnavailable();
+ }
}
- }, null);
+ });
}
@Override
@@ -158,8 +198,12 @@
.getUniqueName(mContext, currentUserId);
final Fingerprint fingerprint = new Fingerprint(name, currentUserId,
enrollmentId, mSensorId);
- handleResponse(FingerprintEnrollClient.class, (c) -> c.onEnrollResult(fingerprint,
- remaining), null);
+ handleResponse(FingerprintEnrollClient.class, (c) -> {
+ c.onEnrollResult(fingerprint, remaining);
+ if (remaining == 0) {
+ mAidlResponseHandlerCallback.onEnrollSuccess();
+ }
+ });
}
@Override
@@ -184,13 +228,12 @@
@Override
public void onLockoutTimed(long durationMillis) {
- handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis),
- null);
+ handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis));
}
@Override
public void onLockoutPermanent() {
- handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null);
+ handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent);
}
@Override
@@ -198,7 +241,7 @@
handleResponse(FingerprintResetLockoutClient.class,
FingerprintResetLockoutClient::onLockoutCleared,
(c) -> FingerprintResetLockoutClient.resetLocalLockoutStateToNone(
- mSensorId, mUserId, mLockoutCache, mLockoutResetDispatcher,
+ mSensorId, mUserId, mLockoutTracker, mLockoutResetDispatcher,
mAuthSessionCoordinator, Utils.getCurrentStrength(mSensorId),
-1 /* requestId */));
}
@@ -206,49 +249,74 @@
@Override
public void onInteractionDetected() {
handleResponse(FingerprintDetectClient.class,
- FingerprintDetectClient::onInteractionDetected, null);
+ FingerprintDetectClient::onInteractionDetected);
}
@Override
public void onEnrollmentsEnumerated(int[] enrollmentIds) {
if (enrollmentIds.length > 0) {
for (int i = 0; i < enrollmentIds.length; i++) {
- final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
- int finalI = i;
- handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(fp,
- enrollmentIds.length - finalI - 1), null);
+ onEnrollmentEnumerated(enrollmentIds[i],
+ enrollmentIds.length - i - 1 /* remaining */);
}
} else {
- handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(null,
- 0), null);
+ handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(
+ null /* identifier */,
+ 0 /* remaining */));
}
}
+ /**
+ * Handle enumerated fingerprint.
+ */
+ public void onEnrollmentEnumerated(int enrollmentId, int remaining) {
+ final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId);
+ handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(fp, remaining));
+ }
+
+ /**
+ * Handle removal of fingerprint.
+ */
+ public void onEnrollmentRemoved(int enrollmentId, int remaining) {
+ final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId);
+ handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(fp, remaining));
+ }
+
@Override
public void onEnrollmentsRemoved(int[] enrollmentIds) {
if (enrollmentIds.length > 0) {
for (int i = 0; i < enrollmentIds.length; i++) {
- final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId);
- int finalI = i;
- handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(fp,
- enrollmentIds.length - finalI - 1), null);
+ onEnrollmentRemoved(enrollmentIds[i], enrollmentIds.length - i - 1 /* remaining */);
}
} else {
- handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null, 0),
- null);
+ handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null /* identifier */,
+ 0 /* remaining */));
}
}
@Override
public void onAuthenticatorIdRetrieved(long authenticatorId) {
handleResponse(FingerprintGetAuthenticatorIdClient.class,
- (c) -> c.onAuthenticatorIdRetrieved(authenticatorId), null);
+ (c) -> c.onAuthenticatorIdRetrieved(authenticatorId));
}
@Override
public void onAuthenticatorIdInvalidated(long newAuthenticatorId) {
handleResponse(FingerprintInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated(
- newAuthenticatorId), null);
+ newAuthenticatorId));
+ }
+
+ /**
+ * Handle clients which are not supported in HIDL HAL.
+ */
+ public <T extends BaseClientMonitor> void onUnsupportedClientScheduled(Class<T> className) {
+ Slog.e(TAG, className + " is not supported in the HAL.");
+ handleResponse(className, (c) -> c.cancel());
+ }
+
+ private <T> void handleResponse(@NonNull Class<T> className,
+ @NonNull Consumer<T> action) {
+ handleResponse(className, action, null /* alternateAction */);
}
private <T> void handleResponse(@NonNull Class<T> className,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
index 299a310..8ff105b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java
@@ -20,7 +20,7 @@
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import com.android.server.biometrics.sensors.fingerprint.hidl.AidlToHidlAdapter;
+import com.android.server.biometrics.sensors.fingerprint.hidl.HidlToAidlSessionAdapter;
import java.util.function.Supplier;
@@ -45,7 +45,7 @@
public AidlSession(@NonNull Supplier<IBiometricsFingerprint> session,
int userId, AidlResponseHandler aidlResponseHandler) {
- mSession = new AidlToHidlAdapter(session, userId, aidlResponseHandler);
+ mSession = new HidlToAidlSessionAdapter(session, userId, aidlResponseHandler);
mHalInterfaceVersion = 0;
mUserId = userId;
mAidlResponseHandler = aidlResponseHandler;
@@ -62,7 +62,7 @@
}
/** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */
- AidlResponseHandler getHalSessionCallback() {
+ public AidlResponseHandler getHalSessionCallback() {
return mAidlResponseHandler;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
index ea1a622..0353969 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java
@@ -30,7 +30,7 @@
import java.util.Map;
import java.util.function.Supplier;
-class FingerprintGetAuthenticatorIdClient extends HalClientMonitor<AidlSession> {
+public class FingerprintGetAuthenticatorIdClient extends HalClientMonitor<AidlSession> {
private static final String TAG = "FingerprintGetAuthenticatorIdClient";
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 032ab87..88a11d9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -59,6 +59,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
import com.android.server.biometrics.AuthenticationStatsCollector;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -73,6 +74,7 @@
import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
import com.android.server.biometrics.sensors.InvalidationRequesterClient;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.PerformanceTracker;
import com.android.server.biometrics.sensors.SensorList;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
@@ -80,6 +82,7 @@
import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
import com.android.server.biometrics.sensors.fingerprint.ServiceProvider;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
+import com.android.server.biometrics.sensors.fingerprint.hidl.HidlToAidlSensorAdapter;
import org.json.JSONArray;
import org.json.JSONException;
@@ -165,10 +168,12 @@
@NonNull SensorProps[] props, @NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
- @NonNull BiometricContext biometricContext) {
+ @NonNull BiometricContext biometricContext,
+ boolean resetLockoutRequiresHardwareAuthToken) {
this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext,
- null /* daemon */);
+ null /* daemon */, resetLockoutRequiresHardwareAuthToken,
+ false /* testHalEnabled */);
}
@VisibleForTesting FingerprintProvider(@NonNull Context context,
@@ -178,7 +183,9 @@
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull BiometricContext biometricContext,
- IFingerprint daemon) {
+ @Nullable IFingerprint daemon,
+ boolean resetLockoutRequiresHardwareAuthToken,
+ boolean testHalEnabled) {
mContext = context;
mBiometricStateCallback = biometricStateCallback;
mAuthenticationStateListeners = authenticationStateListeners;
@@ -191,62 +198,136 @@
mBiometricContext = biometricContext;
mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
mDaemon = daemon;
+ mTestHalEnabled = testHalEnabled;
- AuthenticationStatsBroadcastReceiver mBroadcastReceiver =
- new AuthenticationStatsBroadcastReceiver(
- mContext,
- BiometricsProtoEnums.MODALITY_FINGERPRINT,
- (AuthenticationStatsCollector collector) -> {
- Slog.d(getTag(), "Initializing AuthenticationStatsCollector");
- mAuthenticationStatsCollector = collector;
- });
+ initAuthenticationBroadcastReceiver();
+ initSensors(resetLockoutRequiresHardwareAuthToken, props, gestureAvailabilityDispatcher);
+ }
- final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);
+ private void initAuthenticationBroadcastReceiver() {
+ new AuthenticationStatsBroadcastReceiver(
+ mContext,
+ BiometricsProtoEnums.MODALITY_FINGERPRINT,
+ (AuthenticationStatsCollector collector) -> {
+ Slog.d(getTag(), "Initializing AuthenticationStatsCollector");
+ mAuthenticationStatsCollector = collector;
+ });
+ }
- for (SensorProps prop : props) {
- final int sensorId = prop.commonProps.sensorId;
-
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- if (prop.commonProps.componentInfo != null) {
- for (ComponentInfo info : prop.commonProps.componentInfo) {
- componentInfo.add(new ComponentInfoInternal(info.componentId,
- info.hardwareVersion, info.firmwareVersion, info.serialNumber,
- info.softwareVersion));
+ private void initSensors(boolean resetLockoutRequiresHardwareAuthToken, SensorProps[] props,
+ GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+ if (Flags.deHidl()) {
+ if (!resetLockoutRequiresHardwareAuthToken) {
+ Slog.d(getTag(), "Adding HIDL configs");
+ for (SensorProps sensorConfig: props) {
+ addHidlSensors(sensorConfig, gestureAvailabilityDispatcher,
+ resetLockoutRequiresHardwareAuthToken);
+ }
+ } else {
+ Slog.d(getTag(), "Adding AIDL configs");
+ final List<SensorLocationInternal> workaroundLocations =
+ getWorkaroundSensorProps(mContext);
+ for (SensorProps prop : props) {
+ addAidlSensors(prop, gestureAvailabilityDispatcher, workaroundLocations,
+ resetLockoutRequiresHardwareAuthToken);
}
}
+ } else {
+ final List<SensorLocationInternal> workaroundLocations =
+ getWorkaroundSensorProps(mContext);
- final FingerprintSensorPropertiesInternal internalProp =
- new FingerprintSensorPropertiesInternal(prop.commonProps.sensorId,
- prop.commonProps.sensorStrength,
- prop.commonProps.maxEnrollmentsPerUser,
- componentInfo,
- prop.sensorType,
- prop.halControlsIllumination,
- true /* resetLockoutRequiresHardwareAuthToken */,
- !workaroundLocations.isEmpty() ? workaroundLocations :
- Arrays.stream(prop.sensorLocations).map(location ->
- new SensorLocationInternal(
- location.display,
- location.sensorLocationX,
- location.sensorLocationY,
- location.sensorRadius))
- .collect(Collectors.toList()));
- final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
- internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher,
- mBiometricContext);
- final int sessionUserId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
- sensor.getLazySession().get().getUserId();
- mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
- new SynchronousUserSwitchObserver() {
- @Override
- public void onUserSwitching(int newUserId) {
- scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
- }
- });
- Slog.d(getTag(), "Added: " + internalProp);
+ for (SensorProps prop : props) {
+ final int sensorId = prop.commonProps.sensorId;
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ if (prop.commonProps.componentInfo != null) {
+ for (ComponentInfo info : prop.commonProps.componentInfo) {
+ componentInfo.add(new ComponentInfoInternal(info.componentId,
+ info.hardwareVersion, info.firmwareVersion, info.serialNumber,
+ info.softwareVersion));
+ }
+ }
+ final FingerprintSensorPropertiesInternal internalProp =
+ new FingerprintSensorPropertiesInternal(prop.commonProps.sensorId,
+ prop.commonProps.sensorStrength,
+ prop.commonProps.maxEnrollmentsPerUser,
+ componentInfo,
+ prop.sensorType,
+ prop.halControlsIllumination,
+ true /* resetLockoutRequiresHardwareAuthToken */,
+ !workaroundLocations.isEmpty() ? workaroundLocations :
+ Arrays.stream(prop.sensorLocations).map(
+ location -> new SensorLocationInternal(
+ location.display,
+ location.sensorLocationX,
+ location.sensorLocationY,
+ location.sensorRadius))
+ .collect(Collectors.toList()));
+ final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext,
+ mHandler, internalProp, mLockoutResetDispatcher,
+ gestureAvailabilityDispatcher, mBiometricContext);
+ sensor.init(gestureAvailabilityDispatcher,
+ mLockoutResetDispatcher);
+ final int sessionUserId =
+ sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+ sensor.getLazySession().get().getUserId();
+ mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
+ new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+ }
+ });
+ Slog.d(getTag(), "Added: " + mFingerprintSensors.get(sensorId).toString());
+ }
}
}
+ private void addHidlSensors(@NonNull SensorProps prop,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ boolean resetLockoutRequiresHardwareAuthToken) {
+ final int sensorId = prop.commonProps.sensorId;
+ final Sensor sensor = new HidlToAidlSensorAdapter(getTag() + "/"
+ + sensorId, this, mContext, mHandler,
+ prop, mLockoutResetDispatcher, gestureAvailabilityDispatcher,
+ mBiometricContext, resetLockoutRequiresHardwareAuthToken,
+ () -> scheduleInternalCleanup(sensorId, ActivityManager.getCurrentUser(),
+ null /* callback */));
+ sensor.init(gestureAvailabilityDispatcher, mLockoutResetDispatcher);
+ final int sessionUserId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+ sensor.getLazySession().get().getUserId();
+ mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
+ new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+ }
+ });
+ Slog.d(getTag(), "Added: " + mFingerprintSensors.get(sensorId).toString());
+ }
+
+ private void addAidlSensors(@NonNull SensorProps prop,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ List<SensorLocationInternal> workaroundLocations,
+ boolean resetLockoutRequiresHardwareAuthToken) {
+ final int sensorId = prop.commonProps.sensorId;
+ final Sensor sensor = new Sensor(getTag() + "/" + sensorId,
+ this, mContext, mHandler,
+ prop, mLockoutResetDispatcher, gestureAvailabilityDispatcher,
+ mBiometricContext, workaroundLocations,
+ resetLockoutRequiresHardwareAuthToken);
+ sensor.init(gestureAvailabilityDispatcher, mLockoutResetDispatcher);
+ final int sessionUserId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
+ sensor.getLazySession().get().getUserId();
+ mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
+ new SynchronousUserSwitchObserver() {
+ @Override
+ public void onUserSwitching(int newUserId) {
+ scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
+ }
+ });
+ Slog.d(getTag(), "Added: " + mFingerprintSensors.get(sensorId).toString());
+ }
+
private String getTag() {
return "FingerprintProvider/" + mHalInstanceName;
}
@@ -351,7 +432,10 @@
}
}
- private void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
+ /**
+ * Schedules FingerprintGetAuthenticatorIdClient for specific sensor and user.
+ */
+ protected void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
mHandler.post(() -> {
final FingerprintGetAuthenticatorIdClient client =
new FingerprintGetAuthenticatorIdClient(mContext,
@@ -387,8 +471,8 @@
BiometricsProtoEnums.CLIENT_UNKNOWN,
mAuthenticationStatsCollector),
mBiometricContext, hardwareAuthToken,
- mFingerprintSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
- Utils.getCurrentStrength(sensorId));
+ mFingerprintSensors.get(sensorId).getLockoutTracker(false /* forAuth */),
+ mLockoutResetDispatcher, Utils.getCurrentStrength(sensorId));
scheduleForSensor(sensorId, client);
});
}
@@ -443,18 +527,23 @@
mFingerprintSensors.get(sensorId).getSensorProperties(),
mUdfpsOverlayController, mSidefpsController,
mAuthenticationStateListeners, maxTemplatesPerUser, enrollReason);
- scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
- mBiometricStateCallback, new ClientMonitorCallback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- ClientMonitorCallback.super.onClientFinished(clientMonitor, success);
- if (success) {
- scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
- scheduleInvalidationRequest(sensorId, userId);
- }
- }
- }));
+ if (Flags.deHidl()) {
+ scheduleForSensor(sensorId, client, mBiometricStateCallback);
+ } else {
+ scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
+ mBiometricStateCallback, new ClientMonitorCallback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ ClientMonitorCallback.super.onClientFinished(
+ clientMonitor, success);
+ if (success) {
+ scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
+ scheduleInvalidationRequest(sensorId, userId);
+ }
+ }
+ }));
+ }
});
return id;
}
@@ -497,6 +586,13 @@
final int userId = options.getUserId();
final int sensorId = options.getSensorId();
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
+ final LockoutTracker lockoutTracker;
+ if (Flags.deHidl()) {
+ lockoutTracker = mFingerprintSensors.get(sensorId)
+ .getLockoutTracker(true /* forAuth */);
+ } else {
+ lockoutTracker = null;
+ }
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
mContext, mFingerprintSensors.get(sensorId).getLazySession(), token, requestId,
callback, operationId, restricted, options, cookie,
@@ -510,7 +606,7 @@
mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler,
Utils.getCurrentStrength(sensorId),
SystemClock.elapsedRealtimeClock(),
- null /* lockoutTracker */);
+ lockoutTracker);
scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@Override
@@ -636,6 +732,9 @@
@Override
public boolean isHardwareDetected(int sensorId) {
+ if (Flags.deHidl()) {
+ return mFingerprintSensors.get(sensorId).isHardwareDetected(mHalInstanceName);
+ }
return hasHalInstance();
}
@@ -674,8 +773,12 @@
@Override
public int getLockoutModeForUser(int sensorId, int userId) {
- return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
- Utils.getCurrentStrength(sensorId));
+ if (Flags.deHidl()) {
+ return mFingerprintSensors.get(sensorId).getLockoutModeForUser(userId);
+ } else {
+ return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+ Utils.getCurrentStrength(sensorId));
+ }
}
@Override
@@ -829,6 +932,10 @@
mTestHalEnabled = enabled;
}
+ public boolean getTestHalEnabled() {
+ return mTestHalEnabled;
+ }
+
// TODO(b/174868353): workaround for gaps in HAL interface (remove and get directly from HAL)
// reads values via an overlay instead of querying the HAL
@NonNull
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
index ec225a6..387ae12 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java
@@ -60,7 +60,8 @@
@Authenticators.Types int biometricStrength) {
super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
0 /* cookie */, sensorId, biometricLogger, biometricContext);
- mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
+ mHardwareAuthToken = hardwareAuthToken == null ? null :
+ HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken);
mLockoutCache = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
mBiometricStrength = biometricStrength;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index 893cb8f..dd887bb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -21,21 +21,28 @@
import android.content.Context;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.biometrics.SensorLocationInternal;
+import android.hardware.biometrics.common.ComponentInfo;
+import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
@@ -48,15 +55,20 @@
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.StartUserClient;
import com.android.server.biometrics.sensors.StopUserClient;
import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
/**
* Maintains the state of a single sensor within an instance of the
@@ -73,15 +85,17 @@
@NonNull private final IBinder mToken;
@NonNull private final Handler mHandler;
@NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
- @NonNull private final UserAwareBiometricScheduler mScheduler;
- @NonNull private final LockoutCache mLockoutCache;
+ @NonNull private BiometricScheduler mScheduler;
+ @NonNull private LockoutTracker mLockoutTracker;
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
+ @NonNull private final BiometricContext mBiometricContext;
@Nullable AidlSession mCurrentSession;
- @NonNull private final Supplier<AidlSession> mLazySession;
+ @NonNull private Supplier<AidlSession> mLazySession;
- Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
- @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
+ public Sensor(@NonNull String tag, @NonNull FingerprintProvider provider,
+ @NonNull Context context, @NonNull Handler handler,
+ @NonNull FingerprintSensorPropertiesInternal sensorProperties,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull BiometricContext biometricContext, AidlSession session) {
@@ -91,8 +105,38 @@
mToken = new Binder();
mHandler = handler;
mSensorProperties = sensorProperties;
- mLockoutCache = new LockoutCache();
- mScheduler = new UserAwareBiometricScheduler(tag,
+ mBiometricContext = biometricContext;
+ mAuthenticatorIds = new HashMap<>();
+ mCurrentSession = session;
+ }
+
+ Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
+ @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull BiometricContext biometricContext) {
+ this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
+ gestureAvailabilityDispatcher, biometricContext, null);
+ }
+
+ Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
+ @NonNull Handler handler, @NonNull SensorProps sensorProp,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull BiometricContext biometricContext,
+ @NonNull List<SensorLocationInternal> workaroundLocation,
+ boolean resetLockoutRequiresHardwareAuthToken) {
+ this(tag, provider, context, handler, getFingerprintSensorPropertiesInternal(sensorProp,
+ workaroundLocation, resetLockoutRequiresHardwareAuthToken),
+ lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext, null);
+ }
+
+ /**
+ * Initialize biometric scheduler, lockout tracker and session for the sensor.
+ */
+ public void init(GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ LockoutResetDispatcher lockoutResetDispatcher) {
+ mScheduler = new UserAwareBiometricScheduler(mTag,
BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties),
gestureAvailabilityDispatcher,
() -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
@@ -102,7 +146,7 @@
public StopUserClient<?> getStopUserClient(int userId) {
return new FingerprintStopUserClient(mContext, mLazySession, mToken,
userId, mSensorProperties.sensorId,
- BiometricLogger.ofUnknown(mContext), biometricContext,
+ BiometricLogger.ofUnknown(mContext), mBiometricContext,
() -> mCurrentSession = null);
}
@@ -111,13 +155,38 @@
public StartUserClient<?, ?> getStartUserClient(int newUserId) {
final int sensorId = mSensorProperties.sensorId;
- final AidlResponseHandler resultController = new AidlResponseHandler(
- mContext, mScheduler, sensorId, newUserId,
- mLockoutCache, lockoutResetDispatcher,
- biometricContext.getAuthSessionCoordinator(), () -> {
- Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
- mCurrentSession = null;
- });
+ final AidlResponseHandler resultController;
+
+ if (Flags.deHidl()) {
+ resultController = new AidlResponseHandler(
+ mContext, mScheduler, sensorId, newUserId,
+ mLockoutTracker, lockoutResetDispatcher,
+ mBiometricContext.getAuthSessionCoordinator(), () -> {},
+ new AidlResponseHandler.AidlResponseHandlerCallback() {
+ @Override
+ public void onEnrollSuccess() {
+ mProvider.scheduleLoadAuthenticatorIdsForUser(sensorId,
+ newUserId);
+ mProvider.scheduleInvalidationRequest(sensorId,
+ newUserId);
+ }
+
+ @Override
+ public void onHardwareUnavailable() {
+ Slog.e(mTag,
+ "Fingerprint sensor hardware unavailable.");
+ mCurrentSession = null;
+ }
+ });
+ } else {
+ resultController = new AidlResponseHandler(
+ mContext, mScheduler, sensorId, newUserId,
+ mLockoutTracker, lockoutResetDispatcher,
+ mBiometricContext.getAuthSessionCoordinator(), () -> {
+ Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+ mCurrentSession = null;
+ });
+ }
final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
(userIdStarted, newSession, halInterfaceVersion) -> {
@@ -133,40 +202,58 @@
+ "sensor: "
+ sensorId
+ ", user: " + userIdStarted);
- provider.scheduleInvalidationRequest(sensorId,
+ mProvider.scheduleInvalidationRequest(sensorId,
userIdStarted);
}
};
- return new FingerprintStartUserClient(mContext, provider::getHalInstance,
+ return new FingerprintStartUserClient(mContext, mProvider::getHalInstance,
mToken, newUserId, mSensorProperties.sensorId,
- BiometricLogger.ofUnknown(mContext), biometricContext,
+ BiometricLogger.ofUnknown(mContext), mBiometricContext,
resultController, userStartedCallback);
}
});
- mAuthenticatorIds = new HashMap<>();
+ mLockoutTracker = new LockoutCache();
mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
- mCurrentSession = session;
}
- Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,
- @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
- @NonNull BiometricContext biometricContext) {
- this(tag, provider, context, handler, sensorProperties, lockoutResetDispatcher,
- gestureAvailabilityDispatcher, biometricContext, null);
+ protected static FingerprintSensorPropertiesInternal getFingerprintSensorPropertiesInternal(
+ SensorProps prop, List<SensorLocationInternal> workaroundLocations,
+ boolean resetLockoutRequiresHardwareAuthToken) {
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ if (prop.commonProps.componentInfo != null) {
+ for (ComponentInfo info : prop.commonProps.componentInfo) {
+ componentInfo.add(new ComponentInfoInternal(info.componentId,
+ info.hardwareVersion, info.firmwareVersion, info.serialNumber,
+ info.softwareVersion));
+ }
+ }
+ return new FingerprintSensorPropertiesInternal(prop.commonProps.sensorId,
+ prop.commonProps.sensorStrength,
+ prop.commonProps.maxEnrollmentsPerUser,
+ componentInfo,
+ prop.sensorType,
+ prop.halControlsIllumination,
+ resetLockoutRequiresHardwareAuthToken,
+ !workaroundLocations.isEmpty() ? workaroundLocations :
+ Arrays.stream(prop.sensorLocations).map(location ->
+ new SensorLocationInternal(
+ location.display,
+ location.sensorLocationX,
+ location.sensorLocationY,
+ location.sensorRadius))
+ .collect(Collectors.toList()));
}
- @NonNull Supplier<AidlSession> getLazySession() {
+ @NonNull public Supplier<AidlSession> getLazySession() {
return mLazySession;
}
- @NonNull FingerprintSensorPropertiesInternal getSensorProperties() {
+ @NonNull public FingerprintSensorPropertiesInternal getSensorProperties() {
return mSensorProperties;
}
- @Nullable AidlSession getSessionForUser(int userId) {
+ @Nullable protected AidlSession getSessionForUser(int userId) {
if (mCurrentSession != null && mCurrentSession.getUserId() == userId) {
return mCurrentSession;
} else {
@@ -180,15 +267,18 @@
biometricStateCallback, mProvider, this);
}
- @NonNull BiometricScheduler getScheduler() {
+ @NonNull public BiometricScheduler getScheduler() {
return mScheduler;
}
- @NonNull LockoutCache getLockoutCache() {
- return mLockoutCache;
+ @NonNull protected LockoutTracker getLockoutTracker(boolean forAuth) {
+ if (forAuth) {
+ return null;
+ }
+ return mLockoutTracker;
}
- @NonNull Map<Integer, Long> getAuthenticatorIds() {
+ @NonNull public Map<Integer, Long> getAuthenticatorIds() {
return mAuthenticatorIds;
}
@@ -262,4 +352,49 @@
mScheduler.reset();
mCurrentSession = null;
}
+
+ @NonNull protected Handler getHandler() {
+ return mHandler;
+ }
+
+ @NonNull protected Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Returns true if the sensor hardware is detected.
+ */
+ protected boolean isHardwareDetected(String halInstance) {
+ if (mTestHalEnabled) {
+ return true;
+ }
+ return (ServiceManager.checkService(IFingerprint.DESCRIPTOR + "/" + halInstance)
+ != null);
+ }
+
+ @NonNull protected BiometricContext getBiometricContext() {
+ return mBiometricContext;
+ }
+
+ /**
+ * Returns lockout mode of this sensor.
+ */
+ @LockoutTracker.LockoutMode
+ public int getLockoutModeForUser(int userId) {
+ return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
+ Utils.getCurrentStrength(mSensorProperties.sensorId));
+ }
+
+ public void setScheduler(BiometricScheduler scheduler) {
+ mScheduler = scheduler;
+ }
+
+ public void setLazySession(
+ Supplier<AidlSession> lazySession) {
+ mLazySession = lazySession;
+ }
+
+ public FingerprintProvider getProvider() {
+ return mProvider;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
index a4e6025..5c5b992 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
@@ -29,7 +29,8 @@
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.HalClientMonitor;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
import java.io.File;
import java.util.Map;
@@ -38,7 +39,8 @@
/**
* Sets the HAL's current active user, and updates the framework's authenticatorId cache.
*/
-public class FingerprintUpdateActiveUserClient extends HalClientMonitor<IBiometricsFingerprint> {
+public class FingerprintUpdateActiveUserClient extends
+ StartUserClient<IBiometricsFingerprint, AidlSession> {
private static final String TAG = "FingerprintUpdateActiveUserClient";
private static final String FP_DATA_DIR = "fpdata";
@@ -53,11 +55,24 @@
@NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
@NonNull String owner, int sensorId,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- Supplier<Integer> currentUserId,
+ @NonNull Supplier<Integer> currentUserId,
boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
boolean forceUpdateAuthenticatorId) {
- super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, logger, biometricContext);
+ this(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext, currentUserId,
+ hasEnrolledBiometrics, authenticatorIds, forceUpdateAuthenticatorId,
+ (newUserId, newUser, halInterfaceVersion) -> {});
+ }
+
+ FingerprintUpdateActiveUserClient(@NonNull Context context,
+ @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
+ @NonNull String owner, int sensorId,
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ @NonNull Supplier<Integer> currentUserId,
+ boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
+ boolean forceUpdateAuthenticatorId,
+ @NonNull UserStartedCallback<AidlSession> userStartedCallback) {
+ super(context, lazyDaemon, null /* token */, userId, sensorId, logger, biometricContext,
+ userStartedCallback);
mCurrentUserId = currentUserId;
mForceUpdateAuthenticatorId = forceUpdateAuthenticatorId;
mHasEnrolledBiometrics = hasEnrolledBiometrics;
@@ -70,6 +85,7 @@
if (mCurrentUserId.get() == getTargetUserId() && !mForceUpdateAuthenticatorId) {
Slog.d(TAG, "Already user: " + mCurrentUserId + ", returning");
+ mUserStartedCallback.onUserStarted(getTargetUserId(), null, 0);
callback.onClientFinished(this, true /* success */);
return;
}
@@ -119,6 +135,7 @@
getFreshDaemon().setActiveGroup(targetId, mDirectory.getAbsolutePath());
mAuthenticatorIds.put(targetId, mHasEnrolledBiometrics
? getFreshDaemon().getAuthenticatorId() : 0L);
+ mUserStartedCallback.onUserStarted(targetId, null, 0);
mCallback.onClientFinished(this, true /* success */);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to setActiveGroup: " + e);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java
index c3e5cbe..e9a48e7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java
@@ -20,6 +20,7 @@
import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
import com.android.server.biometrics.HardwareAuthTokenUtils;
+import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
import java.util.ArrayList;
@@ -73,12 +74,12 @@
@Override
public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) {
- mAidlResponseHandler.onEnrollmentsRemoved(new int[]{fingerId});
+ mAidlResponseHandler.onEnrollmentRemoved(fingerId, remaining);
}
@Override
public void onEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
- mAidlResponseHandler.onEnrollmentsEnumerated(new int[]{fingerId});
+ mAidlResponseHandler.onEnrollmentEnumerated(fingerId, remaining);
}
void onChallengeGenerated(long challenge) {
@@ -92,4 +93,8 @@
void onResetLockout() {
mAidlResponseHandler.onLockoutCleared();
}
+
+ <T extends BaseClientMonitor> void unsupportedClientScheduled(Class<T> className) {
+ mAidlResponseHandler.onUnsupportedClientScheduled(className);
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
new file mode 100644
index 0000000..0bb61415
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.hidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.os.Handler;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.StopUserClient;
+import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
+import com.android.server.biometrics.sensors.fingerprint.aidl.Sensor;
+
+import java.util.ArrayList;
+
+/**
+ * Convert HIDL sensor configurations to an AIDL Sensor.
+ */
+public class HidlToAidlSensorAdapter extends Sensor implements IHwBinder.DeathRecipient {
+ private static final String TAG = "HidlToAidlSensorAdapter";
+
+ private final Runnable mInternalCleanupRunnable;
+ private final LockoutResetDispatcher mLockoutResetDispatcher;
+ private LockoutFrameworkImpl mLockoutTracker;
+ private final AuthSessionCoordinator mAuthSessionCoordinator;
+ private final AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+ private int mCurrentUserId = UserHandle.USER_NULL;
+ private IBiometricsFingerprint mDaemon;
+ private AidlSession mSession;
+
+ private final StartUserClient.UserStartedCallback<AidlSession> mUserStartedCallback =
+ (newUserId, newUser, halInterfaceVersion) -> {
+ if (mCurrentUserId != newUserId) {
+ handleUserChanged(newUserId);
+ }
+ };
+
+ public HidlToAidlSensorAdapter(@NonNull String tag, @NonNull FingerprintProvider provider,
+ @NonNull Context context, @NonNull Handler handler,
+ @NonNull SensorProps prop,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull BiometricContext biometricContext,
+ boolean resetLockoutRequiresHardwareAuthToken,
+ @NonNull Runnable internalCleanupRunnable) {
+ this(tag, provider, context, handler, prop, lockoutResetDispatcher,
+ gestureAvailabilityDispatcher, biometricContext,
+ resetLockoutRequiresHardwareAuthToken, internalCleanupRunnable,
+ new AuthSessionCoordinator(), null /* daemon */,
+ null /* onEnrollSuccessCallback */);
+ }
+
+ @VisibleForTesting
+ HidlToAidlSensorAdapter(@NonNull String tag, @NonNull FingerprintProvider provider,
+ @NonNull Context context, @NonNull Handler handler,
+ @NonNull SensorProps prop,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ @NonNull BiometricContext biometricContext,
+ boolean resetLockoutRequiresHardwareAuthToken,
+ @NonNull Runnable internalCleanupRunnable,
+ @NonNull AuthSessionCoordinator authSessionCoordinator,
+ @Nullable IBiometricsFingerprint daemon,
+ @Nullable AidlResponseHandler.AidlResponseHandlerCallback aidlResponseHandlerCallback) {
+ super(tag, provider, context, handler, getFingerprintSensorPropertiesInternal(prop,
+ new ArrayList<>(), resetLockoutRequiresHardwareAuthToken),
+ lockoutResetDispatcher,
+ gestureAvailabilityDispatcher,
+ biometricContext, null /* session */);
+ mLockoutResetDispatcher = lockoutResetDispatcher;
+ mInternalCleanupRunnable = internalCleanupRunnable;
+ mAuthSessionCoordinator = authSessionCoordinator;
+ mDaemon = daemon;
+ mAidlResponseHandlerCallback = aidlResponseHandlerCallback == null
+ ? new AidlResponseHandler.AidlResponseHandlerCallback() {
+ @Override
+ public void onEnrollSuccess() {
+ getScheduler()
+ .scheduleClientMonitor(getFingerprintUpdateActiveUserClient(
+ mCurrentUserId, true /* forceUpdateAuthenticatorIds */));
+ }
+
+ @Override
+ public void onHardwareUnavailable() {
+ mDaemon = null;
+ mSession = null;
+ mCurrentUserId = UserHandle.USER_NULL;
+ }
+ } : aidlResponseHandlerCallback;
+ }
+
+ @Override
+ public void serviceDied(long cookie) {
+ Slog.d(TAG, "HAL died.");
+ mSession = null;
+ mDaemon = null;
+ }
+
+ @Override
+ @LockoutTracker.LockoutMode
+ public int getLockoutModeForUser(int userId) {
+ return mLockoutTracker.getLockoutModeForUser(userId);
+ }
+
+ @Override
+ public void init(GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
+ LockoutResetDispatcher lockoutResetDispatcher) {
+ setLazySession(this::getSession);
+ setScheduler(new UserAwareBiometricScheduler(TAG,
+ BiometricScheduler.sensorTypeFromFingerprintProperties(getSensorProperties()),
+ gestureAvailabilityDispatcher, () -> mCurrentUserId, getUserSwitchCallback()));
+ mLockoutTracker = new LockoutFrameworkImpl(getContext(),
+ userId -> mLockoutResetDispatcher.notifyLockoutResetCallbacks(
+ getSensorProperties().sensorId));
+ }
+
+ @Override
+ @Nullable
+ protected AidlSession getSessionForUser(int userId) {
+ if (mSession != null && mSession.getUserId() == userId) {
+ return mSession;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ protected boolean isHardwareDetected(String halInstance) {
+ return getIBiometricsFingerprint() != null;
+ }
+
+ @NonNull
+ @Override
+ protected LockoutTracker getLockoutTracker(boolean forAuth) {
+ return mLockoutTracker;
+ }
+
+ private synchronized AidlSession getSession() {
+ if (mSession != null && mDaemon != null) {
+ return mSession;
+ } else {
+ return mSession = new AidlSession(this::getIBiometricsFingerprint,
+ mCurrentUserId, getAidlResponseHandler());
+ }
+ }
+
+ private AidlResponseHandler getAidlResponseHandler() {
+ return new AidlResponseHandler(getContext(),
+ getScheduler(),
+ getSensorProperties().sensorId,
+ mCurrentUserId,
+ mLockoutTracker,
+ mLockoutResetDispatcher,
+ mAuthSessionCoordinator,
+ () -> {}, mAidlResponseHandlerCallback);
+ }
+
+ @VisibleForTesting IBiometricsFingerprint getIBiometricsFingerprint() {
+ if (getProvider().getTestHalEnabled()) {
+ final TestHal testHal = new TestHal(getContext(), getSensorProperties().sensorId);
+ testHal.setNotify(new HidlToAidlCallbackConverter(getAidlResponseHandler()));
+ return testHal;
+ }
+
+ if (mDaemon != null) {
+ return mDaemon;
+ }
+
+ try {
+ mDaemon = IBiometricsFingerprint.getService();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get fingerprint HAL", e);
+ } catch (java.util.NoSuchElementException e) {
+ // Service doesn't exist or cannot be opened.
+ Slog.w(TAG, "NoSuchElementException", e);
+ }
+
+ if (mDaemon == null) {
+ Slog.w(TAG, "Fingerprint HAL not available");
+ return null;
+ }
+
+ mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
+
+ Slog.d(TAG, "Fingerprint HAL ready");
+
+ scheduleLoadAuthenticatorIds();
+ mInternalCleanupRunnable.run();
+ return mDaemon;
+ }
+
+ private UserAwareBiometricScheduler.UserSwitchCallback getUserSwitchCallback() {
+ return new UserAwareBiometricScheduler.UserSwitchCallback() {
+ @NonNull
+ @Override
+ public StopUserClient<?> getStopUserClient(int userId) {
+ return new StopUserClient<IBiometricsFingerprint>(getContext(),
+ HidlToAidlSensorAdapter.this::getIBiometricsFingerprint,
+ null /* token */, userId, getSensorProperties().sensorId,
+ BiometricLogger.ofUnknown(getContext()), getBiometricContext(),
+ () -> {
+ mCurrentUserId = UserHandle.USER_NULL;
+ mSession = null;
+ }) {
+ @Override
+ public void start(@NonNull ClientMonitorCallback callback) {
+ super.start(callback);
+ startHalOperation();
+ }
+
+ @Override
+ protected void startHalOperation() {
+ onUserStopped();
+ }
+
+ @Override
+ public void unableToStart() {
+ getCallback().onClientFinished(this, false /* success */);
+ }
+ };
+ }
+
+ @NonNull
+ @Override
+ public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+ return getFingerprintUpdateActiveUserClient(newUserId,
+ false /* forceUpdateAuthenticatorId */);
+ }
+ };
+ }
+
+ private FingerprintUpdateActiveUserClient getFingerprintUpdateActiveUserClient(int newUserId,
+ boolean forceUpdateAuthenticatorIds) {
+ return new FingerprintUpdateActiveUserClient(getContext(),
+ this::getIBiometricsFingerprint, newUserId, TAG,
+ getSensorProperties().sensorId, BiometricLogger.ofUnknown(getContext()),
+ getBiometricContext(), () -> mCurrentUserId,
+ !FingerprintUtils.getInstance(getSensorProperties().sensorId)
+ .getBiometricsForUser(getContext(),
+ newUserId).isEmpty(), getAuthenticatorIds(), forceUpdateAuthenticatorIds,
+ mUserStartedCallback);
+ }
+
+ private void scheduleLoadAuthenticatorIds() {
+ getHandler().post(() -> {
+ for (UserInfo user : UserManager.get(getContext()).getAliveUsers()) {
+ final int targetUserId = user.id;
+ if (!getAuthenticatorIds().containsKey(targetUserId)) {
+ getScheduler().scheduleClientMonitor(getFingerprintUpdateActiveUserClient(
+ targetUserId, true /* forceUpdateAuthenticatorIds */));
+ }
+ }
+ });
+ }
+
+ @VisibleForTesting void handleUserChanged(int newUserId) {
+ Slog.d(TAG, "User changed. Current user is " + newUserId);
+ mSession = null;
+ mCurrentUserId = newUserId;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java
similarity index 73%
rename from services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java
rename to services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java
index b48d232..2fc00e1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java
@@ -25,20 +25,25 @@
import android.hardware.keymaster.HardwareAuthToken;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.Log;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGetAuthenticatorIdClient;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInvalidationClient;
import java.util.function.Supplier;
/**
- * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation.
+ * Adapter to convert HIDL methods into AIDL interface {@link ISession}.
*/
-public class AidlToHidlAdapter implements ISession {
- private final String TAG = "AidlToHidlAdapter";
+public class HidlToAidlSessionAdapter implements ISession {
+
+ private final String TAG = "HidlToAidlSessionAdapter";
+
@VisibleForTesting
static final int ENROLL_TIMEOUT_SEC = 60;
@NonNull
@@ -46,22 +51,13 @@
private final int mUserId;
private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter;
- public AidlToHidlAdapter(Supplier<IBiometricsFingerprint> session, int userId,
+ public HidlToAidlSessionAdapter(Supplier<IBiometricsFingerprint> session, int userId,
AidlResponseHandler aidlResponseHandler) {
mSession = session;
mUserId = userId;
setCallback(aidlResponseHandler);
}
- private void setCallback(AidlResponseHandler aidlResponseHandler) {
- mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
- try {
- mSession.get().setNotify(mHidlToAidlCallbackConverter);
- } catch (RemoteException e) {
- Slog.d(TAG, "Failed to set callback");
- }
- }
-
@Override
public IBinder asBinder() {
return null;
@@ -125,12 +121,16 @@
@Override
public void getAuthenticatorId() throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "getAuthenticatorId unsupported in HIDL");
+ mHidlToAidlCallbackConverter.unsupportedClientScheduled(
+ FingerprintGetAuthenticatorIdClient.class);
}
@Override
public void invalidateAuthenticatorId() throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "invalidateAuthenticatorId unsupported in HIDL");
+ mHidlToAidlCallbackConverter.unsupportedClientScheduled(
+ FingerprintInvalidationClient.class);
}
@Override
@@ -140,72 +140,92 @@
@Override
public void close() throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "close unsupported in HIDL");
}
@Override
public void onUiReady() throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "onUiReady unsupported in HIDL");
}
@Override
public ICancellationSignal authenticateWithContext(long operationId, OperationContext context)
throws RemoteException {
- //Unsupported in HIDL
- return null;
+ Log.e(TAG, "authenticateWithContext unsupported in HIDL");
+ return authenticate(operationId);
}
@Override
public ICancellationSignal enrollWithContext(HardwareAuthToken hat, OperationContext context)
throws RemoteException {
- //Unsupported in HIDL
- return null;
+ Log.e(TAG, "enrollWithContext unsupported in HIDL");
+ return enroll(hat);
}
@Override
public ICancellationSignal detectInteractionWithContext(OperationContext context)
throws RemoteException {
- //Unsupported in HIDL
- return null;
+ Log.e(TAG, "enrollWithContext unsupported in HIDL");
+ return detectInteraction();
}
@Override
public void onPointerDownWithContext(PointerContext context) throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "onPointerDownWithContext unsupported in HIDL");
+ onPointerDown(context.pointerId, (int) context.x, (int) context.y, context.minor,
+ context.major);
}
@Override
public void onPointerUpWithContext(PointerContext context) throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "onPointerUpWithContext unsupported in HIDL");
+ onPointerUp(context.pointerId);
}
@Override
public void onContextChanged(OperationContext context) throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "onContextChanged unsupported in HIDL");
}
@Override
public void onPointerCancelWithContext(PointerContext context) throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "onPointerCancelWithContext unsupported in HIDL");
}
@Override
public void setIgnoreDisplayTouches(boolean shouldIgnore) throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "setIgnoreDisplayTouches unsupported in HIDL");
}
@Override
public int getInterfaceVersion() throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "getInterfaceVersion unsupported in HIDL");
return 0;
}
@Override
public String getInterfaceHash() throws RemoteException {
- //Unsupported in HIDL
+ Log.e(TAG, "getInterfaceHash unsupported in HIDL");
return null;
}
+ private void setCallback(AidlResponseHandler aidlResponseHandler) {
+ mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler);
+ try {
+ if (mSession.get() != null) {
+ long halId = mSession.get().setNotify(mHidlToAidlCallbackConverter);
+ Slog.d(TAG, "Fingerprint HAL ready, HAL ID: " + halId);
+ if (halId == 0) {
+ Slog.d(TAG, "Unable to set HIDL callback.");
+ }
+ } else {
+ Slog.e(TAG, "Unable to set HIDL callback. HIDL daemon is null.");
+ }
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to set callback");
+ }
+ }
+
private class Cancellation extends ICancellationSignal.Stub {
Cancellation() {}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
index 0730c67..2f77275 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
+import android.annotation.NonNull;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -31,15 +32,18 @@
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.sensors.LockoutTracker;
+import java.util.function.Function;
+
/**
* Tracks and enforces biometric lockout for biometric sensors that do not support lockout in the
* HAL.
*/
public class LockoutFrameworkImpl implements LockoutTracker {
- private static final String TAG = "LockoutTracker";
+ private static final String TAG = "LockoutFrameworkImpl";
private static final String ACTION_LOCKOUT_RESET =
"com.android.server.biometrics.sensors.fingerprint.ACTION_LOCKOUT_RESET";
private static final int MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED = 5;
@@ -65,22 +69,32 @@
void onLockoutReset(int userId);
}
- private final Context mContext;
private final LockoutResetCallback mLockoutResetCallback;
private final SparseBooleanArray mTimedLockoutCleared;
private final SparseIntArray mFailedAttempts;
private final AlarmManager mAlarmManager;
private final LockoutReceiver mLockoutReceiver;
private final Handler mHandler;
+ private final Function<Integer, PendingIntent> mLockoutResetIntent;
- public LockoutFrameworkImpl(Context context, LockoutResetCallback lockoutResetCallback) {
- mContext = context;
+ public LockoutFrameworkImpl(@NonNull Context context,
+ @NonNull LockoutResetCallback lockoutResetCallback) {
+ this(context, lockoutResetCallback, (userId) -> PendingIntent.getBroadcast(context, userId,
+ new Intent(ACTION_LOCKOUT_RESET).putExtra(KEY_LOCKOUT_RESET_USER, userId),
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE));
+ }
+
+ @VisibleForTesting
+ LockoutFrameworkImpl(@NonNull Context context,
+ @NonNull LockoutResetCallback lockoutResetCallback,
+ @NonNull Function<Integer, PendingIntent> lockoutResetIntent) {
mLockoutResetCallback = lockoutResetCallback;
mTimedLockoutCleared = new SparseBooleanArray();
mFailedAttempts = new SparseIntArray();
mAlarmManager = context.getSystemService(AlarmManager.class);
mLockoutReceiver = new LockoutReceiver();
mHandler = new Handler(Looper.getMainLooper());
+ mLockoutResetIntent = lockoutResetIntent;
context.registerReceiver(mLockoutReceiver, new IntentFilter(ACTION_LOCKOUT_RESET),
RESET_FINGERPRINT_LOCKOUT, null /* handler */, Context.RECEIVER_EXPORTED);
@@ -129,34 +143,18 @@
return LOCKOUT_NONE;
}
- /**
- * Clears lockout for Fingerprint HIDL HAL
- */
@Override
- public void setLockoutModeForUser(int userId, int mode) {
- mFailedAttempts.put(userId, 0);
- mTimedLockoutCleared.put(userId, true);
- // If we're asked to reset failed attempts externally (i.e. from Keyguard),
- // the alarm might still be pending; remove it.
- cancelLockoutResetForUser(userId);
- mLockoutResetCallback.onLockoutReset(userId);
- }
+ public void setLockoutModeForUser(int userId, int mode) {}
private void cancelLockoutResetForUser(int userId) {
- mAlarmManager.cancel(getLockoutResetIntentForUser(userId));
+ mAlarmManager.cancel(mLockoutResetIntent.apply(userId));
}
private void scheduleLockoutResetForUser(int userId) {
mHandler.post(() -> {
mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS,
- getLockoutResetIntentForUser(userId));
+ SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS,
+ mLockoutResetIntent.apply(userId));
});
}
-
- private PendingIntent getLockoutResetIntentForUser(int userId) {
- return PendingIntent.getBroadcast(mContext, userId,
- new Intent(ACTION_LOCKOUT_RESET).putExtra(KEY_LOCKOUT_RESET_USER, userId),
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
- }
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index c517058..b7ece2ea 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1299,7 +1299,7 @@
}
try {
- mNms.denyProtect(mOwnerUID);
+ mNetd.networkSetProtectDeny(mOwnerUID);
} catch (Exception e) {
Log.wtf(TAG, "Failed to disallow UID " + mOwnerUID + " to call protect() " + e);
}
@@ -1309,7 +1309,7 @@
mOwnerUID = getAppUid(mContext, newPackage, mUserId);
mIsPackageTargetingAtLeastQ = doesPackageTargetAtLeastQ(newPackage);
try {
- mNms.allowProtect(mOwnerUID);
+ mNetd.networkSetProtectAllow(mOwnerUID);
} catch (Exception e) {
Log.wtf(TAG, "Failed to allow UID " + mOwnerUID + " to call protect() " + e);
}
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index 1bd556b..4e341a9 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -56,6 +56,15 @@
}
}
+ @Override
+ public boolean blockScreenOn(Runnable unblocker) {
+ if (mDisplayOffloader == null) {
+ return false;
+ }
+ mDisplayOffloader.onBlockingScreenOn(unblocker);
+ return true;
+ }
+
/**
* Start the offload session. The method returns if the session is already active.
* @return Whether the session was started successfully
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 52c53f3..6d09cc9 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -126,6 +126,8 @@
// To enable these logs, run:
// 'adb shell setprop persist.log.tag.DisplayPowerController2 DEBUG && adb reboot'
private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
+ private static final String SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME =
+ "Screen on blocked by displayoffload";
// If true, uses the color fade on animation.
// We might want to turn this off if we cannot get a guarantee that the screen
@@ -155,6 +157,7 @@
private static final int MSG_SET_DWBC_COLOR_OVERRIDE = 15;
private static final int MSG_SET_DWBC_LOGGING_ENABLED = 16;
private static final int MSG_SET_BRIGHTNESS_FROM_OFFLOAD = 17;
+ private static final int MSG_OFFLOADING_SCREEN_ON_UNBLOCKED = 18;
@@ -339,6 +342,7 @@
// we are waiting for a callback to release it and unblock the screen.
private ScreenOnUnblocker mPendingScreenOnUnblocker;
private ScreenOffUnblocker mPendingScreenOffUnblocker;
+ private Runnable mPendingScreenOnUnblockerByDisplayOffload;
// True if we were in the process of turning off the screen.
// This allows us to recover more gracefully from situations where we abort
@@ -348,10 +352,15 @@
// The elapsed real time when the screen on was blocked.
private long mScreenOnBlockStartRealTime;
private long mScreenOffBlockStartRealTime;
+ private long mScreenOnBlockByDisplayOffloadStartRealTime;
// Screen state we reported to policy. Must be one of REPORTED_TO_POLICY_* fields.
private int mReportedScreenStateToPolicy = REPORTED_TO_POLICY_UNREPORTED;
+ // Used to deduplicate the displayoffload blocking screen on logic. One block per turning on.
+ // This value is reset when screen on is reported or the blocking is cancelled.
+ private boolean mScreenTurningOnWasBlockedByDisplayOffload;
+
// If the last recorded screen state was dozing or not.
private boolean mDozing;
@@ -472,7 +481,7 @@
private boolean mBootCompleted;
private final DisplayManagerFlags mFlags;
- private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession;
+ private DisplayOffloadSession mDisplayOffloadSession;
/**
* Creates the display power controller.
@@ -772,6 +781,10 @@
@Override
public void setDisplayOffloadSession(DisplayOffloadSession session) {
+ if (session == mDisplayOffloadSession) {
+ return;
+ }
+ unblockScreenOnByDisplayOffload();
mDisplayOffloadSession = session;
}
@@ -1735,6 +1748,7 @@
// reporting the display is ready because we only need to ensure the screen is in the
// right power state even as it continues to converge on the desired brightness.
final boolean ready = mPendingScreenOnUnblocker == null
+ && mPendingScreenOnUnblockerByDisplayOffload == null
&& (!mColorFadeEnabled || (!mColorFadeOnAnimator.isStarted()
&& !mColorFadeOffAnimator.isStarted()))
&& mPowerState.waitUntilClean(mCleanListener);
@@ -1983,15 +1997,69 @@
}
}
+ private void blockScreenOnByDisplayOffload(DisplayOffloadSession displayOffloadSession) {
+ if (mPendingScreenOnUnblockerByDisplayOffload != null || displayOffloadSession == null) {
+ return;
+ }
+ mScreenTurningOnWasBlockedByDisplayOffload = true;
+
+ Trace.asyncTraceBegin(
+ Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
+ mScreenOnBlockByDisplayOffloadStartRealTime = SystemClock.elapsedRealtime();
+
+ mPendingScreenOnUnblockerByDisplayOffload =
+ () -> onDisplayOffloadUnblockScreenOn(displayOffloadSession);
+ if (!displayOffloadSession.blockScreenOn(mPendingScreenOnUnblockerByDisplayOffload)) {
+ mPendingScreenOnUnblockerByDisplayOffload = null;
+ long delay =
+ SystemClock.elapsedRealtime() - mScreenOnBlockByDisplayOffloadStartRealTime;
+ Slog.w(mTag, "Tried blocking screen on for offloading but failed. So, end trace after "
+ + delay + " ms.");
+ Trace.asyncTraceEnd(
+ Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
+ return;
+ }
+ Slog.i(mTag, "Blocking screen on for offloading.");
+ }
+
+ private void onDisplayOffloadUnblockScreenOn(DisplayOffloadSession displayOffloadSession) {
+ Message msg = mHandler.obtainMessage(MSG_OFFLOADING_SCREEN_ON_UNBLOCKED,
+ displayOffloadSession);
+ mHandler.sendMessage(msg);
+ }
+
+ private void unblockScreenOnByDisplayOffload() {
+ if (mPendingScreenOnUnblockerByDisplayOffload == null) {
+ return;
+ }
+ mPendingScreenOnUnblockerByDisplayOffload = null;
+ long delay = SystemClock.elapsedRealtime() - mScreenOnBlockByDisplayOffloadStartRealTime;
+ Slog.i(mTag, "Unblocked screen on for offloading after " + delay + " ms");
+ Trace.asyncTraceEnd(
+ Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_BY_DISPLAYOFFLOAD_TRACE_NAME, 0);
+ }
+
private boolean setScreenState(int state) {
return setScreenState(state, false /*reportOnly*/);
}
private boolean setScreenState(int state, boolean reportOnly) {
final boolean isOff = (state == Display.STATE_OFF);
+ final boolean isOn = (state == Display.STATE_ON);
+ final boolean changed = mPowerState.getScreenState() != state;
- if (mPowerState.getScreenState() != state
- || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
+ // If the screen is turning on, give displayoffload a chance to do something before the
+ // screen actually turns on.
+ // TODO(b/316941732): add tests for this displayoffload screen-on blocker.
+ if (isOn && changed && !mScreenTurningOnWasBlockedByDisplayOffload) {
+ blockScreenOnByDisplayOffload(mDisplayOffloadSession);
+ } else if (!isOn && mScreenTurningOnWasBlockedByDisplayOffload) {
+ // No longer turning screen on, so unblock previous screen on blocking immediately.
+ unblockScreenOnByDisplayOffload();
+ mScreenTurningOnWasBlockedByDisplayOffload = false;
+ }
+
+ if (changed || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
// If we are trying to turn screen off, give policy a chance to do something before we
// actually turn the screen off.
if (isOff && !mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity()) {
@@ -2007,8 +2075,9 @@
}
}
- if (!reportOnly && mPowerState.getScreenState() != state
- && readyToUpdateDisplayState()) {
+ if (!reportOnly && changed && readyToUpdateDisplayState()
+ && mPendingScreenOffUnblocker == null
+ && mPendingScreenOnUnblockerByDisplayOffload == null) {
Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state);
String propertyKey = "debug.tracing.screen_state";
@@ -2060,12 +2129,16 @@
}
// Return true if the screen isn't blocked.
- return mPendingScreenOnUnblocker == null;
+ return mPendingScreenOnUnblocker == null
+ && mPendingScreenOnUnblockerByDisplayOffload == null;
}
private void setReportedScreenState(int state) {
Trace.traceCounter(Trace.TRACE_TAG_POWER, "ReportedScreenStateToPolicy", state);
mReportedScreenStateToPolicy = state;
+ if (state == REPORTED_TO_POLICY_SCREEN_ON) {
+ mScreenTurningOnWasBlockedByDisplayOffload = false;
+ }
}
private void loadAmbientLightSensor() {
@@ -2813,6 +2886,12 @@
updatePowerState();
}
break;
+ case MSG_OFFLOADING_SCREEN_ON_UNBLOCKED:
+ if (mDisplayOffloadSession == msg.obj) {
+ unblockScreenOnByDisplayOffload();
+ updatePowerState();
+ }
+ break;
case MSG_CONFIGURE_BRIGHTNESS:
BrightnessConfiguration brightnessConfiguration =
(BrightnessConfiguration) msg.obj;
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 22898a6..25576ce 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -25,6 +25,7 @@
import android.content.res.Resources;
import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
import android.hardware.sidekick.SidekickInternal;
+import android.media.MediaDrm;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
@@ -242,6 +243,10 @@
private SurfaceControl.DisplayMode mActiveSfDisplayMode;
// The active display vsync period in SurfaceFlinger
private float mActiveRenderFrameRate;
+ // The current HDCP level supported by the display, 0 indicates unset
+ // values are defined in hardware/interfaces/drm/aidl/android/hardware/drm/HdcpLevel.aidl
+ private int mConnectedHdcpLevel;
+
private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides =
new DisplayEventReceiver.FrameRateOverride[0];
@@ -675,8 +680,9 @@
mInfo.yDpi = mActiveSfDisplayMode.yDpi;
mInfo.deviceProductInfo = mStaticDisplayInfo.deviceProductInfo;
- // Assume that all built-in displays that have secure output (eg. HDCP) also
- // support compositing from gralloc protected buffers.
+ if (mConnectedHdcpLevel != 0) {
+ mStaticDisplayInfo.secure = mConnectedHdcpLevel >= MediaDrm.HDCP_V1;
+ }
if (mStaticDisplayInfo.secure) {
mInfo.flags = DisplayDeviceInfo.FLAG_SECURE
| DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS;
@@ -1093,6 +1099,12 @@
}
}
+ public void onHdcpLevelsChangedLocked(int connectedLevel, int maxLevel) {
+ if (updateHdcpLevelsLocked(connectedLevel, maxLevel)) {
+ updateDeviceInfoLocked();
+ }
+ }
+
public boolean updateActiveModeLocked(int activeSfModeId, float renderFrameRate) {
if (mActiveSfDisplayMode.id == activeSfModeId
&& mActiveRenderFrameRate == renderFrameRate) {
@@ -1118,6 +1130,22 @@
return true;
}
+ public boolean updateHdcpLevelsLocked(int connectedLevel, int maxLevel) {
+ if (connectedLevel > maxLevel) {
+ Slog.w(TAG, "HDCP connected level: " + connectedLevel
+ + " is larger than max level: " + maxLevel
+ + ", ignoring request.");
+ return false;
+ }
+
+ if (mConnectedHdcpLevel == connectedLevel) {
+ return false;
+ }
+
+ mConnectedHdcpLevel = connectedLevel;
+ return true;
+ }
+
public void requestColorModeLocked(int colorMode) {
if (mActiveColorMode == colorMode) {
return;
@@ -1387,6 +1415,7 @@
long renderPeriod);
void onFrameRateOverridesChanged(long timestampNanos, long physicalDisplayId,
DisplayEventReceiver.FrameRateOverride[] overrides);
+ void onHdcpLevelsChanged(long physicalDisplayId, int connectedLevel, int maxLevel);
}
@@ -1420,6 +1449,11 @@
DisplayEventReceiver.FrameRateOverride[] overrides) {
mListener.onFrameRateOverridesChanged(timestampNanos, physicalDisplayId, overrides);
}
+
+ @Override
+ public void onHdcpLevelsChanged(long physicalDisplayId, int connectedLevel, int maxLevel) {
+ mListener.onHdcpLevelsChanged(physicalDisplayId, connectedLevel, maxLevel);
+ }
}
private final class LocalDisplayEventListener implements DisplayEventListener {
@@ -1489,6 +1523,26 @@
device.onFrameRateOverridesChanged(overrides);
}
}
+
+ @Override
+ public void onHdcpLevelsChanged(long physicalDisplayId, int connectedLevel, int maxLevel) {
+ if (DEBUG) {
+ Slog.d(TAG, "onHdcpLevelsChanged(physicalDisplayId=" + physicalDisplayId
+ + ", connectedLevel=" + connectedLevel + ", maxLevel=" + maxLevel + ")");
+ }
+ synchronized (getSyncRoot()) {
+ LocalDisplayDevice device = mDevices.get(physicalDisplayId);
+ if (device == null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Received hdcp levels change for unhandled physical display: "
+ + "physicalDisplayId=" + physicalDisplayId);
+ }
+ return;
+ }
+
+ device.onHdcpLevelsChangedLocked(connectedLevel, maxLevel);
+ }
+ }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 4089a81..dda50ca 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -167,13 +167,17 @@
/**
* Indicates that the IME window has re-parented to the new target when the IME control changed.
+ *
+ * @param displayId the display hosting the IME window
*/
- public abstract void onImeParentChanged();
+ public abstract void onImeParentChanged(int displayId);
/**
- * Destroys the IME surface.
+ * Destroys the IME surface for the given display.
+ *
+ * @param displayId the display hosting the IME window
*/
- public abstract void removeImeSurface();
+ public abstract void removeImeSurface(int displayId);
/**
* Updates the IME visibility, back disposition and show IME picker status for SystemUI.
@@ -298,11 +302,11 @@
}
@Override
- public void onImeParentChanged() {
+ public void onImeParentChanged(int displayId) {
}
@Override
- public void removeImeSurface() {
+ public void removeImeSurface(int displayId) {
}
@Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 16e043c..0d29b7d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5671,7 +5671,7 @@
}
@Override
- public void onImeParentChanged() {
+ public void onImeParentChanged(int displayId) {
synchronized (ImfLock.class) {
// Hide the IME method menu only when the IME surface parent is changed by the
// input target changed, in case seeing the dialog dismiss flickering during
@@ -5683,7 +5683,7 @@
}
@Override
- public void removeImeSurface() {
+ public void removeImeSurface(int displayId) {
mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
}
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 2da1a68..66e61c0 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -234,7 +234,7 @@
if (pkgList != null && (pkgList.length > 0)) {
for (String pkgName : pkgList) {
try {
- inm.removeAutomaticZenRules(pkgName);
+ inm.removeAutomaticZenRules(pkgName, /* fromUser= */ false);
inm.setNotificationPolicyAccessGranted(pkgName, false);
} catch (Exception e) {
Slog.e(TAG, "Failed to clean up rules for " + pkgName, e);
diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
index 8855666..71a6b5e 100644
--- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
+++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
@@ -109,7 +109,6 @@
if (origin == ZenModeConfig.UPDATE_ORIGIN_INIT
|| origin == ZenModeConfig.UPDATE_ORIGIN_INIT_USER
|| origin == ZenModeConfig.UPDATE_ORIGIN_USER
- || origin == ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
|| !mPowerManager.isInteractive()) {
unregisterScreenOffReceiver();
updateNightModeImmediately(useNightMode);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 75d3dce..a919db9 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5343,13 +5343,14 @@
}
@Override
- public void setZenMode(int mode, Uri conditionId, String reason) throws RemoteException {
+ public void setZenMode(int mode, Uri conditionId, String reason, boolean fromUser) {
enforceSystemOrSystemUI("INotificationManager.setZenMode");
final int callingUid = Binder.getCallingUid();
final long identity = Binder.clearCallingIdentity();
+ enforceUserOriginOnlyFromSystem(fromUser, "setZenMode");
+
try {
- mZenModeHelper.setManualZenMode(mode, conditionId,
- ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, // Checked by enforce()
+ mZenModeHelper.setManualZenMode(mode, conditionId, computeZenOrigin(fromUser),
reason, /* caller= */ null, callingUid);
} finally {
Binder.restoreCallingIdentity(identity);
@@ -5380,7 +5381,8 @@
}
@Override
- public String addAutomaticZenRule(AutomaticZenRule automaticZenRule, String pkg) {
+ public String addAutomaticZenRule(AutomaticZenRule automaticZenRule, String pkg,
+ boolean fromUser) {
validateAutomaticZenRule(automaticZenRule);
checkCallerIsSameApp(pkg);
if (automaticZenRule.getZenPolicy() != null
@@ -5389,6 +5391,7 @@
+ "INTERRUPTION_FILTER_PRIORITY filters");
}
enforcePolicyAccess(Binder.getCallingUid(), "addAutomaticZenRule");
+ enforceUserOriginOnlyFromSystem(fromUser, "addAutomaticZenRule");
// If the calling app is the system (from any user), take the package name from the
// rule's owner rather than from the caller's package.
@@ -5400,24 +5403,18 @@
}
return mZenModeHelper.addAutomaticZenRule(rulePkg, automaticZenRule,
- // TODO: b/308670715: Distinguish origin properly (e.g. USER if creating a rule
- // manually in Settings).
- isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
- "addAutomaticZenRule", Binder.getCallingUid());
+ computeZenOrigin(fromUser), "addAutomaticZenRule", Binder.getCallingUid());
}
@Override
- public boolean updateAutomaticZenRule(String id, AutomaticZenRule automaticZenRule) {
+ public boolean updateAutomaticZenRule(String id, AutomaticZenRule automaticZenRule,
+ boolean fromUser) throws RemoteException {
validateAutomaticZenRule(automaticZenRule);
enforcePolicyAccess(Binder.getCallingUid(), "updateAutomaticZenRule");
+ enforceUserOriginOnlyFromSystem(fromUser, "updateAutomaticZenRule");
- // TODO: b/308670715: Distinguish origin properly (e.g. USER if updating a rule
- // manually in Settings).
return mZenModeHelper.updateAutomaticZenRule(id, automaticZenRule,
- isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
- "updateAutomaticZenRule", Binder.getCallingUid());
+ computeZenOrigin(fromUser), "updateAutomaticZenRule", Binder.getCallingUid());
}
private void validateAutomaticZenRule(AutomaticZenRule rule) {
@@ -5445,27 +5442,24 @@
}
@Override
- public boolean removeAutomaticZenRule(String id) throws RemoteException {
+ public boolean removeAutomaticZenRule(String id, boolean fromUser) throws RemoteException {
Objects.requireNonNull(id, "Id is null");
// Verify that they can modify zen rules.
enforcePolicyAccess(Binder.getCallingUid(), "removeAutomaticZenRule");
+ enforceUserOriginOnlyFromSystem(fromUser, "removeAutomaticZenRule");
- // TODO: b/308670715: Distinguish origin properly (e.g. USER if removing a rule
- // manually in Settings).
- return mZenModeHelper.removeAutomaticZenRule(id,
- isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
+ return mZenModeHelper.removeAutomaticZenRule(id, computeZenOrigin(fromUser),
"removeAutomaticZenRule", Binder.getCallingUid());
}
@Override
- public boolean removeAutomaticZenRules(String packageName) throws RemoteException {
+ public boolean removeAutomaticZenRules(String packageName, boolean fromUser)
+ throws RemoteException {
Objects.requireNonNull(packageName, "Package name is null");
enforceSystemOrSystemUI("removeAutomaticZenRules");
+ enforceUserOriginOnlyFromSystem(fromUser, "removeAutomaticZenRules");
- return mZenModeHelper.removeAutomaticZenRules(packageName,
- isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
+ return mZenModeHelper.removeAutomaticZenRules(packageName, computeZenOrigin(fromUser),
packageName + "|removeAutomaticZenRules", Binder.getCallingUid());
}
@@ -5478,28 +5472,54 @@
}
@Override
- public void setAutomaticZenRuleState(String id, Condition condition) {
+ public void setAutomaticZenRuleState(String id, Condition condition, boolean fromUser) {
Objects.requireNonNull(id, "id is null");
Objects.requireNonNull(condition, "Condition is null");
condition.validate();
enforcePolicyAccess(Binder.getCallingUid(), "setAutomaticZenRuleState");
- // TODO: b/308670715: Distinguish origin properly (e.g. USER if toggling a rule
- // manually in Settings).
- mZenModeHelper.setAutomaticZenRuleState(id, condition,
- isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
+ if (android.app.Flags.modesApi()) {
+ if (fromUser != (condition.source == Condition.SOURCE_USER_ACTION)) {
+ throw new IllegalArgumentException(String.format(
+ "Mismatch between fromUser (%s) and condition.source (%s)",
+ fromUser, Condition.sourceToString(condition.source)));
+ }
+ }
+
+ mZenModeHelper.setAutomaticZenRuleState(id, condition, computeZenOrigin(fromUser),
Binder.getCallingUid());
}
+ @ZenModeConfig.ConfigChangeOrigin
+ private int computeZenOrigin(boolean fromUser) {
+ // "fromUser" is introduced with MODES_API, so only consider it in that case.
+ // (Non-MODES_API behavior should also not depend at all on UPDATE_ORIGIN_USER).
+ if (android.app.Flags.modesApi() && fromUser) {
+ return ZenModeConfig.UPDATE_ORIGIN_USER;
+ } else if (isCallerSystemOrSystemUi()) {
+ return ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI;
+ } else {
+ return ZenModeConfig.UPDATE_ORIGIN_APP;
+ }
+ }
+
+ private void enforceUserOriginOnlyFromSystem(boolean fromUser, String method) {
+ if (android.app.Flags.modesApi()
+ && fromUser
+ && !isCallerSystemOrSystemUiOrShell()) {
+ throw new SecurityException(String.format(
+ "Calling %s with fromUser == true is only allowed for system", method));
+ }
+ }
+
@Override
- public void setInterruptionFilter(String pkg, int filter) throws RemoteException {
+ public void setInterruptionFilter(String pkg, int filter, boolean fromUser) {
enforcePolicyAccess(pkg, "setInterruptionFilter");
final int zen = NotificationManager.zenModeFromInterruptionFilter(filter, -1);
if (zen == -1) throw new IllegalArgumentException("Invalid filter: " + filter);
final int callingUid = Binder.getCallingUid();
- final boolean isSystemOrSystemUi = isCallerSystemOrSystemUi();
+ enforceUserOriginOnlyFromSystem(fromUser, "setInterruptionFilter");
if (android.app.Flags.modesApi() && !canManageGlobalZenPolicy(pkg, callingUid)) {
mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, callingUid, zen);
@@ -5508,9 +5528,7 @@
final long identity = Binder.clearCallingIdentity();
try {
- mZenModeHelper.setManualZenMode(zen, null,
- isSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
+ mZenModeHelper.setManualZenMode(zen, null, computeZenOrigin(fromUser),
/* reason= */ "setInterruptionFilter", /* caller= */ pkg,
callingUid);
} finally {
@@ -5825,10 +5843,11 @@
* {@link Policy#PRIORITY_CATEGORY_MEDIA} from bypassing dnd
*/
@Override
- public void setNotificationPolicy(String pkg, Policy policy) {
+ public void setNotificationPolicy(String pkg, Policy policy, boolean fromUser) {
enforcePolicyAccess(pkg, "setNotificationPolicy");
+ enforceUserOriginOnlyFromSystem(fromUser, "setNotificationPolicy");
int callingUid = Binder.getCallingUid();
- boolean isSystemOrSystemUi = isCallerSystemOrSystemUi();
+ @ZenModeConfig.ConfigChangeOrigin int origin = computeZenOrigin(fromUser);
boolean shouldApplyAsImplicitRule = android.app.Flags.modesApi()
&& !canManageGlobalZenPolicy(pkg, callingUid);
@@ -5873,14 +5892,12 @@
newVisualEffects, policy.priorityConversationSenders);
if (shouldApplyAsImplicitRule) {
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy,
+ origin);
} else {
ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion,
policy);
- mZenModeHelper.setNotificationPolicy(policy,
- isSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
- : ZenModeConfig.UPDATE_ORIGIN_APP,
- callingUid);
+ mZenModeHelper.setNotificationPolicy(policy, origin, callingUid);
}
} catch (RemoteException e) {
Slog.e(TAG, "Failed to set notification policy", e);
diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java
index dc0cf4e..9f3104c 100644
--- a/services/core/java/com/android/server/notification/NotificationShellCmd.java
+++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java
@@ -117,7 +117,6 @@
private final NotificationManagerService mDirectService;
private final INotificationManager mBinderService;
private final PackageManager mPm;
- private NotificationChannel mChannel;
public NotificationShellCmd(NotificationManagerService service) {
mDirectService = service;
@@ -183,7 +182,13 @@
interruptionFilter = INTERRUPTION_FILTER_ALL;
}
final int filter = interruptionFilter;
- mBinderService.setInterruptionFilter(callingPackage, filter);
+ if (android.app.Flags.modesApi()) {
+ mBinderService.setInterruptionFilter(callingPackage, filter,
+ /* fromUser= */ true);
+ } else {
+ mBinderService.setInterruptionFilter(callingPackage, filter,
+ /* fromUser= */ false);
+ }
}
break;
case "allow_dnd": {
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 3f8b595..d1de9b0 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -561,7 +561,7 @@
* {@link Global#ZEN_MODE_IMPORTANT_INTERRUPTIONS}.
*/
void applyGlobalPolicyAsImplicitZenRule(String callingPkg, int callingUid,
- NotificationManager.Policy policy) {
+ NotificationManager.Policy policy, @ConfigChangeOrigin int origin) {
if (!android.app.Flags.modesApi()) {
Log.wtf(TAG, "applyGlobalPolicyAsImplicitZenRule called with flag off!");
return;
@@ -579,7 +579,7 @@
}
// TODO: b/308673679 - Keep user customization of this rule!
rule.zenPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy);
- setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP,
+ setConfigLocked(newConfig, /* triggeringComponent= */ null, origin,
"applyGlobalPolicyAsImplicitZenRule", callingUid);
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c48eccf..2305d6c 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -7237,7 +7237,8 @@
@Override
public boolean isUserInitialized(@UserIdInt int userId) {
- return (getUserInfo(userId).flags & UserInfo.FLAG_INITIALIZED) != 0;
+ final UserInfo userInfo = getUserInfo(userId);
+ return userInfo != null && (userInfo.flags & UserInfo.FLAG_INITIALIZED) != 0;
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 630b9e1..b5e5d84 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2767,10 +2767,7 @@
}
}
- // If the target task is not in the front, then we need to bring it to the front...
- // except... well, with SINGLE_TASK_LAUNCH it's not entirely clear. We'd like to have
- // the same behavior as if a new instance was being started, which means not bringing it
- // to the front if the caller is not itself in the front.
+ // If the target task is not in the front, then we need to bring it to the front.
final boolean differentTopTask;
if (mTargetRootTask.getDisplayArea() == mPreferredTaskDisplayArea) {
final Task focusRootTask = mTargetRootTask.mDisplayContent.getFocusedRootTask();
@@ -2787,49 +2784,47 @@
if (differentTopTask && !avoidMoveToFront()) {
mStartActivity.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
- if (mSourceRecord == null || inTopNonFinishingTask(mSourceRecord)) {
- // We really do want to push this one into the user's face, right now.
- if (mLaunchTaskBehind && mSourceRecord != null) {
- intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
- }
-
- if (intentActivity.isDescendantOf(mTargetRootTask)) {
- // TODO(b/151572268): Figure out a better way to move tasks in above 2-levels
- // tasks hierarchies.
- if (mTargetRootTask != intentTask
- && mTargetRootTask != intentTask.getParent().asTask()) {
- intentTask.getParent().positionChildAt(POSITION_TOP, intentTask,
- false /* includingParents */);
- intentTask = intentTask.getParent().asTaskFragment().getTask();
- }
- // If the activity is visible in multi-windowing mode, it may already be on
- // the top (visible to user but not the global top), then the result code
- // should be START_DELIVERED_TO_TOP instead of START_TASK_TO_FRONT.
- final boolean wasTopOfVisibleRootTask = intentActivity.isVisibleRequested()
- && intentActivity.inMultiWindowMode()
- && intentActivity == mTargetRootTask.topRunningActivity()
- && !intentActivity.mTransitionController.isTransientHide(
- mTargetRootTask);
- // We only want to move to the front, if we aren't going to launch on a
- // different root task. If we launch on a different root task, we will put the
- // task on top there.
- // Defer resuming the top activity while moving task to top, since the
- // current task-top activity may not be the activity that should be resumed.
- mTargetRootTask.moveTaskToFront(intentTask, mNoAnimation, mOptions,
- mStartActivity.appTimeTracker, DEFER_RESUME,
- "bringingFoundTaskToFront");
- mMovedToFront = !wasTopOfVisibleRootTask;
- } else if (intentActivity.getWindowingMode() != WINDOWING_MODE_PINNED) {
- // Leaves reparenting pinned task operations to task organizer to make sure it
- // dismisses pinned task properly.
- // TODO(b/199997762): Consider leaving all reparent operation of organized tasks
- // to task organizer.
- intentTask.reparent(mTargetRootTask, ON_TOP, REPARENT_MOVE_ROOT_TASK_TO_FRONT,
- ANIMATE, DEFER_RESUME, "reparentToTargetRootTask");
- mMovedToFront = true;
- }
- mOptions = null;
+ // We really do want to push this one into the user's face, right now.
+ if (mLaunchTaskBehind && mSourceRecord != null) {
+ intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
}
+
+ if (intentActivity.isDescendantOf(mTargetRootTask)) {
+ // TODO(b/151572268): Figure out a better way to move tasks in above 2-levels
+ // tasks hierarchies.
+ if (mTargetRootTask != intentTask
+ && mTargetRootTask != intentTask.getParent().asTask()) {
+ intentTask.getParent().positionChildAt(POSITION_TOP, intentTask,
+ false /* includingParents */);
+ intentTask = intentTask.getParent().asTaskFragment().getTask();
+ }
+ // If the activity is visible in multi-windowing mode, it may already be on
+ // the top (visible to user but not the global top), then the result code
+ // should be START_DELIVERED_TO_TOP instead of START_TASK_TO_FRONT.
+ final boolean wasTopOfVisibleRootTask = intentActivity.isVisibleRequested()
+ && intentActivity.inMultiWindowMode()
+ && intentActivity == mTargetRootTask.topRunningActivity()
+ && !intentActivity.mTransitionController.isTransientHide(
+ mTargetRootTask);
+ // We only want to move to the front, if we aren't going to launch on a
+ // different root task. If we launch on a different root task, we will put the
+ // task on top there.
+ // Defer resuming the top activity while moving task to top, since the
+ // current task-top activity may not be the activity that should be resumed.
+ mTargetRootTask.moveTaskToFront(intentTask, mNoAnimation, mOptions,
+ mStartActivity.appTimeTracker, DEFER_RESUME,
+ "bringingFoundTaskToFront");
+ mMovedToFront = !wasTopOfVisibleRootTask;
+ } else if (intentActivity.getWindowingMode() != WINDOWING_MODE_PINNED) {
+ // Leaves reparenting pinned task operations to task organizer to make sure it
+ // dismisses pinned task properly.
+ // TODO(b/199997762): Consider leaving all reparent operation of organized tasks
+ // to task organizer.
+ intentTask.reparent(mTargetRootTask, ON_TOP, REPARENT_MOVE_ROOT_TASK_TO_FRONT,
+ ANIMATE, DEFER_RESUME, "reparentToTargetRootTask");
+ mMovedToFront = true;
+ }
+ mOptions = null;
}
if (differentTopTask) {
logPIOnlyCreatorAllowsBAL();
@@ -2850,20 +2845,6 @@
mRootWindowContainer.getDefaultTaskDisplayArea(), mTargetRootTask);
}
- private boolean inTopNonFinishingTask(ActivityRecord r) {
- if (r == null || r.getTask() == null) {
- return false;
- }
-
- final Task rTask = r.getTask();
- final Task parent = rTask.getCreatedByOrganizerTask() != null
- ? rTask.getCreatedByOrganizerTask() : r.getRootTask();
- final ActivityRecord topNonFinishingActivity = parent != null
- ? parent.getTopNonFinishingActivity() : null;
-
- return topNonFinishingActivity != null && topNonFinishingActivity.getTask() == rTask;
- }
-
private void resumeTargetRootTaskIfNeeded() {
if (mDoResume) {
final ActivityRecord next = mTargetRootTask.topRunningActivity(
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ae10ce3..c98280e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4794,7 +4794,8 @@
assignRelativeLayerForIme(getSyncTransaction(), true /* forceUpdate */);
scheduleAnimation();
- mWmService.mH.post(() -> InputMethodManagerInternal.get().onImeParentChanged());
+ mWmService.mH.post(
+ () -> InputMethodManagerInternal.get().onImeParentChanged(getDisplayId()));
} else if (mImeControlTarget != null && mImeControlTarget == mImeLayeringTarget) {
// Even if the IME surface parent is not changed, the layer target belonging to the
// parent may have changes. Then attempt to reassign if the IME control target is
@@ -7090,7 +7091,7 @@
}
@Override
- public void notifyInsetsControlChanged() {
+ public void notifyInsetsControlChanged(int displayId) {
final InsetsStateController stateController = getInsetsStateController();
try {
mRemoteInsetsController.insetsControlChanged(stateController.getRawInsetsState(),
diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
index 8ecbc17..b74eb56 100644
--- a/services/core/java/com/android/server/wm/InsetsControlTarget.java
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -29,8 +29,10 @@
/**
* Notifies the control target that the insets control has changed.
+ *
+ * @param displayId the display hosting the window of this target
*/
- default void notifyInsetsControlChanged() {
+ default void notifyInsetsControlChanged(int displayId) {
};
/**
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 7815679..3c556bf 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -728,7 +728,7 @@
}
@Override
- public void notifyInsetsControlChanged() {
+ public void notifyInsetsControlChanged(int displayId) {
mHandler.post(this);
}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index c4d0129..6b9fcf4 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -72,7 +72,7 @@
};
private final InsetsControlTarget mEmptyImeControlTarget = new InsetsControlTarget() {
@Override
- public void notifyInsetsControlChanged() {
+ public void notifyInsetsControlChanged(int displayId) {
InsetsSourceControl[] controls = getControlsForDispatch(this);
if (controls == null) {
return;
@@ -80,7 +80,7 @@
for (InsetsSourceControl control : controls) {
if (control.getType() == WindowInsets.Type.ime()) {
mDisplayContent.mWmService.mH.post(() ->
- InputMethodManagerInternal.get().removeImeSurface());
+ InputMethodManagerInternal.get().removeImeSurface(displayId));
}
}
}
@@ -370,9 +370,10 @@
provider.onSurfaceTransactionApplied();
}
final ArraySet<InsetsControlTarget> newControlTargets = new ArraySet<>();
+ int displayId = mDisplayContent.getDisplayId();
for (int i = mPendingControlChanged.size() - 1; i >= 0; i--) {
final InsetsControlTarget controlTarget = mPendingControlChanged.valueAt(i);
- controlTarget.notifyInsetsControlChanged();
+ controlTarget.notifyInsetsControlChanged(displayId);
if (mControlTargetProvidersMap.containsKey(controlTarget)) {
// We only collect targets who get controls, not lose controls.
newControlTargets.add(controlTarget);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 5518de7..9305396 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -20,6 +20,7 @@
import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION;
+import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
@@ -170,6 +171,8 @@
// Corresponds to OVERRIDE_ANY_ORIENTATION
private final boolean mIsOverrideAnyOrientationEnabled;
+ // Corresponds to OVERRIDE_ANY_ORIENTATION_TO_USER
+ private final boolean mIsOverrideToUserOrientationEnabled;
// Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT
private final boolean mIsOverrideToPortraitOrientationEnabled;
// Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR
@@ -254,9 +257,8 @@
// Counter for ActivityRecord#setRequestedOrientation
private int mSetOrientationRequestCounter = 0;
- // The min aspect ratio override set by user. Stores the last selected aspect ratio after
- // {@link #shouldApplyUserFullscreenOverride} or {@link #shouldApplyUserMinAspectRatioOverride}
- // have been invoked.
+ // TODO(b/315140179): Make mUserAspectRatio final
+ // The min aspect ratio override set by user
@PackageManager.UserMinAspectRatio
private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET;
@@ -355,6 +357,8 @@
PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE);
mIsOverrideAnyOrientationEnabled = isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION);
+ mIsOverrideToUserOrientationEnabled =
+ isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION_TO_USER);
mIsOverrideToPortraitOrientationEnabled =
isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT);
mIsOverrideToReverseLandscapeOrientationEnabled =
@@ -663,9 +667,11 @@
@ScreenOrientation
int overrideOrientationIfNeeded(@ScreenOrientation int candidate) {
+ final DisplayContent displayContent = mActivityRecord.mDisplayContent;
+ final boolean isIgnoreOrientationRequestEnabled = displayContent != null
+ && displayContent.getIgnoreOrientationRequest();
if (shouldApplyUserFullscreenOverride()
- && mActivityRecord.mDisplayContent != null
- && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) {
+ && isIgnoreOrientationRequestEnabled) {
Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for "
+ mActivityRecord + " is overridden to "
+ screenOrientationToString(SCREEN_ORIENTATION_USER)
@@ -690,7 +696,6 @@
return candidate;
}
- DisplayContent displayContent = mActivityRecord.mDisplayContent;
if (mIsOverrideOrientationOnlyForCameraEnabled && displayContent != null
&& (displayContent.mDisplayRotationCompatPolicy == null
|| !displayContent.mDisplayRotationCompatPolicy
@@ -698,6 +703,17 @@
return candidate;
}
+ // mUserAspectRatio is always initialized first in shouldApplyUserFullscreenOverride(),
+ // which will always come first before this check as user override > device
+ // manufacturer override.
+ if (mUserAspectRatio == PackageManager.USER_MIN_ASPECT_RATIO_UNSET
+ && mIsOverrideToUserOrientationEnabled && isIgnoreOrientationRequestEnabled) {
+ Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for "
+ + mActivityRecord + " is overridden to "
+ + screenOrientationToString(SCREEN_ORIENTATION_USER));
+ return SCREEN_ORIENTATION_USER;
+ }
+
if (mIsOverrideToReverseLandscapeOrientationEnabled
&& (isFixedOrientationLandscape(candidate) || mIsOverrideAnyOrientationEnabled)) {
Slog.w(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for "
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 57939bc..7995028 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -89,6 +89,7 @@
import com.android.window.flags.Flags;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer;
@@ -106,7 +107,7 @@
final WindowProcessController mProcess;
private final String mStringName;
SurfaceSession mSurfaceSession;
- private int mNumWindow = 0;
+ private final ArrayList<WindowState> mAddedWindows = new ArrayList<>();
// Set of visible application overlay window surfaces connected to this session.
private final ArraySet<WindowSurfaceController> mAppOverlaySurfaces = new ArraySet<>();
// Set of visible alert window surfaces connected to this session.
@@ -192,8 +193,7 @@
try {
mCallback.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
- // The caller has died, so we can just forget about this.
- // Hmmm, should we call killSessionLocked()??
+ mClientDead = true;
}
}
@@ -211,12 +211,27 @@
}
}
+ boolean isClientDead() {
+ return mClientDead;
+ }
+
@Override
public void binderDied() {
synchronized (mService.mGlobalLock) {
mCallback.asBinder().unlinkToDeath(this, 0);
mClientDead = true;
- killSessionLocked();
+ try {
+ for (int i = mAddedWindows.size() - 1; i >= 0; i--) {
+ final WindowState w = mAddedWindows.get(i);
+ Slog.i(TAG_WM, "WIN DEATH: " + w);
+ if (w.mActivityRecord != null && w.mActivityRecord.findMainWindow() == w) {
+ mService.mSnapshotController.onAppDied(w.mActivityRecord);
+ }
+ w.removeIfPossible();
+ }
+ } finally {
+ killSessionLocked();
+ }
}
}
@@ -741,7 +756,7 @@
}
}
- void windowAddedLocked() {
+ void onWindowAdded(WindowState w) {
if (mPackageName == null) {
mPackageName = mProcess.mInfo.packageName;
mRelayoutTag = "relayoutWindow: " + mPackageName;
@@ -757,12 +772,14 @@
mService.dispatchNewAnimatorScaleLocked(this);
}
}
- mNumWindow++;
+ mAddedWindows.add(w);
}
- void windowRemovedLocked() {
- mNumWindow--;
- killSessionLocked();
+ void onWindowRemoved(WindowState w) {
+ mAddedWindows.remove(w);
+ if (mAddedWindows.isEmpty()) {
+ killSessionLocked();
+ }
}
@@ -829,7 +846,7 @@
}
private void killSessionLocked() {
- if (mNumWindow > 0 || !mClientDead) {
+ if (!mClientDead) {
return;
}
@@ -838,10 +855,6 @@
return;
}
- if (DEBUG) {
- Slog.v(TAG_WM, "Last window removed from " + this
- + ", destroying " + mSurfaceSession);
- }
ProtoLog.i(WM_SHOW_TRANSACTIONS, " KILL SURFACE SESSION %s", mSurfaceSession);
try {
mSurfaceSession.kill();
@@ -850,6 +863,7 @@
+ " in session " + this + ": " + e.toString());
}
mSurfaceSession = null;
+ mAddedWindows.clear();
mAlertWindowSurfaces.clear();
mAppOverlaySurfaces.clear();
setHasOverlayUi(false);
@@ -869,7 +883,7 @@
}
void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.print("mNumWindow="); pw.print(mNumWindow);
+ pw.print(prefix); pw.print("numWindow="); pw.print(mAddedWindows.size());
pw.print(" mCanAddInternalSystemWindow="); pw.print(mCanAddInternalSystemWindow);
pw.print(" mAppOverlaySurfaces="); pw.print(mAppOverlaySurfaces);
pw.print(" mAlertWindowSurfaces="); pw.print(mAlertWindowSurfaces);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 2125c63..c1310a6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1445,6 +1445,11 @@
if (!mDisplayReady) {
throw new IllegalStateException("Display has not been initialialized");
}
+ if (session.isClientDead()) {
+ ProtoLog.w(WM_ERROR, "Attempted to add window with a client %s "
+ + "that is dead. Aborting.", session);
+ return WindowManagerGlobal.ADD_APP_EXITING;
+ }
final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);
@@ -1629,19 +1634,6 @@
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], attrs, viewVisibility, session.mUid, userId,
session.mCanAddInternalSystemWindow);
- if (win.mDeathRecipient == null) {
- // Client has apparently died, so there is no reason to
- // continue.
- ProtoLog.w(WM_ERROR, "Adding window client %s"
- + " that is dead, aborting.", client.asBinder());
- return WindowManagerGlobal.ADD_APP_EXITING;
- }
-
- if (win.getDisplayContent() == null) {
- ProtoLog.w(WM_ERROR, "Adding window to Display that has been removed.");
- return WindowManagerGlobal.ADD_INVALID_DISPLAY;
- }
-
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid);
@@ -1725,7 +1717,7 @@
displayContent.mTapExcludedWindows.add(win);
}
- win.attach();
+ win.mSession.onWindowAdded(win);
mWindowMap.put(client.asBinder(), win);
win.initAppOpsState();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4e9d23c..56e7c69 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -310,7 +310,6 @@
// mAttrs.flags is tested in animation without being locked. If the bits tested are ever
// modified they will need to be locked.
final WindowManager.LayoutParams mAttrs = new WindowManager.LayoutParams();
- final DeathRecipient mDeathRecipient;
private boolean mIsChildWindow;
final int mBaseLayer;
final int mSubLayer;
@@ -1108,7 +1107,6 @@
mViewVisibility = viewVisibility;
mPolicy = mWmService.mPolicy;
mContext = mWmService.mContext;
- DeathRecipient deathRecipient = new DeathRecipient();
mPowerManagerWrapper = powerManagerWrapper;
mForceSeamlesslyRotate = token.mRoundedCornerOverlay;
mInputWindowHandle = new InputWindowHandleWrapper(new InputWindowHandle(
@@ -1128,22 +1126,6 @@
Slog.v(TAG, "Window " + this + " client=" + c.asBinder()
+ " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
}
- try {
- c.asBinder().linkToDeath(deathRecipient, 0);
- } catch (RemoteException e) {
- mDeathRecipient = null;
- mIsChildWindow = false;
- mLayoutAttached = false;
- mIsImWindow = false;
- mIsWallpaper = false;
- mIsFloatingLayer = false;
- mBaseLayer = 0;
- mSubLayer = 0;
- mWinAnimator = null;
- mOverrideScale = 1f;
- return;
- }
- mDeathRecipient = deathRecipient;
if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW) {
// The multiplier here is to reserve space for multiple
@@ -1238,11 +1220,6 @@
return TouchOcclusionMode.BLOCK_UNTRUSTED;
}
- void attach() {
- if (DEBUG) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
- mSession.windowAddedLocked();
- }
-
void updateGlobalScale() {
if (hasCompatScale()) {
mCompatScale = (mOverrideScale == 1f || mToken.hasSizeCompatBounds())
@@ -2398,14 +2375,7 @@
disposeInputChannel();
mOnBackInvokedCallbackInfo = null;
- mSession.windowRemovedLocked();
- try {
- mClient.asBinder().unlinkToDeath(mDeathRecipient, 0);
- } catch (RuntimeException e) {
- // Ignore if it has already been removed (usually because
- // we are doing this as part of processing a death note.)
- }
-
+ mSession.onWindowRemoved(this);
mWmService.postWindowRemoveCleanupLocked(this);
mWmService.mTrustedPresentationListenerController.removeIgnoredWindowTokens(
@@ -2935,31 +2905,6 @@
}
}
- private class DeathRecipient implements IBinder.DeathRecipient {
- @Override
- public void binderDied() {
- try {
- synchronized (mWmService.mGlobalLock) {
- final WindowState win = mWmService
- .windowForClientLocked(mSession, mClient, false);
- Slog.i(TAG, "WIN DEATH: " + win);
- if (win != null) {
- if (win.mActivityRecord != null
- && win.mActivityRecord.findMainWindow() == win) {
- mWmService.mSnapshotController.onAppDied(win.mActivityRecord);
- }
- win.removeIfPossible();
- } else if (mHasSurface) {
- Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid.");
- WindowState.this.removeIfPossible();
- }
- }
- } catch (IllegalArgumentException ex) {
- // This will happen if the window has already been removed.
- }
- }
- }
-
/** Returns {@code true} if this window desires key events. */
boolean canReceiveKeys() {
return canReceiveKeys(false /* fromUserTouch */);
@@ -3830,7 +3775,7 @@
}
@Override
- public void notifyInsetsControlChanged() {
+ public void notifyInsetsControlChanged(int displayId) {
ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "notifyInsetsControlChanged for %s ", this);
if (mRemoved) {
return;
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index f69f628..022268d 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -1262,7 +1262,7 @@
val apexModuleName = packageState.apexModuleName
val packageName = packageState.packageName
return when {
- packageState.isVendor ->
+ packageState.isVendor || packageState.isOdm ->
permissionAllowlist.getVendorPrivilegedAppAllowlistState(
packageName,
permissionName
@@ -1471,12 +1471,15 @@
// In any case, don't grant a privileged permission to privileged vendor apps,
// if the permission's protectionLevel does not have the extra vendorPrivileged
// flag.
- if (packageState.isVendor && !permission.isVendorPrivileged) {
+ if (
+ (packageState.isVendor || packageState.isOdm) &&
+ !permission.isVendorPrivileged
+ ) {
Slog.w(
LOG_TAG,
"Permission $permissionName cannot be granted to privileged" +
- " vendor app $packageName because it isn't a vendorPrivileged" +
- " permission"
+ " vendor (or odm) app $packageName because it isn't a" +
+ " vendorPrivileged permission"
)
return false
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
index dea838d..fbb14c3 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -88,4 +89,12 @@
verify(mDisplayPowerController).setBrightnessFromOffload(brightness);
}
+
+ @Test
+ public void testBlockScreenOn() {
+ Runnable unblocker = () -> {};
+ mSession.blockScreenOn(unblocker);
+
+ verify(mDisplayOffloader).onBlockingScreenOn(eq(unblocker));
+ }
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index f36854b..0c845de 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -1235,6 +1235,9 @@
@Override
public void stopOffload() {}
+
+ @Override
+ public void onBlockingScreenOn(Runnable unblocker) {}
});
mDisplayOffloadSession = new DisplayOffloadSessionImpl(mDisplayOffloader,
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 3b5cae3..88b2ed4 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -50,14 +50,22 @@
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.PromptInfo;
+import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.face.FaceSensorConfigurations;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceService;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintService;
import android.hardware.iris.IIrisService;
import android.os.Binder;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.InstrumentationRegistry;
@@ -89,6 +97,9 @@
@Rule
public MockitoRule mockitorule = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -118,6 +129,10 @@
@Captor
private ArgumentCaptor<List<FingerprintSensorPropertiesInternal>> mFingerprintPropsCaptor;
@Captor
+ private ArgumentCaptor<FingerprintSensorConfigurations> mFingerprintSensorConfigurationsCaptor;
+ @Captor
+ private ArgumentCaptor<FaceSensorConfigurations> mFaceSensorConfigurationsCaptor;
+ @Captor
private ArgumentCaptor<List<FaceSensorPropertiesInternal>> mFacePropsCaptor;
@Before
@@ -143,6 +158,9 @@
when(mContext.getResources()).thenReturn(mResources);
when(mInjector.getBiometricService()).thenReturn(mBiometricService);
when(mInjector.getConfiguration(any())).thenReturn(config);
+ when(mInjector.getFaceConfiguration(any())).thenReturn(config);
+ when(mInjector.getFingerprintConfiguration(any())).thenReturn(config);
+ when(mInjector.getIrisConfiguration(any())).thenReturn(config);
when(mInjector.getFingerprintService()).thenReturn(mFingerprintService);
when(mInjector.getFaceService()).thenReturn(mFaceService);
when(mInjector.getIrisService()).thenReturn(mIrisService);
@@ -173,12 +191,13 @@
}
@Test
+ @RequiresFlagsDisabled(com.android.server.biometrics.Flags.FLAG_DE_HIDL)
public void testRegisterAuthenticator_registerAuthenticators() throws Exception {
final int fingerprintId = 0;
final int fingerprintStrength = 15;
final int faceId = 1;
- final int faceStrength = 4095;
+ final int faceStrength = 15;
final String[] config = {
// ID0:Fingerprint:Strong
@@ -206,6 +225,51 @@
Utils.authenticatorStrengthToPropertyStrength(faceStrength));
}
+ @Test
+ @RequiresFlagsEnabled(com.android.server.biometrics.Flags.FLAG_DE_HIDL)
+ public void testRegisterAuthenticator_registerAuthenticatorsLegacy() throws RemoteException {
+ final int fingerprintId = 0;
+ final int fingerprintStrength = 15;
+
+ final int faceId = 1;
+ final int faceStrength = 4095;
+
+ final String[] config = {
+ // ID0:Fingerprint:Strong
+ String.format("%d:2:%d", fingerprintId, fingerprintStrength),
+ // ID2:Face:Convenience
+ String.format("%d:8:%d", faceId, faceStrength)
+ };
+
+ when(mInjector.getFingerprintConfiguration(any())).thenReturn(config);
+ when(mInjector.getFaceConfiguration(any())).thenReturn(config);
+ when(mInjector.getFingerprintAidlInstances()).thenReturn(new String[]{});
+ when(mInjector.getFaceAidlInstances()).thenReturn(new String[]{});
+
+ mAuthService = new AuthService(mContext, mInjector);
+ mAuthService.onStart();
+
+ verify(mFingerprintService).registerAuthenticatorsLegacy(
+ mFingerprintSensorConfigurationsCaptor.capture());
+
+ final SensorProps[] fingerprintProp = mFingerprintSensorConfigurationsCaptor.getValue()
+ .getSensorPairForInstance("defaultHIDL").second;
+
+ assertEquals(fingerprintProp[0].commonProps.sensorId, fingerprintId);
+ assertEquals(fingerprintProp[0].commonProps.sensorStrength,
+ Utils.authenticatorStrengthToPropertyStrength(fingerprintStrength));
+
+ verify(mFaceService).registerAuthenticatorsLegacy(
+ mFaceSensorConfigurationsCaptor.capture());
+
+ final android.hardware.biometrics.face.SensorProps[] faceProp =
+ mFaceSensorConfigurationsCaptor.getValue()
+ .getSensorPairForInstance("defaultHIDL").second;
+
+ assertEquals(faceProp[0].commonProps.sensorId, faceId);
+ assertEquals(faceProp[0].commonProps.sensorStrength,
+ Utils.authenticatorStrengthToPropertyStrength(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/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
new file mode 100644
index 0000000..c9e1c4a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face;
+
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+import static android.hardware.face.FaceSensorProperties.TYPE_UNKNOWN;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.SensorProps;
+import android.hardware.face.FaceSensorConfigurations;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.provider.Settings;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.biometrics.Flags;
+import com.android.server.biometrics.Utils;
+import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@Presubmit
+@SmallTest
+public class FaceServiceTest {
+ private static final int ID_DEFAULT = 2;
+ private static final int ID_VIRTUAL = 6;
+ private static final String NAME_DEFAULT = "default";
+ private static final String NAME_VIRTUAL = "virtual";
+
+ @Rule
+ public final MockitoRule mMockito = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+ @Rule
+ public final FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();
+ @Mock
+ FaceProvider mFaceProviderDefault;
+ @Mock
+ FaceProvider mFaceProviderVirtual;
+ @Mock
+ IFace mDefaultFaceDaemon;
+ @Mock
+ IFace mVirtualFaceDaemon;
+ @Mock
+ IBiometricService mIBiometricService;
+
+ private final SensorProps mDefaultSensorProps = new SensorProps();
+ private final SensorProps mVirtualSensorProps = new SensorProps();
+ private FaceService mFaceService;
+ private final FaceSensorPropertiesInternal mSensorPropsDefault =
+ new FaceSensorPropertiesInternal(ID_DEFAULT, STRENGTH_STRONG,
+ 2 /* maxEnrollmentsPerUser */,
+ List.of(),
+ TYPE_UNKNOWN,
+ true /* supportsFaceDetection */,
+ true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresChallenge */);
+ private final FaceSensorPropertiesInternal mSensorPropsVirtual =
+ new FaceSensorPropertiesInternal(ID_VIRTUAL, STRENGTH_STRONG,
+ 2 /* maxEnrollmentsPerUser */,
+ List.of(),
+ TYPE_UNKNOWN,
+ true /* supportsFaceDetection */,
+ true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresChallenge */);
+ private FaceSensorConfigurations mFaceSensorConfigurations;
+
+ @Before
+ public void setUp() throws RemoteException {
+ when(mDefaultFaceDaemon.getSensorProps()).thenReturn(
+ new SensorProps[]{mDefaultSensorProps});
+ when(mVirtualFaceDaemon.getSensorProps()).thenReturn(
+ new SensorProps[]{mVirtualSensorProps});
+ when(mFaceProviderDefault.getSensorProperties()).thenReturn(List.of(mSensorPropsDefault));
+ when(mFaceProviderVirtual.getSensorProperties()).thenReturn(List.of(mSensorPropsVirtual));
+
+ mFaceSensorConfigurations = new FaceSensorConfigurations(false);
+ mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_DEFAULT, NAME_VIRTUAL},
+ (name) -> {
+ if (name.equals(IFace.DESCRIPTOR + "/" + NAME_DEFAULT)) {
+ return mDefaultFaceDaemon;
+ } else if (name.equals(IFace.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
+ return mVirtualFaceDaemon;
+ }
+ return null;
+ });
+ }
+
+ private void initService() {
+ mFaceService = new FaceService(mContext,
+ (filteredSensorProps, resetLockoutRequiresChallenge) -> {
+ if (NAME_DEFAULT.equals(filteredSensorProps.first)) return mFaceProviderDefault;
+ if (NAME_VIRTUAL.equals(filteredSensorProps.first)) return mFaceProviderVirtual;
+ return null;
+ }, () -> mIBiometricService);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void registerAuthenticatorsLegacy_defaultOnly() throws Exception {
+ initService();
+
+ mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ waitForRegistration();
+
+ verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT),
+ eq(BiometricAuthenticator.TYPE_FACE),
+ eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)),
+ any());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void registerAuthenticatorsLegacy_virtualOnly() throws Exception {
+ initService();
+ Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
+ Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
+
+ mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ waitForRegistration();
+
+ verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL),
+ eq(BiometricAuthenticator.TYPE_FACE),
+ eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)), any());
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
+ mFaceSensorConfigurations = new FaceSensorConfigurations(false);
+ mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_VIRTUAL},
+ (name) -> {
+ if (name.equals(IFace.DESCRIPTOR + "/" + NAME_DEFAULT)) {
+ return mDefaultFaceDaemon;
+ } else if (name.equals(IFace.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
+ return mVirtualFaceDaemon;
+ }
+ return null;
+ });
+ initService();
+
+ mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ waitForRegistration();
+
+ verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL),
+ eq(BiometricAuthenticator.TYPE_FACE),
+ eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)), any());
+ }
+
+ private void waitForRegistration() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mFaceService.mServiceWrapper.addAuthenticatorsRegisteredCallback(
+ new IFaceAuthenticatorsRegisteredCallback.Stub() {
+ public void onAllAuthenticatorsRegistered(
+ List<FaceSensorPropertiesInternal> sensors) {
+ latch.countDown();
+ }
+ });
+ latch.await(10, TimeUnit.SECONDS);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index f43120d..8b1a291 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -16,6 +16,9 @@
package com.android.server.biometrics.sensors.face.aidl;
+import static android.os.UserHandle.USER_NULL;
+import static android.os.UserHandle.USER_SYSTEM;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
@@ -32,15 +35,19 @@
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.ISession;
import android.hardware.biometrics.face.SensorProps;
+import android.hardware.face.HidlFaceSensorConfig;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -49,6 +56,7 @@
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -59,6 +67,10 @@
@SmallTest
public class FaceProviderTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final String TAG = "FaceProviderTest";
private static final float FRR_THRESHOLD = 0.2f;
@@ -109,7 +121,7 @@
mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext,
- mDaemon);
+ mDaemon, false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */);
}
@Test
@@ -124,10 +136,38 @@
assertThat(currentClient).isInstanceOf(FaceInternalCleanupClient.class);
assertThat(currentClient.getSensorId()).isEqualTo(prop.commonProps.sensorId);
- assertThat(currentClient.getTargetUserId()).isEqualTo(UserHandle.USER_SYSTEM);
+ assertThat(currentClient.getTargetUserId()).isEqualTo(USER_SYSTEM);
}
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void testAddingHidlSensors() {
+ when(mResources.getIntArray(anyInt())).thenReturn(new int[]{});
+ when(mResources.getBoolean(anyInt())).thenReturn(false);
+
+ final int faceId = 0;
+ final int faceStrength = 15;
+ final String config = String.format("%d:8:%d", faceId, faceStrength);
+ final HidlFaceSensorConfig faceSensorConfig = new HidlFaceSensorConfig();
+ faceSensorConfig.parse(config, mContext);
+ final HidlFaceSensorConfig[] hidlFaceSensorConfig =
+ new HidlFaceSensorConfig[]{faceSensorConfig};
+ mFaceProvider = new FaceProvider(mContext,
+ mBiometricStateCallback, hidlFaceSensorConfig, TAG,
+ mLockoutResetDispatcher, mBiometricContext, mDaemon,
+ true /* resetLockoutRequiresChallenge */,
+ true /* testHalEnabled */);
+
+ assertThat(mFaceProvider.mFaceSensors.get(faceId)
+ .getLazySession().get().getUserId()).isEqualTo(USER_NULL);
+
+ waitForIdle();
+
+ assertThat(mFaceProvider.mFaceSensors.get(faceId)
+ .getLazySession().get().getUserId()).isEqualTo(USER_SYSTEM);
+ }
+
@SuppressWarnings("rawtypes")
@Test
public void halServiceDied_resetsAllSchedulers() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index 7a293e8..e7f7195 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -157,7 +157,7 @@
sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext,
null /* handler */, internalProp, mLockoutResetDispatcher, mBiometricContext);
-
+ sensor.init(mLockoutResetDispatcher, mFaceProvider);
mScheduler.reset();
assertNull(mScheduler.getCurrentClient());
@@ -185,6 +185,7 @@
sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);
final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null,
internalProp, mLockoutResetDispatcher, mBiometricContext, mCurrentSession);
+ sensor.init(mLockoutResetDispatcher, mFaceProvider);
mScheduler = (UserAwareBiometricScheduler) sensor.getScheduler();
sensor.mCurrentSession = new AidlSession(0, mock(ISession.class),
USER_ID, mHalCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
new file mode 100644
index 0000000..4e43332
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.face.hidl;
+
+import static com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter.ENROLL_TIMEOUT_SEC;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.face.V1_0.IBiometricsFace;
+import android.hardware.biometrics.face.V1_0.OptionalUint64;
+import android.hardware.biometrics.face.V1_0.Status;
+import android.hardware.face.Face;
+import android.hardware.face.HidlFaceSensorConfig;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.testing.TestableContext;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient;
+import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
+import com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+public class HidlToAidlSensorAdapterTest {
+ private static final String TAG = "HidlToAidlSensorAdapterTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+ private static final byte[] HAT = new byte[69];
+ @Rule
+ public final MockitoRule mMockito = MockitoJUnit.rule();
+
+ @Mock
+ private IBiometricService mBiometricService;
+ @Mock
+ private LockoutResetDispatcher mLockoutResetDispatcherForSensor;
+ @Mock
+ private LockoutResetDispatcher mLockoutResetDispatcherForClient;
+ @Mock
+ private BiometricLogger mLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private AuthSessionCoordinator mAuthSessionCoordinator;
+ @Mock
+ private FaceProvider mFaceProvider;
+ @Mock
+ private Runnable mInternalCleanupAndGetFeatureRunnable;
+ @Mock
+ private IBiometricsFace mDaemon;
+ @Mock
+ AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+ @Mock
+ BiometricUtils<Face> mBiometricUtils;
+
+ private final TestLooper mLooper = new TestLooper();
+ private HidlToAidlSensorAdapter mHidlToAidlSensorAdapter;
+ private final TestableContext mContext = new TestableContext(
+ ApplicationProvider.getApplicationContext());
+
+ @Before
+ public void setUp() throws RemoteException {
+ final OptionalUint64 result = new OptionalUint64();
+ result.status = Status.OK;
+
+ when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+ when(mDaemon.setCallback(any())).thenReturn(result);
+ doAnswer((answer) -> {
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+ .onLockoutChanged(0);
+ return null;
+ }).when(mDaemon).resetLockout(any());
+ doAnswer((answer) -> {
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+ .onEnrollmentProgress(1, 0);
+ return null;
+ }).when(mDaemon).enroll(any(), anyInt(), any());
+
+ mContext.getOrCreateTestableResources();
+
+ final String config = String.format("%d:8:15", SENSOR_ID);
+ final BiometricScheduler scheduler = new BiometricScheduler(TAG,
+ new Handler(mLooper.getLooper()),
+ BiometricScheduler.SENSOR_TYPE_FACE,
+ null /* gestureAvailabilityTracker */,
+ mBiometricService, 10 /* recentOperationsLimit */);
+ final HidlFaceSensorConfig faceSensorConfig = new HidlFaceSensorConfig();
+ faceSensorConfig.parse(config, mContext);
+ mHidlToAidlSensorAdapter = new HidlToAidlSensorAdapter(TAG, mFaceProvider,
+ mContext, new Handler(mLooper.getLooper()), faceSensorConfig,
+ mLockoutResetDispatcherForSensor, mBiometricContext,
+ false /* resetLockoutRequiresChallenge */, mInternalCleanupAndGetFeatureRunnable,
+ mAuthSessionCoordinator, mDaemon, mAidlResponseHandlerCallback);
+ mHidlToAidlSensorAdapter.init(mLockoutResetDispatcherForSensor, mFaceProvider);
+ mHidlToAidlSensorAdapter.setScheduler(scheduler);
+ mHidlToAidlSensorAdapter.handleUserChanged(USER_ID);
+ }
+
+ @Test
+ public void lockoutTimedResetViaClient() {
+ setLockoutTimed();
+
+ mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(
+ new FaceResetLockoutClient(mContext, mHidlToAidlSensorAdapter.getLazySession(),
+ USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT,
+ mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */),
+ mLockoutResetDispatcherForClient, 0 /* biometricStrength */));
+ mLooper.dispatchAll();
+
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false/* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mLockoutResetDispatcherForClient, never()).notifyLockoutResetCallbacks(SENSOR_ID);
+ verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(SENSOR_ID);
+ verify(mAuthSessionCoordinator, never()).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void lockoutTimedResetViaCallback() {
+ setLockoutTimed();
+
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback().onLockoutChanged(0);
+ mLooper.dispatchAll();
+
+ verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(eq(
+ SENSOR_ID));
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID))
+ .isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mAuthSessionCoordinator, never()).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void lockoutPermanentResetViaCallback() {
+ setLockoutPermanent();
+
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback().onLockoutChanged(0);
+ mLooper.dispatchAll();
+
+ verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(eq(
+ SENSOR_ID));
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mAuthSessionCoordinator, never()).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void lockoutPermanentResetViaClient() {
+ setLockoutPermanent();
+
+ mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(
+ new FaceResetLockoutClient(mContext,
+ mHidlToAidlSensorAdapter.getLazySession(),
+ USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT,
+ mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */),
+ mLockoutResetDispatcherForClient, 0 /* biometricStrength */));
+ mLooper.dispatchAll();
+
+ verify(mLockoutResetDispatcherForClient, never()).notifyLockoutResetCallbacks(SENSOR_ID);
+ verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(SENSOR_ID);
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mAuthSessionCoordinator, never()).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void verifyOnEnrollSuccessCallback() {
+ mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(new FaceEnrollClient(mContext,
+ mHidlToAidlSensorAdapter.getLazySession(), null /* token */, null /* listener */,
+ USER_ID, HAT, TAG, 1 /* requestId */, mBiometricUtils,
+ new int[]{} /* disabledFeatures */, ENROLL_TIMEOUT_SEC, null /* previewSurface */,
+ SENSOR_ID, mLogger, mBiometricContext, 1 /* maxTemplatesPerUser */,
+ false /* debugConsent */));
+ mLooper.dispatchAll();
+
+ verify(mAidlResponseHandlerCallback).onEnrollSuccess();
+ }
+
+ private void setLockoutTimed() {
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+ .onLockoutChanged(1);
+ mLooper.dispatchAll();
+
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID))
+ .isEqualTo(LockoutTracker.LOCKOUT_TIMED);
+ }
+
+ private void setLockoutPermanent() {
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+ .onLockoutChanged(-1);
+ mLooper.dispatchAll();
+
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_PERMANENT);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
similarity index 80%
rename from services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java
rename to services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
index 9a40e8a..b9a4fb4 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
@@ -16,7 +16,7 @@
package com.android.server.biometrics.sensors.face.hidl;
-import static com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter.ENROLL_TIMEOUT_SEC;
+import static com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter.ENROLL_TIMEOUT_SEC;
import static com.android.server.biometrics.sensors.face.hidl.FaceGenerateChallengeClient.CHALLENGE_TIMEOUT_SEC;
import static com.google.common.truth.Truth.assertThat;
@@ -68,7 +68,7 @@
@Presubmit
@SmallTest
-public class AidlToHidlAdapterTest {
+public class HidlToAidlSessionAdapterTest {
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@@ -84,25 +84,32 @@
private Clock mClock;
private final long mChallenge = 100L;
- private AidlToHidlAdapter mAidlToHidlAdapter;
+ private HidlToAidlSessionAdapter mHidlToAidlSessionAdapter;
private final Face mFace = new Face("face" /* name */, 1 /* faceId */, 0 /* deviceId */);
private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_REQUIRE_DIVERSITY;
private final byte[] mFeatures = new byte[]{Feature.REQUIRE_ATTENTION};
@Before
public void setUp() throws RemoteException {
+ final OptionalUint64 setCallbackResult = new OptionalUint64();
+ setCallbackResult.value = 1;
+
+ when(mSession.setCallback(any())).thenReturn(setCallbackResult);
+
TestableContext testableContext = new TestableContext(
InstrumentationRegistry.getInstrumentation().getContext());
testableContext.addMockSystemService(FaceManager.class, mFaceManager);
- mAidlToHidlAdapter = new AidlToHidlAdapter(testableContext, () -> mSession, 0 /* userId */,
- mAidlResponseHandler, mClock);
+
+ mHidlToAidlSessionAdapter = new HidlToAidlSessionAdapter(testableContext, () -> mSession,
+ 0 /* userId */, mAidlResponseHandler, mClock);
mHardwareAuthToken.timestamp = new Timestamp();
mHardwareAuthToken.mac = new byte[10];
- final OptionalUint64 result = new OptionalUint64();
- result.status = Status.OK;
- result.value = mChallenge;
- when(mSession.generateChallenge(anyInt())).thenReturn(result);
+ final OptionalUint64 generateChallengeResult = new OptionalUint64();
+ generateChallengeResult.status = Status.OK;
+ generateChallengeResult.value = mChallenge;
+
+ when(mSession.generateChallenge(anyInt())).thenReturn(generateChallengeResult);
when(mFaceManager.getEnrolledFaces(anyInt())).thenReturn(List.of(mFace));
}
@@ -112,16 +119,16 @@
final ArgumentCaptor<Long> challengeCaptor = ArgumentCaptor.forClass(Long.class);
- mAidlToHidlAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
verify(mSession).generateChallenge(CHALLENGE_TIMEOUT_SEC);
verify(mAidlResponseHandler).onChallengeGenerated(challengeCaptor.capture());
assertThat(challengeCaptor.getValue()).isEqualTo(mChallenge);
forwardTime(10 /* seconds */);
- mAidlToHidlAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
forwardTime(20 /* seconds */);
- mAidlToHidlAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
//Confirms that the challenge is cached and the hal method is not called again
verifyNoMoreInteractions(mSession);
@@ -129,7 +136,7 @@
.onChallengeGenerated(mChallenge);
forwardTime(60 /* seconds */);
- mAidlToHidlAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
//HAL method called after challenge has timed out
verify(mSession, times(2)).generateChallenge(CHALLENGE_TIMEOUT_SEC);
@@ -138,11 +145,11 @@
@Test
public void testRevokeChallenge_waitsUntilEmpty() throws RemoteException {
for (int i = 0; i < 3; i++) {
- mAidlToHidlAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
forwardTime(10 /* seconds */);
}
for (int i = 0; i < 3; i++) {
- mAidlToHidlAdapter.revokeChallenge(0);
+ mHidlToAidlSessionAdapter.revokeChallenge(0);
forwardTime((i + 1) * 10 /* seconds */);
}
@@ -151,20 +158,19 @@
@Test
public void testRevokeChallenge_timeout() throws RemoteException {
- mAidlToHidlAdapter.generateChallenge();
- mAidlToHidlAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
forwardTime(700);
- mAidlToHidlAdapter.generateChallenge();
- mAidlToHidlAdapter.revokeChallenge(0);
+ mHidlToAidlSessionAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.revokeChallenge(0);
verify(mSession).revokeChallenge();
}
@Test
public void testEnroll() throws RemoteException {
- ICancellationSignal cancellationSignal = mAidlToHidlAdapter.enroll(mHardwareAuthToken,
- EnrollmentType.DEFAULT, mFeatures,
- null /* previewSurface */);
+ ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter.enroll(
+ mHardwareAuthToken, EnrollmentType.DEFAULT, mFeatures, null /* previewSurface */);
ArgumentCaptor<ArrayList<Integer>> featureCaptor = ArgumentCaptor.forClass(ArrayList.class);
verify(mSession).enroll(any(), eq(ENROLL_TIMEOUT_SEC), featureCaptor.capture());
@@ -182,7 +188,8 @@
@Test
public void testAuthenticate() throws RemoteException {
final int operationId = 2;
- ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId);
+ ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter.authenticate(
+ operationId);
verify(mSession).authenticate(operationId);
@@ -193,7 +200,7 @@
@Test
public void testDetectInteraction() throws RemoteException {
- ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction();
+ ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter.detectInteraction();
verify(mSession).authenticate(0);
@@ -204,7 +211,7 @@
@Test
public void testEnumerateEnrollments() throws RemoteException {
- mAidlToHidlAdapter.enumerateEnrollments();
+ mHidlToAidlSessionAdapter.enumerateEnrollments();
verify(mSession).enumerate();
}
@@ -212,7 +219,7 @@
@Test
public void testRemoveEnrollment() throws RemoteException {
final int[] enrollments = new int[]{1};
- mAidlToHidlAdapter.removeEnrollments(enrollments);
+ mHidlToAidlSessionAdapter.removeEnrollments(enrollments);
verify(mSession).remove(enrollments[0]);
}
@@ -226,8 +233,8 @@
when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
- mAidlToHidlAdapter.setFeature(mFeature);
- mAidlToHidlAdapter.getFeatures();
+ mHidlToAidlSessionAdapter.setFeature(mFeature);
+ mHidlToAidlSessionAdapter.getFeatures();
verify(mSession).getFeature(eq(mFeature), anyInt());
verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture());
@@ -244,8 +251,8 @@
when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
- mAidlToHidlAdapter.setFeature(mFeature);
- mAidlToHidlAdapter.getFeatures();
+ mHidlToAidlSessionAdapter.setFeature(mFeature);
+ mHidlToAidlSessionAdapter.getFeatures();
verify(mSession).getFeature(eq(mFeature), anyInt());
verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture());
@@ -260,8 +267,8 @@
when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result);
- mAidlToHidlAdapter.setFeature(mFeature);
- mAidlToHidlAdapter.getFeatures();
+ mHidlToAidlSessionAdapter.setFeature(mFeature);
+ mHidlToAidlSessionAdapter.getFeatures();
verify(mSession).getFeature(eq(mFeature), anyInt());
verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any());
@@ -270,7 +277,7 @@
@Test
public void testGetFeatures_featureNotSet() throws RemoteException {
- mAidlToHidlAdapter.getFeatures();
+ mHidlToAidlSessionAdapter.getFeatures();
verify(mSession, never()).getFeature(eq(mFeature), anyInt());
verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any());
@@ -283,7 +290,7 @@
when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt())).thenReturn(Status.OK);
- mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled);
+ mHidlToAidlSessionAdapter.setFeature(mHardwareAuthToken, feature, enabled);
verify(mAidlResponseHandler).onFeatureSet(feature);
}
@@ -296,7 +303,7 @@
when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt()))
.thenReturn(Status.INTERNAL_ERROR);
- mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled);
+ mHidlToAidlSessionAdapter.setFeature(mHardwareAuthToken, feature, enabled);
verify(mAidlResponseHandler).onError(BiometricFaceConstants.FACE_ERROR_UNKNOWN,
0 /* vendorCode */);
@@ -311,7 +318,7 @@
when(mSession.getAuthenticatorId()).thenReturn(result);
- mAidlToHidlAdapter.getAuthenticatorId();
+ mHidlToAidlSessionAdapter.getAuthenticatorId();
verify(mSession).getAuthenticatorId();
verify(mAidlResponseHandler).onAuthenticatorIdRetrieved(authenticatorId);
@@ -319,7 +326,7 @@
@Test
public void testResetLockout() throws RemoteException {
- mAidlToHidlAdapter.resetLockout(mHardwareAuthToken);
+ mHidlToAidlSessionAdapter.resetLockout(mHardwareAuthToken);
ArgumentCaptor<ArrayList> hatCaptor = ArgumentCaptor.forClass(ArrayList.class);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index 2aa62d9..f570ba2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -42,13 +42,19 @@
import android.app.AppOpsManager;
import android.content.pm.PackageManager;
import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
+import android.hardware.fingerprint.FingerprintSensorConfigurations;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.os.Binder;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.testing.TestableContext;
@@ -58,6 +64,7 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.LocalServices;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
@@ -89,6 +96,9 @@
@Rule
public final MockitoRule mMockito = MockitoJUnit.rule();
@Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
public final TestableContext mContext = new TestableContext(
InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
@Rule
@@ -110,6 +120,10 @@
private IBinder mToken;
@Mock
private VirtualDeviceManagerInternal mVdmInternal;
+ @Mock
+ private IFingerprint mDefaultFingerprintDaemon;
+ @Mock
+ private IFingerprint mVirtualFingerprintDaemon;
@Captor
private ArgumentCaptor<FingerprintAuthenticateOptions> mAuthenticateOptionsCaptor;
@@ -126,7 +140,10 @@
List.of(),
TYPE_UDFPS_OPTICAL,
false /* resetLockoutRequiresHardwareAuthToken */);
+ private FingerprintSensorConfigurations mFingerprintSensorConfigurations;
private FingerprintService mService;
+ private final SensorProps mDefaultSensorProps = new SensorProps();
+ private final SensorProps mVirtualSensorProps = new SensorProps();
@Before
public void setup() throws Exception {
@@ -139,6 +156,10 @@
.thenAnswer(i -> i.getArguments()[0].equals(ID_DEFAULT));
when(mFingerprintVirtual.containsSensor(anyInt()))
.thenAnswer(i -> i.getArguments()[0].equals(ID_VIRTUAL));
+ when(mDefaultFingerprintDaemon.getSensorProps()).thenReturn(
+ new SensorProps[]{mDefaultSensorProps});
+ when(mVirtualFingerprintDaemon.getSensorProps()).thenReturn(
+ new SensorProps[]{mVirtualSensorProps});
mContext.addMockSystemService(AppOpsManager.class, mAppOpsManager);
for (int permission : List.of(OP_USE_BIOMETRIC, OP_USE_FINGERPRINT)) {
@@ -150,6 +171,18 @@
mContext.getTestablePermissions().setPermission(
permission, PackageManager.PERMISSION_GRANTED);
}
+
+ mFingerprintSensorConfigurations = new FingerprintSensorConfigurations(
+ true /* resetLockoutRequiresHardwareAuthToken */);
+ mFingerprintSensorConfigurations.addAidlSensors(new String[]{NAME_DEFAULT, NAME_VIRTUAL},
+ (name) -> {
+ if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_DEFAULT)) {
+ return mDefaultFingerprintDaemon;
+ } else if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
+ return mVirtualFingerprintDaemon;
+ }
+ return null;
+ });
}
private void initServiceWith(String... aidlInstances) {
@@ -160,6 +193,10 @@
if (NAME_DEFAULT.equals(name)) return mFingerprintDefault;
if (NAME_VIRTUAL.equals(name)) return mFingerprintVirtual;
return null;
+ }, (sensorPropsPair, resetLockoutRequiresHardwareAuthToken) -> {
+ if (NAME_DEFAULT.equals(sensorPropsPair.first)) return mFingerprintDefault;
+ if (NAME_VIRTUAL.equals(sensorPropsPair.first)) return mFingerprintVirtual;
+ return null;
});
}
@@ -180,6 +217,17 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void registerAuthenticatorsLegacy_defaultOnly() throws Exception {
+ initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
+
+ mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+ waitForRegistration();
+
+ verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any());
+ }
+
+ @Test
public void registerAuthenticators_virtualOnly() throws Exception {
initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
@@ -192,6 +240,19 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void registerAuthenticatorsLegacy_virtualOnly() throws Exception {
+ initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
+ Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
+ Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
+
+ mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+ waitForRegistration();
+
+ verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
+ }
+
+ @Test
public void registerAuthenticators_virtualAlwaysWhenNoOther() throws Exception {
initServiceWith(NAME_VIRTUAL);
@@ -201,6 +262,28 @@
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
+ mFingerprintSensorConfigurations =
+ new FingerprintSensorConfigurations(true);
+ mFingerprintSensorConfigurations.addAidlSensors(new String[]{NAME_VIRTUAL},
+ (name) -> {
+ if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_DEFAULT)) {
+ return mDefaultFingerprintDaemon;
+ } else if (name.equals(IFingerprint.DESCRIPTOR + "/" + NAME_VIRTUAL)) {
+ return mVirtualFingerprintDaemon;
+ }
+ return null;
+ });
+ initServiceWith(NAME_VIRTUAL);
+
+ mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+ waitForRegistration();
+
+ verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
+ }
+
private void waitForRegistration() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mService.mServiceWrapper.addAuthenticatorsRegisteredCallback(
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 4cfb83f..bf5986c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -16,6 +16,9 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
+import static android.os.UserHandle.USER_NULL;
+import static android.os.UserHandle.USER_SYSTEM;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
@@ -34,17 +37,20 @@
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.SensorLocation;
import android.hardware.biometrics.fingerprint.SensorProps;
+import android.hardware.fingerprint.HidlFingerprintSensorConfig;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.HalClientMonitor;
@@ -52,6 +58,7 @@
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -64,6 +71,10 @@
private static final String TAG = "FingerprintProviderTest";
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Mock
private Context mContext;
@Mock
@@ -115,7 +126,8 @@
mFingerprintProvider = new FingerprintProvider(mContext,
mBiometricStateCallback, mAuthenticationStateListeners, mSensorProps, TAG,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher, mBiometricContext,
- mDaemon);
+ mDaemon, false /* resetLockoutRequiresHardwareAuthToken */,
+ true /* testHalEnabled */);
}
@Test
@@ -123,17 +135,42 @@
waitForIdle();
for (SensorProps prop : mSensorProps) {
- final BiometricScheduler scheduler =
- mFingerprintProvider.mFingerprintSensors.get(prop.commonProps.sensorId)
- .getScheduler();
- BaseClientMonitor currentClient = scheduler.getCurrentClient();
-
- assertThat(currentClient).isInstanceOf(FingerprintInternalCleanupClient.class);
- assertThat(currentClient.getSensorId()).isEqualTo(prop.commonProps.sensorId);
- assertThat(currentClient.getTargetUserId()).isEqualTo(UserHandle.USER_SYSTEM);
+ final Sensor sensor =
+ mFingerprintProvider.mFingerprintSensors.get(prop.commonProps.sensorId);
+ assertThat(sensor.getLazySession().get().getUserId()).isEqualTo(USER_SYSTEM);
}
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
+ public void testAddingHidlSensors() {
+ when(mResources.getIntArray(anyInt())).thenReturn(new int[]{});
+ when(mResources.getBoolean(anyInt())).thenReturn(false);
+
+ final int fingerprintId = 0;
+ final int fingerprintStrength = 15;
+ final String config = String.format("%d:2:%d", fingerprintId, fingerprintStrength);
+ final HidlFingerprintSensorConfig fingerprintSensorConfig =
+ new HidlFingerprintSensorConfig();
+ fingerprintSensorConfig.parse(config, mContext);
+ HidlFingerprintSensorConfig[] hidlFingerprintSensorConfigs =
+ new HidlFingerprintSensorConfig[]{fingerprintSensorConfig};
+ mFingerprintProvider = new FingerprintProvider(mContext,
+ mBiometricStateCallback, mAuthenticationStateListeners,
+ hidlFingerprintSensorConfigs, TAG, mLockoutResetDispatcher,
+ mGestureAvailabilityDispatcher, mBiometricContext, mDaemon,
+ false /* resetLockoutRequiresHardwareAuthToken */,
+ true /* testHalEnabled */);
+
+ assertThat(mFingerprintProvider.mFingerprintSensors.get(fingerprintId)
+ .getLazySession().get().getUserId()).isEqualTo(USER_NULL);
+
+ waitForIdle();
+
+ assertThat(mFingerprintProvider.mFingerprintSensors.get(fingerprintId)
+ .getLazySession().get().getUserId()).isEqualTo(USER_SYSTEM);
+ }
+
@SuppressWarnings("rawtypes")
@Test
public void halServiceDied_resetsAllSchedulers() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index 4102600..126a05e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -96,7 +96,7 @@
private final TestLooper mLooper = new TestLooper();
private final LockoutCache mLockoutCache = new LockoutCache();
- private UserAwareBiometricScheduler mScheduler;
+ private BiometricScheduler mScheduler;
private AidlResponseHandler mHalCallback;
@Before
@@ -164,7 +164,8 @@
final Sensor sensor = new Sensor("SensorTest", mFingerprintProvider, mContext,
null /* handler */, internalProp, mLockoutResetDispatcher,
mGestureAvailabilityDispatcher, mBiometricContext, mCurrentSession);
- mScheduler = (UserAwareBiometricScheduler) sensor.getScheduler();
+ sensor.init(mGestureAvailabilityDispatcher, mLockoutResetDispatcher);
+ mScheduler = sensor.getScheduler();
sensor.mCurrentSession = new AidlSession(0, mock(ISession.class),
USER_ID, mHalCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java
new file mode 100644
index 0000000..89a4961
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint.hidl;
+
+import static android.hardware.fingerprint.FingerprintManager.ENROLL_ENROLL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.hardware.biometrics.IBiometricService;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.HidlFingerprintSensorConfig;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+
+import androidx.annotation.NonNull;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
+import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.BiometricUtils;
+import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.StopUserClient;
+import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
+import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
+import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
+import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class HidlToAidlSensorAdapterTest {
+
+ private static final String TAG = "HidlToAidlSensorAdapterTest";
+ private static final int USER_ID = 2;
+ private static final int SENSOR_ID = 4;
+ private static final byte[] HAT = new byte[69];
+
+ @Rule
+ public final MockitoRule mMockito = MockitoJUnit.rule();
+
+ @Mock
+ private IBiometricService mBiometricService;
+ @Mock
+ private LockoutResetDispatcher mLockoutResetDispatcherForSensor;
+ @Mock
+ private LockoutResetDispatcher mLockoutResetDispatcherForClient;
+ @Mock
+ private BiometricLogger mLogger;
+ @Mock
+ private BiometricContext mBiometricContext;
+ @Mock
+ private AuthSessionCoordinator mAuthSessionCoordinator;
+ @Mock
+ private FingerprintProvider mFingerprintProvider;
+ @Mock
+ private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
+ @Mock
+ private Runnable mInternalCleanupRunnable;
+ @Mock
+ private AlarmManager mAlarmManager;
+ @Mock
+ private IBiometricsFingerprint mDaemon;
+ @Mock
+ private AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
+ @Mock
+ private BiometricUtils<Fingerprint> mBiometricUtils;
+ @Mock
+ private AuthenticationStateListeners mAuthenticationStateListeners;
+
+ private final TestLooper mLooper = new TestLooper();
+ private HidlToAidlSensorAdapter mHidlToAidlSensorAdapter;
+ private final TestableContext mContext = new TestableContext(
+ ApplicationProvider.getApplicationContext());
+
+ private final UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback =
+ new UserAwareBiometricScheduler.UserSwitchCallback() {
+ @NonNull
+ @Override
+ public StopUserClient<?> getStopUserClient(int userId) {
+ return new StopUserClient<IBiometricsFingerprint>(mContext,
+ mHidlToAidlSensorAdapter::getIBiometricsFingerprint, null, USER_ID,
+ SENSOR_ID, mLogger, mBiometricContext, () -> {}) {
+ @Override
+ protected void startHalOperation() {
+ getCallback().onClientFinished(this, true /* success */);
+ }
+
+ @Override
+ public void unableToStart() {}
+ };
+ }
+
+ @NonNull
+ @Override
+ public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+ return new StartUserClient<IBiometricsFingerprint, AidlSession>(mContext,
+ mHidlToAidlSensorAdapter::getIBiometricsFingerprint, null,
+ USER_ID, SENSOR_ID,
+ mLogger, mBiometricContext,
+ (newUserId1, newUser, halInterfaceVersion) ->
+ mHidlToAidlSensorAdapter.handleUserChanged(newUserId1)) {
+ @Override
+ public void start(@NonNull ClientMonitorCallback callback) {
+ super.start(callback);
+ startHalOperation();
+ }
+
+ @Override
+ protected void startHalOperation() {
+ mUserStartedCallback.onUserStarted(USER_ID, null, 0);
+ getCallback().onClientFinished(this, true /* success */);
+ }
+
+ @Override
+ public void unableToStart() {}
+ };
+ }
+ };;
+
+ @Before
+ public void setUp() throws RemoteException {
+ when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
+ doAnswer((answer) -> {
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback()
+ .onEnrollmentProgress(1 /* enrollmentId */, 0 /* remaining */);
+ return null;
+ }).when(mDaemon).enroll(any(), anyInt(), anyInt());
+
+ mContext.addMockSystemService(AlarmManager.class, mAlarmManager);
+ mContext.getOrCreateTestableResources();
+
+ final String config = String.format("%d:2:15", SENSOR_ID);
+ final UserAwareBiometricScheduler scheduler = new UserAwareBiometricScheduler(TAG,
+ new Handler(mLooper.getLooper()),
+ BiometricScheduler.SENSOR_TYPE_FP_OTHER,
+ null /* gestureAvailabilityDispatcher */,
+ mBiometricService,
+ () -> USER_ID,
+ mUserSwitchCallback);
+ final HidlFingerprintSensorConfig fingerprintSensorConfig =
+ new HidlFingerprintSensorConfig();
+ fingerprintSensorConfig.parse(config, mContext);
+ mHidlToAidlSensorAdapter = new HidlToAidlSensorAdapter(TAG,
+ mFingerprintProvider, mContext, new Handler(mLooper.getLooper()),
+ fingerprintSensorConfig, mLockoutResetDispatcherForSensor,
+ mGestureAvailabilityDispatcher, mBiometricContext,
+ false /* resetLockoutRequiresHardwareAuthToken */,
+ mInternalCleanupRunnable, mAuthSessionCoordinator, mDaemon,
+ mAidlResponseHandlerCallback);
+ mHidlToAidlSensorAdapter.init(mGestureAvailabilityDispatcher,
+ mLockoutResetDispatcherForSensor);
+ mHidlToAidlSensorAdapter.setScheduler(scheduler);
+ mHidlToAidlSensorAdapter.handleUserChanged(USER_ID);
+ }
+
+ @Test
+ public void lockoutTimedResetViaClient() {
+ setLockoutTimed();
+
+ mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(
+ new FingerprintResetLockoutClient(mContext,
+ mHidlToAidlSensorAdapter.getLazySession(),
+ USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT,
+ mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */),
+ mLockoutResetDispatcherForClient, 0 /* biometricStrength */));
+ mLooper.dispatchAll();
+
+ verify(mAlarmManager).setExact(anyInt(), anyLong(), any());
+ verify(mLockoutResetDispatcherForClient).notifyLockoutResetCallbacks(SENSOR_ID);
+ verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(SENSOR_ID);
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void lockoutTimedResetViaCallback() {
+ setLockoutTimed();
+
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback().onLockoutCleared();
+ mLooper.dispatchAll();
+
+ verify(mLockoutResetDispatcherForSensor, times(2)).notifyLockoutResetCallbacks(eq(
+ SENSOR_ID));
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void lockoutPermanentResetViaCallback() {
+ setLockoutPermanent();
+
+ mHidlToAidlSensorAdapter.getLazySession().get().getHalSessionCallback().onLockoutCleared();
+ mLooper.dispatchAll();
+
+ verify(mLockoutResetDispatcherForSensor, times(2)).notifyLockoutResetCallbacks(eq(
+ SENSOR_ID));
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void lockoutPermanentResetViaClient() {
+ setLockoutPermanent();
+
+ mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(
+ new FingerprintResetLockoutClient(mContext,
+ mHidlToAidlSensorAdapter.getLazySession(),
+ USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT,
+ mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */),
+ mLockoutResetDispatcherForClient, 0 /* biometricStrength */));
+ mLooper.dispatchAll();
+
+ verify(mAlarmManager, atLeast(1)).setExact(anyInt(), anyLong(), any());
+ verify(mLockoutResetDispatcherForClient).notifyLockoutResetCallbacks(SENSOR_ID);
+ verify(mLockoutResetDispatcherForSensor).notifyLockoutResetCallbacks(SENSOR_ID);
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_NONE);
+ verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), anyInt(), anyLong());
+ }
+
+ @Test
+ public void verifyOnEnrollSuccessCallback() {
+ mHidlToAidlSensorAdapter.getScheduler().scheduleClientMonitor(new FingerprintEnrollClient(
+ mContext, mHidlToAidlSensorAdapter.getLazySession(), null /* token */,
+ 1 /* requestId */, null /* listener */, USER_ID, HAT, TAG, mBiometricUtils,
+ SENSOR_ID, mLogger, mBiometricContext,
+ mHidlToAidlSensorAdapter.getSensorProperties(), null, null,
+ mAuthenticationStateListeners, 5 /* maxTemplatesPerUser */, ENROLL_ENROLL));
+ mLooper.dispatchAll();
+
+ verify(mAidlResponseHandlerCallback).onEnrollSuccess();
+ }
+
+ private void setLockoutPermanent() {
+ for (int i = 0; i < 20; i++) {
+ mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .addFailedAttemptForUser(USER_ID);
+ }
+
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_PERMANENT);
+ }
+
+ private void setLockoutTimed() {
+ for (int i = 0; i < 5; i++) {
+ mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .addFailedAttemptForUser(USER_ID);
+ }
+
+ assertThat(mHidlToAidlSensorAdapter.getLockoutTracker(false /* forAuth */)
+ .getLockoutModeForUser(USER_ID)).isEqualTo(LockoutTracker.LOCKOUT_TIMED);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapterTest.java
similarity index 79%
rename from services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java
rename to services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapterTest.java
index b78ba82..d723e87 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapterTest.java
@@ -41,7 +41,7 @@
@Presubmit
@SmallTest
-public class AidlToHidlAdapterTest {
+public class HidlToAidlSessionAdapterTest {
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@@ -54,11 +54,11 @@
private final long mChallenge = 100L;
private final int mUserId = 0;
- private AidlToHidlAdapter mAidlToHidlAdapter;
+ private HidlToAidlSessionAdapter mHidlToAidlSessionAdapter;
@Before
public void setUp() {
- mAidlToHidlAdapter = new AidlToHidlAdapter(() -> mSession, mUserId,
+ mHidlToAidlSessionAdapter = new HidlToAidlSessionAdapter(() -> mSession, mUserId,
mAidlResponseHandler);
mHardwareAuthToken.timestamp = new Timestamp();
mHardwareAuthToken.mac = new byte[10];
@@ -67,7 +67,7 @@
@Test
public void testGenerateChallenge() throws RemoteException {
when(mSession.preEnroll()).thenReturn(mChallenge);
- mAidlToHidlAdapter.generateChallenge();
+ mHidlToAidlSessionAdapter.generateChallenge();
verify(mSession).preEnroll();
verify(mAidlResponseHandler).onChallengeGenerated(mChallenge);
@@ -75,7 +75,7 @@
@Test
public void testRevokeChallenge() throws RemoteException {
- mAidlToHidlAdapter.revokeChallenge(mChallenge);
+ mHidlToAidlSessionAdapter.revokeChallenge(mChallenge);
verify(mSession).postEnroll();
verify(mAidlResponseHandler).onChallengeRevoked(0L);
@@ -84,9 +84,9 @@
@Test
public void testEnroll() throws RemoteException {
final ICancellationSignal cancellationSignal =
- mAidlToHidlAdapter.enroll(mHardwareAuthToken);
+ mHidlToAidlSessionAdapter.enroll(mHardwareAuthToken);
- verify(mSession).enroll(any(), anyInt(), eq(AidlToHidlAdapter.ENROLL_TIMEOUT_SEC));
+ verify(mSession).enroll(any(), anyInt(), eq(HidlToAidlSessionAdapter.ENROLL_TIMEOUT_SEC));
cancellationSignal.cancel();
@@ -96,7 +96,8 @@
@Test
public void testAuthenticate() throws RemoteException {
final int operationId = 2;
- final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId);
+ final ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter.authenticate(
+ operationId);
verify(mSession).authenticate(operationId, mUserId);
@@ -107,7 +108,8 @@
@Test
public void testDetectInteraction() throws RemoteException {
- final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction();
+ final ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter
+ .detectInteraction();
verify(mSession).authenticate(0 /* operationId */, mUserId);
@@ -118,7 +120,7 @@
@Test
public void testEnumerateEnrollments() throws RemoteException {
- mAidlToHidlAdapter.enumerateEnrollments();
+ mHidlToAidlSessionAdapter.enumerateEnrollments();
verify(mSession).enumerate();
}
@@ -126,7 +128,7 @@
@Test
public void testRemoveEnrollment() throws RemoteException {
final int[] enrollmentIds = new int[]{1};
- mAidlToHidlAdapter.removeEnrollments(enrollmentIds);
+ mHidlToAidlSessionAdapter.removeEnrollments(enrollmentIds);
verify(mSession).remove(mUserId, enrollmentIds[0]);
}
@@ -134,14 +136,14 @@
@Test
public void testRemoveMultipleEnrollments() throws RemoteException {
final int[] enrollmentIds = new int[]{1, 2};
- mAidlToHidlAdapter.removeEnrollments(enrollmentIds);
+ mHidlToAidlSessionAdapter.removeEnrollments(enrollmentIds);
verify(mSession).remove(mUserId, 0);
}
@Test
public void testResetLockout() throws RemoteException {
- mAidlToHidlAdapter.resetLockout(mHardwareAuthToken);
+ mHidlToAidlSessionAdapter.resetLockout(mHardwareAuthToken);
verify(mAidlResponseHandler).onLockoutCleared();
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
index b4281d63c..101ea6d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -183,6 +183,13 @@
}
@Test
+ public void testIsUserInitialized_NonExistentUserReturnsFalse() {
+ int nonExistentUserId = UserHandle.USER_NULL;
+ assertThat(mUserManagerService.isUserInitialized(nonExistentUserId))
+ .isFalse();
+ }
+
+ @Test
public void testSetUserRestrictionWithIncorrectID() throws Exception {
int incorrectId = 1;
while (mUserManagerService.userExists(incorrectId)) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
index 260ee396..30843d2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
@@ -245,8 +245,8 @@
}
@Test
- @TestParameters({"{origin: ORIGIN_USER}", "{origin: ORIGIN_INIT}", "{origin: ORIGIN_INIT_USER}",
- "{origin: ORIGIN_SYSTEM_OR_SYSTEMUI}"})
+ @TestParameters({"{origin: ORIGIN_USER}", "{origin: ORIGIN_INIT}",
+ "{origin: ORIGIN_INIT_USER}"})
public void apply_nightModeWithScreenOn_appliedImmediatelyBasedOnOrigin(ChangeOrigin origin) {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
@@ -263,7 +263,7 @@
@Test
@TestParameters({"{origin: ORIGIN_APP}", "{origin: ORIGIN_RESTORE_BACKUP}",
- "{origin: ORIGIN_UNKNOWN}"})
+ "{origin: ORIGIN_SYSTEM_OR_SYSTEMUI}", "{origin: ORIGIN_UNKNOWN}"})
public void apply_nightModeWithScreenOn_willBeAppliedLaterBasedOnOrigin(ChangeOrigin origin) {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
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 56c75b5..9a13595 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -80,6 +80,9 @@
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS;
+import static android.service.notification.Condition.SOURCE_CONTEXT;
+import static android.service.notification.Condition.SOURCE_USER_ACTION;
+import static android.service.notification.Condition.STATE_TRUE;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING;
@@ -224,6 +227,7 @@
import android.provider.MediaStore;
import android.provider.Settings;
import android.service.notification.Adjustment;
+import android.service.notification.Condition;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.DeviceEffectsApplier;
import android.service.notification.INotificationListener;
@@ -342,6 +346,11 @@
private static final String SCHEME_TIMEOUT = "timeout";
private static final String REDACTED_TEXT = "redacted text";
+ private static final AutomaticZenRule SOME_ZEN_RULE =
+ new AutomaticZenRule.Builder("rule", Uri.parse("uri"))
+ .setOwner(new ComponentName("pkg", "cls"))
+ .build();
+
private final int mUid = Binder.getCallingUid();
private final @UserIdInt int mUserId = UserHandle.getUserId(mUid);
@@ -9048,7 +9057,7 @@
zenPolicy, NotificationManager.INTERRUPTION_FILTER_NONE, isEnabled);
try {
- mBinderService.addAutomaticZenRule(rule, mContext.getPackageName());
+ mBinderService.addAutomaticZenRule(rule, mContext.getPackageName(), false);
fail("Zen policy only applies to priority only mode");
} catch (IllegalArgumentException e) {
// yay
@@ -9056,11 +9065,11 @@
rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
- mBinderService.addAutomaticZenRule(rule, mContext.getPackageName());
+ mBinderService.addAutomaticZenRule(rule, mContext.getPackageName(), false);
rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
null, NotificationManager.INTERRUPTION_FILTER_NONE, isEnabled);
- mBinderService.addAutomaticZenRule(rule, mContext.getPackageName());
+ mBinderService.addAutomaticZenRule(rule, mContext.getPackageName(), false);
}
@Test
@@ -9075,7 +9084,7 @@
boolean isEnabled = true;
AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
- mBinderService.addAutomaticZenRule(rule, "com.android.settings");
+ mBinderService.addAutomaticZenRule(rule, "com.android.settings", false);
// verify that zen mode helper gets passed in a package name of "android"
verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule),
@@ -9097,7 +9106,7 @@
boolean isEnabled = true;
AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
- mBinderService.addAutomaticZenRule(rule, "com.android.settings");
+ mBinderService.addAutomaticZenRule(rule, "com.android.settings", false);
// verify that zen mode helper gets passed in a package name of "android"
verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule),
@@ -9117,7 +9126,7 @@
boolean isEnabled = true;
AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
- mBinderService.addAutomaticZenRule(rule, "another.package");
+ mBinderService.addAutomaticZenRule(rule, "another.package", false);
// verify that zen mode helper gets passed in the package name from the arg, not the owner
verify(mockZenModeHelper).addAutomaticZenRule(
@@ -9128,10 +9137,8 @@
@Test
@EnableFlags(android.app.Flags.FLAG_MODES_API)
public void testAddAutomaticZenRule_typeManagedCanBeUsedByDeviceOwners() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
mService.setCallerIsNormalPackage();
- mService.setZenHelper(mock(ZenModeHelper.class));
- when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
- .thenReturn(true);
AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri"))
.setType(AutomaticZenRule.TYPE_MANAGED)
@@ -9139,8 +9146,9 @@
.build();
when(mDevicePolicyManager.isActiveDeviceOwner(anyInt())).thenReturn(true);
- mBinderService.addAutomaticZenRule(rule, "pkg");
- // No exception!
+ mBinderService.addAutomaticZenRule(rule, "pkg", /* fromUser= */ false);
+
+ verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(rule), anyInt(), any(), anyInt());
}
@Test
@@ -9158,7 +9166,144 @@
when(mDevicePolicyManager.isActiveDeviceOwner(anyInt())).thenReturn(false);
assertThrows(IllegalArgumentException.class,
- () -> mBinderService.addAutomaticZenRule(rule, "pkg"));
+ () -> mBinderService.addAutomaticZenRule(rule, "pkg", /* fromUser= */ false));
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void addAutomaticZenRule_fromUser_mappedToOriginUser() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.isSystemUid = true;
+
+ mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ true);
+
+ verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(SOME_ZEN_RULE),
+ eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyString(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void addAutomaticZenRule_fromSystemNotUser_mappedToOriginSystem() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.isSystemUid = true;
+
+ mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ false);
+
+ verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(SOME_ZEN_RULE),
+ eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void addAutomaticZenRule_fromApp_mappedToOriginApp() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.setCallerIsNormalPackage();
+
+ mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ false);
+
+ verify(zenModeHelper).addAutomaticZenRule(eq("pkg"), eq(SOME_ZEN_RULE),
+ eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void addAutomaticZenRule_fromAppFromUser_blocked() throws Exception {
+ setUpMockZenTest();
+ mService.setCallerIsNormalPackage();
+
+ assertThrows(SecurityException.class, () ->
+ mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ true));
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void updateAutomaticZenRule_fromUserFromSystem_allowed() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.isSystemUid = true;
+
+ mBinderService.updateAutomaticZenRule("id", SOME_ZEN_RULE, /* fromUser= */ true);
+
+ verify(zenModeHelper).updateAutomaticZenRule(eq("id"), eq(SOME_ZEN_RULE),
+ eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyString(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void updateAutomaticZenRule_fromUserFromApp_blocked() throws Exception {
+ setUpMockZenTest();
+ mService.setCallerIsNormalPackage();
+
+ assertThrows(SecurityException.class, () ->
+ mBinderService.addAutomaticZenRule(SOME_ZEN_RULE, "pkg", /* fromUser= */ true));
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void removeAutomaticZenRule_fromUserFromSystem_allowed() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.isSystemUid = true;
+
+ mBinderService.removeAutomaticZenRule("id", /* fromUser= */ true);
+
+ verify(zenModeHelper).removeAutomaticZenRule(eq("id"),
+ eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyString(), anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void removeAutomaticZenRule_fromUserFromApp_blocked() throws Exception {
+ setUpMockZenTest();
+ mService.setCallerIsNormalPackage();
+
+ assertThrows(SecurityException.class, () ->
+ mBinderService.removeAutomaticZenRule("id", /* fromUser= */ true));
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void setAutomaticZenRuleState_fromUserMatchesConditionSource_okay() throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.setCallerIsNormalPackage();
+
+ Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
+ SOURCE_CONTEXT);
+ mBinderService.setAutomaticZenRuleState("id", withSourceContext, /* fromUser= */ false);
+ verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceContext),
+ eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyInt());
+
+ Condition withSourceUser = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
+ SOURCE_USER_ACTION);
+ mBinderService.setAutomaticZenRuleState("id", withSourceUser, /* fromUser= */ true);
+ verify(zenModeHelper).setAutomaticZenRuleState(eq("id"), eq(withSourceUser),
+ eq(ZenModeConfig.UPDATE_ORIGIN_USER), anyInt());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void setAutomaticZenRuleState_fromUserDoesNotMatchConditionSource_blocked()
+ throws Exception {
+ ZenModeHelper zenModeHelper = setUpMockZenTest();
+ mService.setCallerIsNormalPackage();
+
+ Condition withSourceContext = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
+ SOURCE_CONTEXT);
+ assertThrows(IllegalArgumentException.class,
+ () -> mBinderService.setAutomaticZenRuleState("id", withSourceContext,
+ /* fromUser= */ true));
+
+ Condition withSourceUser = new Condition(Uri.parse("uri"), "summary", STATE_TRUE,
+ SOURCE_USER_ACTION);
+ assertThrows(IllegalArgumentException.class,
+ () -> mBinderService.setAutomaticZenRuleState("id", withSourceUser,
+ /* fromUser= */ false));
+ }
+
+ private ZenModeHelper setUpMockZenTest() {
+ ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
+ mService.setZenHelper(zenModeHelper);
+ when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+ .thenReturn(true);
+ return zenModeHelper;
}
@Test
@@ -9184,7 +9329,7 @@
});
mService.getBinderService().setZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS, null,
- "testing!");
+ "testing!", false);
waitForIdle();
InOrder inOrder = inOrder(mContext);
@@ -13441,9 +13586,10 @@
.thenReturn(true);
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
- mBinderService.setNotificationPolicy("package", policy);
+ mBinderService.setNotificationPolicy("package", policy, false);
- verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy));
+ verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy),
+ eq(ZenModeConfig.UPDATE_ORIGIN_APP));
}
@Test
@@ -13457,7 +13603,7 @@
mService.isSystemUid = true;
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
- mBinderService.setNotificationPolicy("package", policy);
+ mBinderService.setNotificationPolicy("package", policy, false);
verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
}
@@ -13479,7 +13625,7 @@
.build()));
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
- mBinderService.setNotificationPolicy("package", policy);
+ mBinderService.setNotificationPolicy("package", policy, false);
verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
}
@@ -13495,7 +13641,7 @@
.thenReturn(true);
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
- mBinderService.setNotificationPolicy("package", policy);
+ mBinderService.setNotificationPolicy("package", policy, false);
verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
}
@@ -13525,7 +13671,7 @@
when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
.thenReturn(true);
- mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY);
+ mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY, false);
verify(zenHelper).applyGlobalZenModeAsImplicitZenRule(eq("package"), anyInt(),
eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS));
@@ -13542,7 +13688,7 @@
.thenReturn(true);
mService.isSystemUid = true;
- mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY);
+ mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY, false);
verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), eq("package"),
@@ -13565,7 +13711,7 @@
.setDeviceProfile(AssociationRequest.DEVICE_PROFILE_WATCH)
.build()));
- mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY);
+ mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY, false);
verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), eq("package"), anyInt());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 1aea56c..bc63c29e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -112,6 +112,7 @@
import android.os.Parcel;
import android.os.Process;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.provider.Settings.Global;
@@ -211,7 +212,8 @@
private static final ZenDeviceEffects NO_EFFECTS = new ZenDeviceEffects.Builder().build();
@Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
+ SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
ConditionProviders mConditionProviders;
@Mock NotificationManager mNotificationManager;
@@ -3372,6 +3374,29 @@
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void removeAutomaticZenRule_propagatesOriginToEffectsApplier() {
+ mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+ reset(mDeviceEffectsApplier);
+
+ String ruleId = addRuleWithEffects(new ZenDeviceEffects.Builder()
+ .setShouldSuppressAmbientDisplay(true)
+ .setShouldDimWallpaper(true)
+ .build());
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
+ mTestableLooper.processAllMessages();
+ verify(mDeviceEffectsApplier).apply(any(), eq(UPDATE_ORIGIN_APP));
+
+ // Now delete the (currently active!) rule. For example, assume this is done from settings.
+ mZenModeHelper.removeAutomaticZenRule(ruleId, UPDATE_ORIGIN_USER, "remove",
+ Process.SYSTEM_UID);
+ mTestableLooper.processAllMessages();
+
+ verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_USER));
+ }
+
+ @Test
public void testDeviceEffects_applied() {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
@@ -3511,8 +3536,8 @@
AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
.setDeviceEffects(effects)
.build();
- return mZenModeHelper.addAutomaticZenRule("pkg", rule, UPDATE_ORIGIN_APP, "",
- CUSTOM_PKG_UID);
+ return mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
+ UPDATE_ORIGIN_APP, "reasons", CUSTOM_PKG_UID);
}
@Test
@@ -3619,7 +3644,8 @@
Policy policy = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, policy);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, policy,
+ UPDATE_ORIGIN_APP);
ZenPolicy expectedZenPolicy = new ZenPolicy.Builder()
.disallowAllSounds()
@@ -3643,13 +3669,14 @@
PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- original);
+ original, UPDATE_ORIGIN_APP);
// Change priorityCallSenders: contacts -> starred.
Policy updated = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
PRIORITY_SENDERS_STARRED, PRIORITY_SENDERS_STARRED,
Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, updated);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, updated,
+ UPDATE_ORIGIN_APP);
ZenPolicy expectedZenPolicy = new ZenPolicy.Builder()
.disallowAllSounds()
@@ -3671,7 +3698,7 @@
withoutWtfCrash(
() -> mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME,
- CUSTOM_PKG_UID, new Policy(0, 0, 0)));
+ CUSTOM_PKG_UID, new Policy(0, 0, 0), UPDATE_ORIGIN_APP));
assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty();
}
@@ -3684,7 +3711,7 @@
Policy.getAllSuppressedVisualEffects(), STATE_FALSE,
CONVERSATION_SENDERS_IMPORTANT);
mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- writtenPolicy);
+ writtenPolicy, UPDATE_ORIGIN_APP);
Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
CUSTOM_PKG_NAME);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index c6796dc..985be42 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -19,6 +19,7 @@
import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION;
+import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
@@ -673,6 +674,47 @@
}
@Test
+ @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER})
+ public void testOverrideOrientationIfNeeded_fullscreenOverrideEnabled_returnsUser()
+ throws Exception {
+ mDisplayContent.setIgnoreOrientationRequest(true);
+ assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER})
+ public void testOverrideOrientationIfNeeded_fullscreenOverrideEnabled_optOut_returnsUnchanged()
+ throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE, /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+ mDisplayContent.setIgnoreOrientationRequest(true);
+
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER})
+ public void testOverrideOrientationIfNeeded_fullscreenOverrideEnabled_returnsUnchanged()
+ throws Exception {
+ mDisplayContent.setIgnoreOrientationRequest(false);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER})
+ public void testOverrideOrientationIfNeeded_fullscreenAndUserOverrideEnabled_returnsUnchanged()
+ throws Exception {
+ prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+ }
+
+ @Test
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT})
public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsPortrait()
throws Exception {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index f99b489..8bf4833 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -499,11 +499,16 @@
public void testAddWindowWithSubWindowTypeByWindowContext() {
spyOn(mWm.mWindowContextListenerController);
- final WindowToken windowToken = createTestWindowToken(TYPE_INPUT_METHOD, mDefaultDisplay);
- final Session session = getTestSession();
+ final WindowState parentWin = createWindow(null, TYPE_INPUT_METHOD, "ime");
+ final IBinder parentToken = parentWin.mToken.token;
+ parentWin.mAttrs.token = parentToken;
+ mWm.mWindowMap.put(parentToken, parentWin);
+ final Session session = parentWin.mSession;
+ session.onWindowAdded(parentWin);
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
TYPE_APPLICATION_ATTACHED_DIALOG);
- params.token = windowToken.token;
+ params.token = parentToken;
+ params.setTitle("attached-dialog");
final IBinder windowContextToken = new Binder();
params.setWindowContextToken(windowContextToken);
doReturn(true).when(mWm.mWindowContextListenerController)
@@ -517,6 +522,12 @@
verify(mWm.mWindowContextListenerController, never()).registerWindowContainerListener(any(),
any(), any(), anyInt(), any(), anyBoolean());
+
+ assertTrue(parentWin.hasChild());
+ assertTrue(parentWin.isAttached());
+ session.binderDied();
+ assertFalse(parentWin.hasChild());
+ assertFalse(parentWin.isAttached());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index df4af11..616a23e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -294,7 +294,7 @@
*/
static void suppressInsetsAnimation(InsetsControlTarget target) {
spyOn(target);
- Mockito.doNothing().when(target).notifyInsetsControlChanged();
+ Mockito.doNothing().when(target).notifyInsetsControlChanged(anyInt());
}
@After
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 72db7fe..ccd4ce0 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2395,7 +2395,19 @@
return null;
}
- return queryEventsHelper(UserHandle.getCallingUserId(), query.getBeginTimeMillis(),
+ final int callingUserId = UserHandle.getCallingUserId();
+ int userId = query.getUserId();
+ if (userId == UserHandle.USER_NULL) {
+ // Convert userId to actual user Id if not specified in the query object.
+ userId = callingUserId;
+ }
+ if (userId != callingUserId) {
+ getContext().enforceCallingPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "No permission to query usage stats for user " + userId);
+ }
+
+ return queryEventsHelper(userId, query.getBeginTimeMillis(),
query.getEndTimeMillis(), callingPackage, query.getEventTypeFilter());
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
index 4720d27..f9fa9b7 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
@@ -106,96 +106,104 @@
@Override
public void onAttentionGained() {
Slog.v(TAG, "BinderCallback#onAttentionGained");
- mEgressingData = true;
- if (mAttentionListener == null) {
- return;
- }
- try {
- mAttentionListener.onAttentionGained();
- } catch (RemoteException e) {
- Slog.e(TAG, "Error delivering attention gained event.", e);
- try {
- callback.onVisualQueryDetectionServiceFailure(
- new VisualQueryDetectionServiceFailure(
- ERROR_CODE_ILLEGAL_ATTENTION_STATE,
- "Attention listener failed to switch to GAINED state."));
- } catch (RemoteException ex) {
- Slog.v(TAG, "Fail to call onVisualQueryDetectionServiceFailure");
+ synchronized (mLock) {
+ mEgressingData = true;
+ if (mAttentionListener == null) {
+ return;
}
- return;
+ try {
+ mAttentionListener.onAttentionGained();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error delivering attention gained event.", e);
+ try {
+ callback.onVisualQueryDetectionServiceFailure(
+ new VisualQueryDetectionServiceFailure(
+ ERROR_CODE_ILLEGAL_ATTENTION_STATE,
+ "Attention listener fails to switch to GAINED state."));
+ } catch (RemoteException ex) {
+ Slog.v(TAG, "Fail to call onVisualQueryDetectionServiceFailure");
+ }
+ }
}
}
@Override
public void onAttentionLost() {
Slog.v(TAG, "BinderCallback#onAttentionLost");
- mEgressingData = false;
- if (mAttentionListener == null) {
- return;
- }
- try {
- mAttentionListener.onAttentionLost();
- } catch (RemoteException e) {
- Slog.e(TAG, "Error delivering attention lost event.", e);
- try {
- callback.onVisualQueryDetectionServiceFailure(
- new VisualQueryDetectionServiceFailure(
- ERROR_CODE_ILLEGAL_ATTENTION_STATE,
- "Attention listener failed to switch to LOST state."));
- } catch (RemoteException ex) {
- Slog.v(TAG, "Fail to call onVisualQueryDetectionServiceFailure");
+ synchronized (mLock) {
+ mEgressingData = false;
+ if (mAttentionListener == null) {
+ return;
}
- return;
+ try {
+ mAttentionListener.onAttentionLost();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error delivering attention lost event.", e);
+ try {
+ callback.onVisualQueryDetectionServiceFailure(
+ new VisualQueryDetectionServiceFailure(
+ ERROR_CODE_ILLEGAL_ATTENTION_STATE,
+ "Attention listener fails to switch to LOST state."));
+ } catch (RemoteException ex) {
+ Slog.v(TAG, "Fail to call onVisualQueryDetectionServiceFailure");
+ }
+ }
}
}
@Override
public void onQueryDetected(@NonNull String partialQuery) throws RemoteException {
- Objects.requireNonNull(partialQuery);
Slog.v(TAG, "BinderCallback#onQueryDetected");
- if (!mEgressingData) {
- Slog.v(TAG, "Query should not be egressed within the unattention state.");
- callback.onVisualQueryDetectionServiceFailure(
- new VisualQueryDetectionServiceFailure(
- ERROR_CODE_ILLEGAL_STREAMING_STATE,
- "Cannot stream queries without attention signals."));
- return;
+ synchronized (mLock) {
+ Objects.requireNonNull(partialQuery);
+ if (!mEgressingData) {
+ Slog.v(TAG, "Query should not be egressed within the unattention state.");
+ callback.onVisualQueryDetectionServiceFailure(
+ new VisualQueryDetectionServiceFailure(
+ ERROR_CODE_ILLEGAL_STREAMING_STATE,
+ "Cannot stream queries without attention signals."));
+ return;
+ }
+ mQueryStreaming = true;
+ callback.onQueryDetected(partialQuery);
+ Slog.i(TAG, "Egressed from visual query detection process.");
}
- mQueryStreaming = true;
- callback.onQueryDetected(partialQuery);
- Slog.i(TAG, "Egressed from visual query detection process.");
}
@Override
public void onQueryFinished() throws RemoteException {
Slog.v(TAG, "BinderCallback#onQueryFinished");
- if (!mQueryStreaming) {
- Slog.v(TAG, "Query streaming state signal FINISHED is block since there is"
- + " no active query being streamed.");
- callback.onVisualQueryDetectionServiceFailure(
- new VisualQueryDetectionServiceFailure(
- ERROR_CODE_ILLEGAL_STREAMING_STATE,
- "Cannot send FINISHED signal with no query streamed."));
- return;
+ synchronized (mLock) {
+ if (!mQueryStreaming) {
+ Slog.v(TAG, "Query streaming state signal FINISHED is block since there is"
+ + " no active query being streamed.");
+ callback.onVisualQueryDetectionServiceFailure(
+ new VisualQueryDetectionServiceFailure(
+ ERROR_CODE_ILLEGAL_STREAMING_STATE,
+ "Cannot send FINISHED signal with no query streamed."));
+ return;
+ }
+ callback.onQueryFinished();
+ mQueryStreaming = false;
}
- callback.onQueryFinished();
- mQueryStreaming = false;
}
@Override
public void onQueryRejected() throws RemoteException {
Slog.v(TAG, "BinderCallback#onQueryRejected");
- if (!mQueryStreaming) {
- Slog.v(TAG, "Query streaming state signal REJECTED is block since there is"
- + " no active query being streamed.");
- callback.onVisualQueryDetectionServiceFailure(
- new VisualQueryDetectionServiceFailure(
- ERROR_CODE_ILLEGAL_STREAMING_STATE,
- "Cannot send REJECTED signal with no query streamed."));
- return;
+ synchronized (mLock) {
+ if (!mQueryStreaming) {
+ Slog.v(TAG, "Query streaming state signal REJECTED is block since there is"
+ + " no active query being streamed.");
+ callback.onVisualQueryDetectionServiceFailure(
+ new VisualQueryDetectionServiceFailure(
+ ERROR_CODE_ILLEGAL_STREAMING_STATE,
+ "Cannot send REJECTED signal with no query streamed."));
+ return;
+ }
+ callback.onQueryRejected();
+ mQueryStreaming = false;
}
- callback.onQueryRejected();
- mQueryStreaming = false;
}
};
return mRemoteDetectionService.run(