diff options
207 files changed, 7193 insertions, 2160 deletions
diff --git a/Android.bp b/Android.bp index 91f03e0036a3..bb9304819e80 100644 --- a/Android.bp +++ b/Android.bp @@ -97,6 +97,7 @@ filegroup { // 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 83db4cbb7e43..900c90203f41 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 @@ public class JobSchedulerService extends com.android.server.SystemService /* 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 @@ public class JobSchedulerService extends com.android.server.SystemService /* 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 6449edcd3103..3addf9f98db2 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 @@ public final class JobServiceContext implements ServiceConnection { /* 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 @@ public final class JobServiceContext implements ServiceConnection { 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 12a6f7459bce..d3ed89b347ec 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -35874,19 +35874,19 @@ package android.provider { 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 @@ package android.provider { 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 @@ package android.provider { 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 42daea24593e..aaeba66e4a40 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -369,11 +369,14 @@ package android.app { } 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 94385717d349..b7db5f5d8b0c 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -167,7 +167,7 @@ interface INotificationManager 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 @@ interface INotificationManager @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 @@ interface INotificationManager 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 d23b16d636a7..f76a45b37661 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -1184,14 +1184,20 @@ public class NotificationManager { */ @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 @@ public class NotificationManager { * @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 @@ public class NotificationManager { * @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 @@ public class NotificationManager { * @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 @@ public class NotificationManager { * @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 @@ public class NotificationManager { * @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 @@ public class NotificationManager { */ // 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 @@ public class NotificationManager { */ // 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 8c63d1857865..3cd292392694 100644 --- a/core/java/android/app/usage/UsageEventsQuery.java +++ b/core/java/android/app/usage/UsageEventsQuery.java @@ -19,9 +19,11 @@ package android.app.usage; 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 @@ public final class UsageEventsQuery implements Parcelable { 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 @@ public final class UsageEventsQuery implements Parcelable { int eventTypesLength = in.readInt(); mEventTypes = new int[eventTypesLength]; in.readIntArray(mEventTypes); + mUserId = in.readInt(); } /** @@ -87,6 +92,11 @@ public final class UsageEventsQuery implements Parcelable { return eventTypeSet; } + /** @hide */ + public @UserIdInt int getUserId() { + return mUserId; + } + @Override public int describeContents() { return 0; @@ -98,6 +108,7 @@ public final class UsageEventsQuery implements Parcelable { dest.writeLong(mEndTimeMillis); dest.writeInt(mEventTypes.length); dest.writeIntArray(mEventTypes); + dest.writeInt(mUserId); } @NonNull @@ -126,6 +137,7 @@ public final class UsageEventsQuery implements Parcelable { 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 @@ public final class UsageEventsQuery implements Parcelable { } 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 d13d962015de..12da66515c07 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1393,6 +1393,18 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { 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 57025c25f97b..9a1796fd3d97 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -56,3 +56,11 @@ flag { 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 f71e853a1170..ffd7212a7525 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -715,6 +715,14 @@ public abstract class DisplayManagerInternal { 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 @@ public abstract class DisplayManagerInternal { */ 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 index 94d9a72f0215..26367b3c3dbf 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 000000000000..6ef692f81069 --- /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 000000000000..cab146d0ff54 --- /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 7080133dc597..0096877f548a 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.IFaceServiceReceiver; 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 @@ interface IFaceService { @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/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.aidl index 7be2eaf7b105..ebb05dc88182 100644 --- a/packages/SystemUI/src/com/android/systemui/common/domain/CommonDomainLayerModule.kt +++ b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.aidl @@ -13,15 +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 - -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 -} +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 000000000000..f214494a5d7b --- /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 000000000000..d481153fc642 --- /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 f594c00b0e47..606b171f36ba 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.ISidefpsController; 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 @@ interface IFingerprintService { @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 4bfff16d973f..7d00b80488a9 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -6911,7 +6911,11 @@ public final class ContactsContract { * <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 @@ public final class ContactsContract { * <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 875031fb0cb3..23c8393dadc3 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -960,8 +960,8 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { mKeyphraseMetadata = new KeyphraseMetadata(1, mText, fakeSupportedLocales, AlwaysOnHotwordDetector.RECOGNITION_MODE_VOICE_TRIGGER); } + notifyStateChangedLocked(); } - notifyStateChanged(availability); } /** @@ -1371,8 +1371,8 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { mAvailability = STATE_INVALID; mIsAvailabilityOverriddenByTestApi = false; + notifyStateChangedLocked(); } - notifyStateChanged(STATE_INVALID); super.destroy(); } @@ -1402,8 +1402,6 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { */ // 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 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { // 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 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { + 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 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { } } - 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 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { } } - 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 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { 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 b7d97057a08b..adc54f5b5a8c 100644 --- a/core/java/android/service/voice/VisualQueryDetector.java +++ b/core/java/android/service/voice/VisualQueryDetector.java @@ -94,7 +94,9 @@ public class VisualQueryDetector { */ public void updateState(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) { - mInitializationDelegate.updateState(options, sharedMemory); + synchronized (mInitializationDelegate.getLock()) { + mInitializationDelegate.updateState(options, sharedMemory); + } } @@ -116,18 +118,21 @@ public class VisualQueryDetector { if (DEBUG) { Slog.i(TAG, "#startRecognition"); } - // 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(); + synchronized (mInitializationDelegate.getLock()) { + // check if the detector is active with the initialization delegate + mInitializationDelegate.startRecognition(); + + 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 @@ public class VisualQueryDetector { if (DEBUG) { Slog.i(TAG, "#stopRecognition"); } - // check if the detector is active with the initialization delegate - mInitializationDelegate.startRecognition(); - - try { - mManagerService.stopPerceiving(); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); + synchronized (mInitializationDelegate.getLock()) { + // check if the detector is active with the initialization delegate + mInitializationDelegate.stopRecognition(); + + try { + mManagerService.stopPerceiving(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return true; } - return true; } /** @@ -160,12 +167,16 @@ public class VisualQueryDetector { 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 @@ public class VisualQueryDetector { /** @hide */ void registerOnDestroyListener(Consumer<AbstractDetector> onDestroyListener) { - mInitializationDelegate.registerOnDestroyListener(onDestroyListener); + synchronized (mInitializationDelegate.getLock()) { + mInitializationDelegate.registerOnDestroyListener(onDestroyListener); + } } /** @@ -282,6 +295,15 @@ public class VisualQueryDetector { 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 @@ public class VisualQueryDetector { 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 31d759ea92e6..18080e4478fc 100644 --- a/core/java/android/view/DisplayEventReceiver.java +++ b/core/java/android/view/DisplayEventReceiver.java @@ -287,6 +287,16 @@ public abstract class DisplayEventReceiver { } /** + * 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 @@ public abstract class DisplayEventReceiver { 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 0077dab7ff63..4b5595feb966 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 @@ flag { 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 07beb114898d..52ad49a7cf28 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -15,6 +15,14 @@ flag { } 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 201b23cc172c..31910ac7fce5 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -169,20 +169,12 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { boolean mDecorFitsSystemWindows = true; @VisibleForTesting - public final boolean mDefaultEdgeToEdge; + public final boolean mEdgeToEdgeEnforced; private final ProxyOnBackInvokedDispatcher mProxyOnBackInvokedDispatcher; @@ -396,11 +388,11 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { final int statusBarColor = a.getColor(R.styleable.Window_statusBarColor, statusBarDefaultColor); - mStatusBarColor = statusBarColor == statusBarDefaultColor && !mDefaultEdgeToEdge + mStatusBarColor = statusBarColor == statusBarDefaultColor && !mEdgeToEdgeEnforced ? statusBarCompatibleColor : statusBarColor; } @@ -2574,9 +2566,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 c24d21dda68c..17aad43edb6b 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -653,6 +653,8 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p 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 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p 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 fef8ad7499f7..f007cc5a23bd 100644 --- a/core/jni/android_view_DisplayEventReceiver.cpp +++ b/core/jni/android_view_DisplayEventReceiver.cpp @@ -41,6 +41,7 @@ static struct { jmethodID dispatchHotplugConnectionError; jmethodID dispatchModeChanged; jmethodID dispatchFrameRateOverrides; + jmethodID dispatchHdcpLevelsChanged; struct { jclass clazz; @@ -96,6 +97,8 @@ private: 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 @@ void NativeDisplayEventReceiver::dispatchFrameRateOverrides( 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 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) { 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 c5889ba78159..75cfba02120e 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -429,6 +429,7 @@ message ServiceRecordProto { 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 000000000000..da3a465ade7e --- /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 000000000000..613089c8777d --- /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 6bdc06af2c5c..de55b0759edd 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 @@ public final class PhoneWindowTest { 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 aaddf0e50ddd..742d5a2627eb 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 562d20d05429..7fb959ae4d76 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.annotation.Nullable; 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 @@ public class EnableZenModeDialog { 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 9084aa2dc71a..e83b9bc25799 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.content.res.Resources; 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 @@ public class EditUserPhotoController { 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 @@ public class EditUserPhotoController { onPhotoCropped(data.getData()); return true; } - } return false; } @@ -115,7 +118,13 @@ public class EditUserPhotoController { } 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 5dc1079e8b56..9d9b0a9fe496 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 @@ internal class ElementNode( } 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 @@ private fun IntermediateMeasureScope.place( 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 439dc00d2e8e..d332910c54b1 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 @@ class ElementTest { 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 @@ class ElementTest { 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 @@ class ElementTest { 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 @@ class ElementTest { 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 83af630ab098..3cd65cde274e 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.remember 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 @@ class MovableElementTest { } 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 321cf637824a..ebbd5006be55 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.assertIsDisplayed 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 @@ class SceneTransitionLayoutTest { // 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 @@ class SceneTransitionLayoutTest { 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 e94eff32c30c..8001f418a12c 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.fillMaxSize 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.TestScenes 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 @@ class SharedElementTest { 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 000000000000..e743c7885c14 --- /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 06de2965f716..6d6d57506a3a 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.getValue 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 @@ interface TransitionTestBuilder { @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 @@ fun ComposeContentTestRule.testTransition( 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 0870122594b1..adf4fc6c8ae3 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.statusbar.policy.KeyguardStateController 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 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { } @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 000000000000..9daf1860ebb8 --- /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 000000000000..3f7e0df427c6 --- /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 cf076c557765..a84b9fa32d85 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.ext.junit.runners.AndroidJUnit4 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 @@ import org.junit.runner.RunWith @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() - - 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) + 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() + + 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() - - 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) + 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() + + 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() - - 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) + 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() + + 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() + + assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2) + assertThat(tiles()).hasSize(1) + assertThat(tiles().last()).isEqualTo(TEST_TILE_2) + } + } - val tiles = collectValues(underTest.getTiles(TEST_USER_2)) - underTest.updateWithDefaults(TEST_USER_2, TEST_DEFAULTS_2, true) - runCurrent() + @Test + fun isActiveFollowsPackageManagerAdapter() = + with(kosmos) { + testScope.runTest { + packageManagerAdapterFacade.setIsActive(false) + assertThat(underTest.isTileActive()).isFalse() + + packageManagerAdapterFacade.setIsActive(true) + assertThat(underTest.isTileActive()).isTrue() + } + } - assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2) - assertThat(tiles()).hasSize(1) - assertThat(tiles().last()).isEqualTo(TEST_TILE_2) + @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 eebb145ef384..90779cb1c0b3 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.ext.junit.runners.AndroidJUnit4 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, - ) - - underTest.init() - - assertThat(underTest.tile).isEqualTo(TEST_TILE) - assertThat(underTest.tiles.first()).isEqualTo(TEST_TILE) + with(kosmos) { + testScope.runTest { + customTileRepository.setTileActive(true) + customTileStatePersister.persistState( + TileServiceKey(TEST_COMPONENT, TEST_USER.identifier), + TEST_TILE, + ) + + underTest.initForUser(TEST_USER) + + 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() } - - underTest.updateTile(TEST_TILE) - runCurrent() - initJob.join() - - assertThat(tiles()).hasSize(1) - assertThat(tiles().last()).isEqualTo(TEST_TILE) + 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() + + 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() } - - defaultsRepository.putDefaults(TEST_USER, TEST_COMPONENT, TEST_DEFAULTS) - defaultsRepository.requestNewDefaults(TEST_USER, TEST_COMPONENT) - runCurrent() - initJob.join() - - assertThat(tiles()).hasSize(1) - assertThat(tiles().last()).isEqualTo(TEST_TILE) + 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) } + + 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) + } } @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) - - val initJob = backgroundScope.launch { underTest.init() } - advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS) + 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.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 000000000000..f67c70ce783f --- /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 2ecf01f6999d..123734742820 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 @@ class WifiViewModelTest : SysuiTestCase() { 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 @@ class WifiViewModelTest : SysuiTestCase() { } @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 @@ class WifiViewModelTest : SysuiTestCase() { } @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 @@ class WifiViewModelTest : SysuiTestCase() { } @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 @@ class WifiViewModelTest : SysuiTestCase() { } @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 7b9027004eea..50d560728fb7 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 87c31c8b8aae..dca84b9fab7c 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 deed6c8c7f25..90cc1fbb1fba 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 b9478018d7d3..0267454c9161 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 cf63cc74521d..6cb2d457daea 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 8ad32b4f1695..a00cdfa05726 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 @@ enum class FingerprintSensorType { 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 38043b4020e5..f4a2811a1b5a 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.ChannelExt.trySendWithFailureLoggin 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 @@ import kotlinx.coroutines.flow.stateIn 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 @@ constructor( } .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 c19ea19833d2..710667438299 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 @@ import kotlinx.coroutines.flow.combine /** * 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 84f7dd5edecf..af32eb534155 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 com.android.systemui.util.time.SystemClock 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 @@ constructor( 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/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/ConfigurationInteractor.kt deleted file mode 100644 index 89053d1d05fa..000000000000 --- 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 3648f3b2c3b9..13539850a598 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 @@ class ConfigurationInteractor @Inject constructor(private val repository: Config /** 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 ca8268dc89a3..a25c78871115 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.bouncer.ui.BouncerViewModule; 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 @@ import javax.inject.Named; 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 5ec51f4c3dad..15404238099d 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.server.notification.Flags.vibrateWhileUnlocked 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 @@ import javax.inject.Inject 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 7e360cfad66d..52f2759fe63d 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 @@ constructor( 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 000000000000..d12d193aa43b --- /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 d1d323fff87f..78906acd9cea 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 @@ object AlternateBouncerViewBinder { } } } + + 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 01a1ca3eeb93..08e2a8fb6d77 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 @@ object KeyguardRootViewBinder { iconsAppearTranslationPx: Int, screenOffAnimationController: ScreenOffAnimationController, ) { - val statusViewMigrated = KeyguardShadeMigrationNssl.isEnabled animate().cancel() val animatorListener = object : AnimatorListenerAdapter() { @@ -404,13 +403,13 @@ object KeyguardRootViewBinder { 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 @@ object KeyguardRootViewBinder { } !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 @@ object KeyguardRootViewBinder { 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 @@ object KeyguardRootViewBinder { 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 @@ object KeyguardRootViewBinder { .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 9d557bb8a3b2..920fc04168a7 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 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi 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 a693ec9317d0..0bf9ad02eb58 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.graphics.Rect 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.LockIconView 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 @@ constructor( 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 @@ constructor( 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 000000000000..a8e3be79fc5a --- /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 023d16cab013..5d6b0cebf959 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 @@ package com.android.systemui.keyguard.ui.viewmodel 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 @@ import kotlinx.coroutines.flow.Flow 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 000000000000..e18893a381f6 --- /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 3e8bbb3b4c34..c45caf0b18fd 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 @@ constructor( occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel, occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel, dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, + alternateBouncerToAodTransitionViewModel: AlternateBouncerToAodTransitionViewModel, ) { private val color: Flow<Int> = configurationRepository.onAnyConfigurationChange @@ -65,6 +66,7 @@ constructor( 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 9a50d8370525..fe0b3656c3d7 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 @@ package com.android.systemui.keyguard.ui.viewmodel 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.shared.model.KeyguardState 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 @@ constructor( deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, transitionInteractor: KeyguardTransitionInteractor, deviceEntryIconViewModel: DeviceEntryIconViewModel, + udfpsOverlayInteractor: UdfpsOverlayInteractor, ) { private val isShowingAod: Flow<Boolean> = transitionInteractor.startedKeyguardState.map { keyguardState -> @@ -73,11 +74,7 @@ constructor( 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 f95713bf1802..eacaa40de821 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.transitions.DeviceEntryIconTransition 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( 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 a99c51c2e557..be9393655c5d 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 @@ constructor( } 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 898298c58b32..77279f225d83 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 @@ object MediaInSceneContainerFlag { /** 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 ca5302e13545..c932cee92a30 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 @@ interface CustomTileRepository { */ 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 @@ class CustomTileRepositoryImpl constructor( private val tileSpec: TileSpec.CustomTileSpec, private val customTileStatePersister: CustomTileStatePersister, + private val packageManagerAdapter: PackageManagerAdapter, @Background private val backgroundContext: CoroutineContext, ) : CustomTileRepository { @@ -149,6 +160,34 @@ constructor( } } + 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 @@ constructor( 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 351bba538463..10b012d30fcd 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 @@ package com.android.systemui.qs.tiles.impl.custom.domain.interactor 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.launchIn 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 @@ constructor( 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 @@ constructor( * 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 @@ constructor( * - 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 580c42122a85..ef3df482ea45 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 @@ import kotlinx.coroutines.flow.StateFlow /** * 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 1156250666f3..8c3e4a5d0be2 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 @@ interface SceneContainerFlags { 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 95f7c94a235f..878e6faf32e7 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 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } @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 07ce57735dc7..3cf468f302b2 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.domain.interactor.KeyguardTransitionInterac 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.NotificationInsetsController; 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.phone.StatusBarKeyguardViewManager; 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 @@ public class NotificationShadeWindowViewController implements Dumpable { }; private final SystemClock mClock; + @ExperimentalCoroutinesApi @Inject public NotificationShadeWindowViewController( LockscreenShadeTransitionController transitionController, @@ -190,7 +202,13 @@ public class NotificationShadeWindowViewController implements Dumpable { 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 @@ public class NotificationShadeWindowViewController implements Dumpable { 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 843a454d73a0..984fcad469a8 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.ActivityManager; 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.annotations.VisibleForTesting; 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 class StatusBarIconView extends AnimatedImageView implements StatusIconDi } 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 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi 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 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi 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 000000000000..e7012ea51caf --- /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 8d1e8d0ab524..0c67279c1660 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.util.FloatProperty 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.ShadeExpansionListener 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 @@ constructor( 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 @@ constructor( * * @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 cf03d1c5addc..2cc1403a80a5 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 @@ constructor( 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 afc123faba79..319b49972bd2 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.app.Notification 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 @@ class IconBuilder @Inject constructor( } 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 9e8654a66bde..76e5fd3bd4f2 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.os.Bundle 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 @@ class IconManager @Inject constructor( 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 @@ class IconManager @Inject constructor( 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 @@ class IconManager @Inject constructor( 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 054e381f096a..442c0978fd77 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 @@ public final class IconPack { @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 @@ public final class IconPack { * 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 @@ public final class IconPack { @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 @@ public final class IconPack { @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 @@ public final class IconPack { 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 2624363c7dec..db33f92c1848 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 24e7f05a9c1d..9556c2e3159c 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 @@ import com.android.systemui.flags.RefactorFlagUtils 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 3bbdfd164ba7..85e63e58bc7a 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.flags.RefactorFlag; 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.GroupExpans 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } }; 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mAmbientState.setExpansionChanging(false); if (!mIsExpanded) { resetScrollPosition(); - mNotificationsController.resetUserExpandedStates(); + mResetUserExpandedStatesRunnable.run(); clearTemporaryViews(); clearUserLockedViews(); resetAllSwipeState(); @@ -4316,21 +4311,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 e6315fd159d5..d2fca8fca837 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.plugins.statusbar.StatusBarStateController; 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 @@ public class NotificationStackScrollLayoutController implements Dumpable { 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 @@ public class NotificationStackScrollLayoutController implements Dumpable { 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 @@ public class NotificationStackScrollLayoutController implements Dumpable { 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 4de3a7fd9ff1..3a650aa19eb0 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 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor 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.WithPrev 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.first 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 98c173402109..3cd9a8f598a4 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 @@ import com.android.systemui.flags.RefactorFlagUtils 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 a62a1ed9f0c0..e79f3ff19031 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 @@ public class LegacyNotificationIconAreaControllerImpl implements } @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 0dabafbdecb0..f34a44a5c4b0 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 @@ public class NotificationIconContainer extends ViewGroup { @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 fa0cb5c939ab..66bf527f5047 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 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable { @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 91219f02818c..0ee09390d03a 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 @@ open class AuthContainerViewTest : SysuiTestCase() { ) 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 bbf471c34cc7..6a686726b959 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 @@ class UdfpsOverlayInteractorTest : SysuiTestCase() { 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 863d9ebb41e4..fd5a584935ef 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 @@ package com.android.systemui.biometrics.ui.viewmodel 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 @@ class DeviceEntryUdfpsTouchOverlayViewModelTest : SysuiTestCase() { 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 @@ class DeviceEntryUdfpsTouchOverlayViewModelTest : SysuiTestCase() { 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 7475235cfeae..6170e0c2db1a 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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa ) 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 bfa36412ceb5..000000000000 --- 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 c5c02080fb80..9e007e970a92 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 @@ import org.mockito.MockitoAnnotations @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 @@ class ConfigurationInteractorTest : SysuiTestCase() { 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 22569e27d02a..c864704f6997 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.biometrics.AuthController 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 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() { { 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 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() { 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 1f99303f10ed..0a464e6047d3 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.AndroidTestingRunner 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.Test 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 @@ class MediaViewControllerTest : SysuiTestCase() { // 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 @@ class MediaViewControllerTest : SysuiTestCase() { } @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 @@ class MediaViewControllerTest : SysuiTestCase() { // 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 b90ccc0e3d7e..994166172aff 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 @@ internal class SceneContainerFlagsTest( // Flags.SCENE_CONTAINER_ENABLED is no longer needed. val field = Flags::class.java.getField("SCENE_CONTAINER_ENABLED") field.isAccessible = true - field.set(null, true) - - 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() + field.set(null, true) // note: this does not work with multivalent tests + } + 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 9d997dae6836..86d8d54684ac 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.LockIconViewController 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.KeyguardFaceAuthInteracto 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.NotificationInsetsController 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.statusbar.window.StatusBarWindowStateController 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 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @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 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { 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 9750f60c7b19..d9ff892145c7 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.FakeDeviceEntryFaceAuthRepo 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.NotificationInsetsController 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.statusbar.window.StatusBarWindowStateController 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.spy import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +@ExperimentalCoroutinesApi @RunWith(AndroidTestingRunner::class) @RunWithLooper(setAsMainLooper = true) @SmallTest @@ -259,6 +267,12 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { 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 a6180ec8ea0e..f178046b665a 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.statusbar.StatusBarIcon; 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 @@ public class StatusBarIconViewTest extends SysuiTestCase { .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 f3094cdd4faf..170f651aed91 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 @@ class NotificationsKeyguardViewStateRepositoryTest : SysuiTestCase() { 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 ba5ba2c31a42..ad7dee33b6d8 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.NotificationEntry; 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 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { 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 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { 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 ac20683b4f49..4bfd7e3bef83 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_0 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 @@ open class HideNotificationsInteractorTest : SysuiTestCase() { 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 21774aa2f8d7..c17a8ef62a4b 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.SysUITestModule 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 @@ class NotificationListViewModelTest : SysuiTestCase() { [ 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 0f779d977c71..44fa13283991 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 @@ import org.mockito.MockitoAnnotations 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 @@ class MobileIconViewModelTest : SysuiTestCase() { } @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 @@ class MobileIconViewModelTest : SysuiTestCase() { } @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 54756590ee83..8b1a1d99978c 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 000000000000..86a4509b4d62 --- /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 6b38d6ea315a..050c2c9793f3 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 @@ class FakeConfigurationRepository @Inject constructor() : ConfigurationRepositor 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 index f4f05d85540e..f4f05d85540e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt +++ b/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 000000000000..d9c6e4f1f605 --- /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 000000000000..e4821b04fcef --- /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 6557bcfd8337..67e9289f5d92 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 @@ val Kosmos.deviceEntryIconViewModel by Fixture { 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 000000000000..d70524840145 --- /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 ccf03911495f..ba803d8baef0 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 @@ package com.android.systemui.qs.tiles.impl.custom.data.repository 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 @@ class FakeCustomTileRepository( 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 @@ class FakeCustomTileRepository( 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 000000000000..c9a7655b5571 --- /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 baca8b2ef476..4232b274f94f 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 b5c9ffdfffae..5111b08a1812 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -103,7 +103,6 @@ java_library_static { "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 2ba3a1d751d0..1d1e2d994787 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.IntentFilter; 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 @@ public class VpnManagerService extends IVpnManager.Stub { 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 @@ public class VpnManagerService extends IVpnManager.Stub { 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 df8f17ac9d7c..3ae5527153b7 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_INSTR_BACKGROUND_FGS_PERMI 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_AM; 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 @@ public final class ActiveServices { // 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 @@ public final class ActiveServices { // 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 @@ public final class ActiveServices { // mAllowWhileInUsePermissionInFgs. r.mAllowStartForegroundAtEntering = r.getFgsAllowStart(); r.mAllowWhileInUsePermissionInFgsAtEntering = - r.isFgsAllowedWIU(); + r.isFgsAllowedWiu_forCapabilities(); r.mStartForegroundCount++; r.mFgsEnterTime = SystemClock.uptimeMillis(); if (!stopProcStatsOp) { @@ -2632,7 +2630,7 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { * @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 @@ public final class ActiveServices { * 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 @@ public final class ActiveServices { 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 @@ public final class ActiveServices { 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 d0ab287785e3..626b70b51093 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 @@ public final class CachedAppOptimizer { @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 fc8ad6bc94e7..05303f6fef63 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 @@ public class ForegroundServiceTypeLoggerModule { 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 @@ public class ForegroundServiceTypeLoggerModule { 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 3424578a78d2..b507a604343e 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 @@ public class OomAdjuster { 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 0fba73998bb9..08b129eeb8e5 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.IApplicationThread; 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 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN // 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 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN // 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 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN // 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 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN boolean mLoggedInfoAllowStartForeground; @PowerExemptionManager.ReasonCode - int mAllowWIUInBindService = REASON_DENIED; + int mAllowWiu_inBindService = REASON_DENIED; + + @PowerExemptionManager.ReasonCode + int mAllowWiu_byBindings = REASON_DENIED; + + @PowerExemptionManager.ReasonCode + int mAllowStart_inBindService = REASON_DENIED; + + @PowerExemptionManager.ReasonCode + int mAllowStart_byBindings = REASON_DENIED; + + /** + * 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); + } + + /** + * 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 - int mAllowWIUByBindings = REASON_DENIED; + 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 mAllowStartInBindService = REASON_DENIED; + 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 mAllowStartByBindings = REASON_DENIED; + 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 - int getFgsAllowWIU() { - return mAllowWhileInUsePermissionInFgsReasonNoBinding != REASON_DENIED - ? mAllowWhileInUsePermissionInFgsReasonNoBinding - : mAllowWIUInBindService; + 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 + ); } - boolean isFgsAllowedWIU() { - return getFgsAllowWIU() != REASON_DENIED; + @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 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN } 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 int startLegacy = getFgsAllowStart_legacy(); + final int startNew = getFgsAllowStart_new(); - final boolean wiuChanged = allowedChanged(origWiu, newWiu); - final boolean startChanged = allowedChanged(origStart, newStart); + final boolean wiuChanged = allowedChanged(wiuLegacy, wiuNew); + final boolean startChanged = allowedChanged(startLegacy, startNew); if (!wiuChanged && !startChanged) { return; @@ -311,15 +444,14 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN 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 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN 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 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN 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 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN 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 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN 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 d9e8dddddae4..654aebd89de2 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 @@ flag { 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 dafea9a199fd..d5d8fd22314b 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.ITestSessionCallback; 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 @@ public class AuthService extends SystemService { /** * Allows to test with various device sensor configurations. - * @param context - * @return */ @VisibleForTesting public String[] getConfiguration(Context context) { @@ -131,6 +133,30 @@ public class AuthService extends SystemService { } /** + * 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 @@ public class AuthService extends SystemService { } 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 @@ public class AuthService extends SystemService { 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 037ea38a2d17..89b638be3500 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 @@ public class BiometricScheduler { }; @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 57feedc0e68e..0c3dfa7b3b84 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 @@ public class BiometricSchedulerOperation { 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 7f8f38f3e9d2..6daaad1baf83 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 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T> // 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 80754702415a..3753bbdba276 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 @@ public class UserAwareBiometricScheduler extends BiometricScheduler { 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 578d9dc2aede..6af223b3660a 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.IFace; 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.Slog; 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.ArrayList; 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 @@ public class FaceService extends SystemService { 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 @@ public class FaceService extends SystemService { @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 @@ public class FaceService extends SystemService { 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 @@ public class FaceService extends SystemService { }); } + @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 class FaceService extends SystemService { } 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 e5d4a635876d..ef2be790ed34 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 @@ import com.android.server.biometrics.sensors.LockoutTracker; * 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 57f5b34c197a..098be2120e03 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.face.Face; 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 @@ public class AidlResponseHandler extends ISessionCallback.Stub { 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 @@ public class AidlResponseHandler extends ISessionCallback.Stub { 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 @@ public class AidlResponseHandler extends ISessionCallback.Stub { 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 @@ public class AidlResponseHandler extends ISessionCallback.Stub { @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 @@ public class AidlResponseHandler extends ISessionCallback.Stub { @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 @@ public class AidlResponseHandler extends ISessionCallback.Stub { return; } c.onAuthenticationFrame(AidlConversionUtils.toFrameworkAuthenticationFrame(frame)); - }, null); + }); } @Override @@ -134,7 +170,7 @@ public class AidlResponseHandler extends ISessionCallback.Stub { return; } c.onEnrollmentFrame(AidlConversionUtils.toFrameworkEnrollmentFrame(frame)); - }, null); + }); } @Override @@ -149,9 +185,13 @@ public class AidlResponseHandler extends ISessionCallback.Stub { 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 @@ public class AidlResponseHandler extends ISessionCallback.Stub { .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 @@ public class AidlResponseHandler extends ISessionCallback.Stub { 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 @@ public class AidlResponseHandler extends ISessionCallback.Stub { 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 @@ public class AidlResponseHandler extends ISessionCallback.Stub { 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 @@ public class AidlResponseHandler extends ISessionCallback.Stub { lockoutMode = LockoutTracker.LOCKOUT_TIMED; } - mLockoutCache.setLockoutModeForUser(mUserId, lockoutMode); + mLockoutTracker.setLockoutModeForUser(mUserId, lockoutMode); if (duration == 0) { mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId); @@ -296,6 +340,20 @@ public class AidlResponseHandler extends ISessionCallback.Stub { }); } + /** + * 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 e70e25aebe9b..af46f441d6ce 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.content.Context; 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 class AidlSession { 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 @@ public class AidlSession { } /** 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 c15049b48bb2..c41b706555e8 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.ClientMonitorCallback; 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 @@ public class FaceGetFeatureClient extends HalClientMonitor<AidlSession> implemen 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 dd9c6d50ae9e..9fa15b8ea3a1 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.SynchronousUserSwitchObserver; 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 android.view.Surface; 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.ClientMonitorCallbackConverter; 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 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, 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 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { } } - 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { 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 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { } 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 77b5592c5064..d02eefaed101 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.annotation.NonNull; 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.ErrorConsumer; 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 @@ public class FaceResetLockoutClient extends HalClientMonitor<AidlSession> implem 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 @@ public class FaceResetLockoutClient extends HalClientMonitor<AidlSession> implem 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 @@ public class FaceResetLockoutClient extends HalClientMonitor<AidlSession> implem @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 @@ public class FaceResetLockoutClient extends HalClientMonitor<AidlSession> implem } 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 54e66eb4cca4..3e5c59914913 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.annotation.Nullable; 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 android.util.proto.ProtoOutputStream; 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.BiometricScheduler; 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 @@ public class Sensor { @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 class Sensor { 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 class Sensor { 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 @@ public class Sensor { 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 @@ public class Sensor { 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 @@ public class Sensor { 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 8385c3fa7103..0c34d702184a 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.BiometricsProto; 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 @@ public class FaceUpdateActiveUserClient extends HalClientMonitor<IBiometricsFace 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 @@ public class FaceUpdateActiveUserClient extends HalClientMonitor<IBiometricsFace 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 36a9790d2d4b..7a574cecc0b1 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 @@ public class HidlToAidlCallbackConverter extends IBiometricsFaceClientCallback.S 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 000000000000..6355cb57a752 --- /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 index 489b213677dd..5daf2d4fbcf4 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.List; 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 static final String TAG = "HidlToAidlSessionAdapter"; - private final String TAG = "AidlToHidlAdapter"; 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 @@ public class AidlToHidlAdapter implements ISession { 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 @@ public class AidlToHidlAdapter implements ISession { @Override public EnrollmentStageConfig[] getEnrollmentConfig(byte enrollmentType) throws RemoteException { - //unsupported in HIDL + Slog.e(TAG, "getEnrollmentConfig unsupported in HIDL"); return null; } @@ -244,19 +216,6 @@ public class AidlToHidlAdapter implements ISession { } } - 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 @@ public class AidlToHidlAdapter implements ISession { @Override public void invalidateAuthenticatorId() throws RemoteException { - //unsupported in HIDL + Slog.e(TAG, "invalidateAuthenticatorId unsupported in HIDL"); + mHidlToAidlCallbackConverter.onUnsupportedClientScheduled(); } @Override @@ -279,47 +239,105 @@ public class AidlToHidlAdapter implements ISession { @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 @@ public class AidlToHidlAdapter implements ISession { 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 83b306b07c27..e01d672d7e34 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.ITestSession; 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.annotations.VisibleForTesting; 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 @@ public class FingerprintService extends SystemService { @NonNull private final Function<String, FingerprintProvider> mFingerprintProvider; @NonNull + private final FingerprintProviderFunction mFingerprintProviderFunction; + @NonNull private final BiometricStateCallback<ServiceProvider, FingerprintSensorPropertiesInternal> mBiometricStateCallback; @NonNull @@ -136,6 +141,11 @@ public class FingerprintService extends SystemService { @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 @@ public class FingerprintService extends SystemService { @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 @@ public class FingerprintService extends SystemService { () -> IBiometricService.Stub.asInterface( ServiceManager.getService(Context.BIOMETRIC_SERVICE)), () -> ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR), - null /* fingerprintProvider */); + null /* fingerprintProvider */, + null /* fingerprintProviderFunction */); } @VisibleForTesting @@ -1029,7 +1052,8 @@ public class FingerprintService extends SystemService { 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 @@ public class FingerprintService extends SystemService { 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 @@ public class FingerprintService extends SystemService { 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 @@ public class FingerprintService extends SystemService { }); } + @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 4a019436cf6f..bd21cf4002b0 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.fingerprint.Fingerprint; 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.BaseClientMonitor; 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 @@ public class AidlResponseHandler extends ISessionCallback.Stub { 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 @@ public class AidlResponseHandler extends ISessionCallback.Stub { 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 @@ public class AidlResponseHandler extends ISessionCallback.Stub { @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 @@ public class AidlResponseHandler extends ISessionCallback.Stub { 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 @@ public class AidlResponseHandler extends ISessionCallback.Stub { .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 @@ public class AidlResponseHandler extends ISessionCallback.Stub { @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 @@ public class AidlResponseHandler extends ISessionCallback.Stub { 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 @@ public class AidlResponseHandler extends ISessionCallback.Stub { @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 299a310caee9..8ff105baa981 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.annotation.NonNull; 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 class AidlSession { 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 @@ public class AidlSession { } /** 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 ea1a622c36ab..03539690c0a8 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 com.android.server.biometrics.sensors.HalClientMonitor; 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 032ab87196f8..88a11d9c0ceb 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 android.util.proto.ProtoOutputStream; 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.ClientMonitorCallbackConverter; 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.GestureAvailabilityDisp 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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; - }); - - final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context); + initAuthenticationBroadcastReceiver(); + initSensors(resetLockoutRequiresHardwareAuthToken, props, gestureAvailabilityDispatcher); + } - for (SensorProps prop : props) { - final int sensorId = prop.commonProps.sensorId; + private void initAuthenticationBroadcastReceiver() { + new AuthenticationStatsBroadcastReceiver( + mContext, + BiometricsProtoEnums.MODALITY_FINGERPRINT, + (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 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); } } - - 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); + } else { + final List<SensorLocationInternal> workaroundLocations = + getWorkaroundSensorProps(mContext); + + 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } } - 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler, Utils.getCurrentStrength(sensorId), SystemClock.elapsedRealtimeClock(), - null /* lockoutTracker */); + lockoutTracker); scheduleForSensor(sensorId, client, new ClientMonitorCallback() { @Override @@ -636,6 +732,9 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @Override public boolean isHardwareDetected(int sensorId) { + if (Flags.deHidl()) { + return mFingerprintSensors.get(sensorId).isHardwareDetected(mHalInstanceName); + } return hasHalInstance(); } @@ -674,8 +773,12 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @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 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi 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 ec225a60d54b..387ae12147ae 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 @@ public class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> @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 893cb8f9b4fc..dd887bb05c12 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.annotation.Nullable; 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.BiometricStateCallback; 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 @@ public class Sensor { @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 @@ public class Sensor { 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 class Sensor { 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 class Sensor { 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 @@ public class Sensor { + "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 @@ public class Sensor { 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 @@ public class Sensor { 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 a4e602553101..5c5b9928f57a 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.BiometricsProto; 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 @@ import java.util.function.Supplier; /** * 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 @@ public class FingerprintUpdateActiveUserClient extends HalClientMonitor<IBiometr @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 @@ public class FingerprintUpdateActiveUserClient extends HalClientMonitor<IBiometr 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 @@ public class FingerprintUpdateActiveUserClient extends HalClientMonitor<IBiometr 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 c3e5cbe7daf4..e9a48e79d245 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.annotation.NonNull; 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 @@ public class HidlToAidlCallbackConverter extends IBiometricsFingerprintClientCal @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 @@ public class HidlToAidlCallbackConverter extends IBiometricsFingerprintClientCal 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 000000000000..0bb6141583d5 --- /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 index b48d232e65af..2fc00e126354 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.biometrics.fingerprint.V2_1.IBiometricsFingerprint; 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 @@ public class AidlToHidlAdapter implements ISession { 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 @@ public class AidlToHidlAdapter implements ISession { @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 @@ public class AidlToHidlAdapter implements ISession { @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 0730c672acd9..2f77275890dd 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 @@ package com.android.server.biometrics.sensors.fingerprint.hidl; 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.Slog; 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 @@ public class LockoutFrameworkImpl implements LockoutTracker { 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(@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)); + } - public LockoutFrameworkImpl(Context context, LockoutResetCallback lockoutResetCallback) { - mContext = context; + @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 @@ public class LockoutFrameworkImpl implements LockoutTracker { 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 c5170585a1b3..b7ece2ea65b1 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 @@ public class Vpn { } 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 @@ public class Vpn { 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 1bd556bdcc4f..4e341a9c19b4 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 @@ public class DisplayOffloadSessionImpl implements DisplayManagerInternal.Display } } + @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 52c53f3d658e..6d09cc9d37ba 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 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal // 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 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal 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 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal // 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 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal // 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 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal private boolean mBootCompleted; private final DisplayManagerFlags mFlags; - private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession; + private DisplayOffloadSession mDisplayOffloadSession; /** * Creates the display power controller. @@ -772,6 +781,10 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal @Override public void setDisplayOffloadSession(DisplayOffloadSession session) { + if (session == mDisplayOffloadSession) { + return; + } + unblockScreenOnByDisplayOffload(); mDisplayOffloadSession = session; } @@ -1735,6 +1748,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal // 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 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal } } + 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 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal } } - 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 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal } // 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 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal 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 22898a65c5de..25576ce9efd6 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.Context; 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 @@ final class LocalDisplayAdapter extends DisplayAdapter { 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 @@ final class LocalDisplayAdapter extends DisplayAdapter { 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 @@ final class LocalDisplayAdapter extends DisplayAdapter { } } + 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 @@ final class LocalDisplayAdapter extends DisplayAdapter { 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 @@ final class LocalDisplayAdapter extends DisplayAdapter { long renderPeriod); void onFrameRateOverridesChanged(long timestampNanos, long physicalDisplayId, DisplayEventReceiver.FrameRateOverride[] overrides); + void onHdcpLevelsChanged(long physicalDisplayId, int connectedLevel, int maxLevel); } @@ -1420,6 +1449,11 @@ final class LocalDisplayAdapter extends DisplayAdapter { 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 @@ final class LocalDisplayAdapter extends DisplayAdapter { 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 4089a81dfc20..dda50cab2cdd 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 @@ public abstract class InputMethodManagerInternal { /** * 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 @@ public abstract class InputMethodManagerInternal { } @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 16e043cfb64d..0d29b7dca8d4 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 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @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 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @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 2da1a689f69f..66e61c076030 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 @@ public class ConditionProviders extends ManagedServices { 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 885566693b9a..71a6b5ed0581 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 @@ class DefaultDeviceEffectsApplier implements DeviceEffectsApplier { 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 75d3dce55abd..a919db947593 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 @@ public class NotificationManagerService extends SystemService { } @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 @@ public class NotificationManagerService extends SystemService { } @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 @@ public class NotificationManagerService extends SystemService { + "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 @@ public class NotificationManagerService extends SystemService { } 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 @@ public class NotificationManagerService extends SystemService { } @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 @@ public class NotificationManagerService extends SystemService { } @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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { * {@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 @@ public class NotificationManagerService extends SystemService { 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 dc0cf4e09207..9f3104cbd7b0 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 @@ public class NotificationShellCmd extends ShellCommand { 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 @@ public class NotificationShellCmd extends ShellCommand { 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 3f8b5952a1bc..d1de9b086c5d 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 @@ public class ZenModeHelper { * {@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 @@ public class ZenModeHelper { } // 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 c48eccf2aac5..2305d6c9fba9 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 @@ public class UserManagerService extends IUserManager.Stub { @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 630b9e139456..b5e5d848c75c 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 @@ class ActivityStarter { } } - // 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 @@ class ActivityStarter { 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()); + // 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 (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 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 @@ class ActivityStarter { 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 ae10ce3690aa..c98280e30242 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp 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 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } @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 8ecbc177896c..b74eb56ebdca 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 @@ interface InsetsControlTarget { /** * 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 781567990235..3c556bf7b126 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 @@ class InsetsPolicy { } @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 c4d01291f558..6b9fcf411ce1 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 @@ class InsetsStateController { }; 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 @@ class InsetsStateController { 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 @@ class InsetsStateController { 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 5518de7b64fd..9305396caa19 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.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 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 @@ final class LetterboxUiController { // 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 @@ final class LetterboxUiController { // 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 @@ final class LetterboxUiController { 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 @@ final class LetterboxUiController { @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 @@ final class LetterboxUiController { return candidate; } - DisplayContent displayContent = mActivityRecord.mDisplayContent; if (mIsOverrideOrientationOnlyForCameraEnabled && displayContent != null && (displayContent.mDisplayRotationCompatPolicy == null || !displayContent.mDisplayRotationCompatPolicy @@ -698,6 +703,17 @@ final class LetterboxUiController { 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 57939bc4f348..7995028b1ec7 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.server.wm.WindowManagerService.H; 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 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { 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 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { 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 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } } + 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 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } } - void windowAddedLocked() { + void onWindowAdded(WindowState w) { if (mPackageName == null) { mPackageName = mProcess.mInfo.packageName; mRelayoutTag = "relayoutWindow: " + mPackageName; @@ -757,12 +772,14 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { 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 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } private void killSessionLocked() { - if (mNumWindow > 0 || !mClientDead) { + if (!mClientDead) { return; } @@ -838,10 +855,6 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { 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 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { + " in session " + this + ": " + e.toString()); } mSurfaceSession = null; + mAddedWindows.clear(); mAlertWindowSurfaces.clear(); mAppOverlaySurfaces.clear(); setHasOverlayUi(false); @@ -869,7 +883,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } 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 2125c63eed34..c1310a6880fd 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 4e9d23c88db4..56e7c69fe529 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } - 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } @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 f69f6283f968..022268df4a63 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 @@ class AppIdPermissionPolicy : SchemePolicy() { val apexModuleName = packageState.apexModuleName val packageName = packageState.packageName return when { - packageState.isVendor -> + packageState.isVendor || packageState.isOdm -> permissionAllowlist.getVendorPrivilegedAppAllowlistState( packageName, permissionName @@ -1471,12 +1471,15 @@ class AppIdPermissionPolicy : SchemePolicy() { // 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 dea838d3763d..fbb14c3db9f9 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 @@ package com.android.server.display; 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 @@ public class DisplayOffloadSessionImplTest { 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 f36854b1ea78..0c845de8846e 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 @@ public class LocalDisplayAdapterTest { @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 3b5cae328b3c..88b2ed4f79c9 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.IBiometricEnabledOnKeyguardCallback; 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 @@ public class AuthServiceTest { @Rule public MockitoRule mockitorule = MockitoJUnit.rule(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -118,6 +129,10 @@ public class AuthServiceTest { @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 @@ public class AuthServiceTest { 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 @@ public class AuthServiceTest { } @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 @@ public class AuthServiceTest { 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 000000000000..c9e1c4a8bfc5 --- /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 f43120d1a755..8b1a2915820a 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.common.CommonProps; 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.HalClientMonitor; 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 @@ import java.util.ArrayList; @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 @@ public class FaceProviderTest { mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback, mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext, - mDaemon); + mDaemon, false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */); } @Test @@ -124,10 +136,38 @@ public class FaceProviderTest { 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 7a293e80c183..e7f7195588ff 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 @@ public class SensorTest { 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 @@ public class SensorTest { 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 000000000000..4e43332ab52c --- /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 index 9a40e8a7201a..b9a4fb4e0939 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 @@ import java.util.List; @Presubmit @SmallTest -public class AidlToHidlAdapterTest { +public class HidlToAidlSessionAdapterTest { @Rule public final MockitoRule mockito = MockitoJUnit.rule(); @@ -84,25 +84,32 @@ public class AidlToHidlAdapterTest { 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 @@ public class AidlToHidlAdapterTest { 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 @@ public class AidlToHidlAdapterTest { .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 @@ public class AidlToHidlAdapterTest { @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 @@ public class AidlToHidlAdapterTest { @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 @@ public class AidlToHidlAdapterTest { @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 @@ public class AidlToHidlAdapterTest { @Test public void testDetectInteraction() throws RemoteException { - ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction(); + ICancellationSignal cancellationSignal = mHidlToAidlSessionAdapter.detectInteraction(); verify(mSession).authenticate(0); @@ -204,7 +211,7 @@ public class AidlToHidlAdapterTest { @Test public void testEnumerateEnrollments() throws RemoteException { - mAidlToHidlAdapter.enumerateEnrollments(); + mHidlToAidlSessionAdapter.enumerateEnrollments(); verify(mSession).enumerate(); } @@ -212,7 +219,7 @@ public class AidlToHidlAdapterTest { @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 @@ public class AidlToHidlAdapterTest { 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 @@ public class AidlToHidlAdapterTest { 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 @@ public class AidlToHidlAdapterTest { 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 @@ public class AidlToHidlAdapterTest { @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 @@ public class AidlToHidlAdapterTest { 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 @@ public class AidlToHidlAdapterTest { 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 @@ public class AidlToHidlAdapterTest { when(mSession.getAuthenticatorId()).thenReturn(result); - mAidlToHidlAdapter.getAuthenticatorId(); + mHidlToAidlSessionAdapter.getAuthenticatorId(); verify(mSession).getAuthenticatorId(); verify(mAidlResponseHandler).onAuthenticatorIdRetrieved(authenticatorId); @@ -319,7 +326,7 @@ public class AidlToHidlAdapterTest { @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 2aa62d96168d..f570ba23441d 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 static org.mockito.Mockito.when; 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 androidx.test.platform.app.InstrumentationRegistry; 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 @@ public class FingerprintServiceTest { @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 @@ public class FingerprintServiceTest { private IBinder mToken; @Mock private VirtualDeviceManagerInternal mVdmInternal; + @Mock + private IFingerprint mDefaultFingerprintDaemon; + @Mock + private IFingerprint mVirtualFingerprintDaemon; @Captor private ArgumentCaptor<FingerprintAuthenticateOptions> mAuthenticateOptionsCaptor; @@ -126,7 +140,10 @@ public class FingerprintServiceTest { 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 @@ public class FingerprintServiceTest { .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 @@ public class FingerprintServiceTest { 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 @@ public class FingerprintServiceTest { 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 @@ public class FingerprintServiceTest { } @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 @@ public class FingerprintServiceTest { } @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 @@ public class FingerprintServiceTest { 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 4cfb83fa1c69..bf5986c1d0f3 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.IFingerprint; 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.LockoutResetDispatcher; 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 @@ public class FingerprintProviderTest { private static final String TAG = "FingerprintProviderTest"; + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + @Mock private Context mContext; @Mock @@ -115,7 +126,8 @@ public class FingerprintProviderTest { mFingerprintProvider = new FingerprintProvider(mContext, mBiometricStateCallback, mAuthenticationStateListeners, mSensorProps, TAG, mLockoutResetDispatcher, mGestureAvailabilityDispatcher, mBiometricContext, - mDaemon); + mDaemon, false /* resetLockoutRequiresHardwareAuthToken */, + true /* testHalEnabled */); } @Test @@ -123,17 +135,42 @@ public class FingerprintProviderTest { 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 410260074fbd..126a05e12628 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 @@ public class SensorTest { 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 @@ public class SensorTest { 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 000000000000..89a49615dbe1 --- /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 index b78ba82bd8fe..d723e87a62ad 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 @@ import org.mockito.junit.MockitoRule; @Presubmit @SmallTest -public class AidlToHidlAdapterTest { +public class HidlToAidlSessionAdapterTest { @Rule public final MockitoRule mockito = MockitoJUnit.rule(); @@ -54,11 +54,11 @@ public class AidlToHidlAdapterTest { 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 @@ public class AidlToHidlAdapterTest { @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 @@ public class AidlToHidlAdapterTest { @Test public void testRevokeChallenge() throws RemoteException { - mAidlToHidlAdapter.revokeChallenge(mChallenge); + mHidlToAidlSessionAdapter.revokeChallenge(mChallenge); verify(mSession).postEnroll(); verify(mAidlResponseHandler).onChallengeRevoked(0L); @@ -84,9 +84,9 @@ public class AidlToHidlAdapterTest { @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 @@ public class AidlToHidlAdapterTest { @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 @@ public class AidlToHidlAdapterTest { @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 @@ public class AidlToHidlAdapterTest { @Test public void testEnumerateEnrollments() throws RemoteException { - mAidlToHidlAdapter.enumerateEnrollments(); + mHidlToAidlSessionAdapter.enumerateEnrollments(); verify(mSession).enumerate(); } @@ -126,7 +128,7 @@ public class AidlToHidlAdapterTest { @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 @@ public class AidlToHidlAdapterTest { @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 b4281d63c5e2..101ea6d81a16 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 @@ public class UserManagerServiceTest { } @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 260ee39656ea..30843d222742 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 @@ public class DefaultDeviceEffectsApplierTest { } @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 @@ public class DefaultDeviceEffectsApplierTest { @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 56c75b52cf18..9a1359591890 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.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; 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.DeviceConfig; 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { }); mService.getBinderService().setZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS, null, - "testing!"); + "testing!", false); waitForIdle(); InOrder inOrder = inOrder(mContext); @@ -13441,9 +13586,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .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 1aea56cd8f9f..bc63c29e9955 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.net.Uri; 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 @@ public class ZenModeHelperTest extends UiServiceTestCase { 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 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @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 @@ public class ZenModeHelperTest extends UiServiceTestCase { 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 @@ public class ZenModeHelperTest extends UiServiceTestCase { 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 @@ public class ZenModeHelperTest extends UiServiceTestCase { 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 @@ public class ZenModeHelperTest extends UiServiceTestCase { 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 @@ public class ZenModeHelperTest extends UiServiceTestCase { 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 c6796dc9e90d..985be429d42b 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 @@ package com.android.server.wm; 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 @@ public class LetterboxUiControllerTest extends WindowTestsBase { } @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 f99b489720b9..8bf4833bc2ca 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 class WindowManagerServiceTests extends WindowTestsBase { 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 @@ public class WindowManagerServiceTests extends WindowTestsBase { 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 df4af112c087..616a23e7ab5b 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 @@ class WindowTestsBase extends SystemServiceTestsBase { */ 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 72db7fecb8ac..ccd4ce038367 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 @@ public class UsageStatsService extends SystemService implements 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 4720d279a676..f9fa9b7a9524 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 @@ final class VisualQueryDetectorSession extends DetectorSession { @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); + synchronized (mLock) { + mEgressingData = true; + if (mAttentionListener == null) { + return; + } 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"); + 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"); + } } - return; } } @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); + synchronized (mLock) { + mEgressingData = false; + if (mAttentionListener == null) { + return; + } 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"); + 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"); + } } - return; } } @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( |