diff options
43 files changed, 672 insertions, 230 deletions
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index edcab29dec94..0ff9f6655b8a 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -431,7 +431,7 @@ public class ActivityOptions { private boolean mOverrideTaskTransition; private String mSplashScreenThemeResName; @SplashScreen.SplashScreenStyle - private int mSplashScreenStyle; + private int mSplashScreenStyle = SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED; private boolean mRemoveWithTaskOrganizer; private boolean mLaunchedFromBubble; private boolean mTransientLaunch; diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index b5298fc9e1c7..8daf9f0450c4 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1010,12 +1010,12 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * This change id restricts treatments that force a given min aspect ratio to activities * whose orientation is fixed to portrait. * - * This treatment only takes effect if OVERRIDE_MIN_ASPECT_RATIO is also enabled. + * This treatment is enabled by default and only takes effect if OVERRIDE_MIN_ASPECT_RATIO is + * also enabled. * @hide */ @ChangeId @Overridable - @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S_V2) @TestApi public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // buganizer id diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java index 43ef33e1f420..28046c56b9f8 100644 --- a/core/java/android/hardware/biometrics/BiometricConstants.java +++ b/core/java/android/hardware/biometrics/BiometricConstants.java @@ -151,6 +151,12 @@ public interface BiometricConstants { int BIOMETRIC_ERROR_RE_ENROLL = 16; /** + * The privacy setting has been enabled and will block use of the sensor. + * @hide + */ + int BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED = 18; + + /** * This constant is only used by SystemUI. It notifies SystemUI that authentication was paused * because the authentication attempt was unsuccessful. * @hide diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java index fe43c83d17f1..fd46f243874b 100644 --- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java @@ -69,7 +69,7 @@ public interface BiometricFaceConstants { BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL, BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED, BIOMETRIC_ERROR_RE_ENROLL, - FACE_ERROR_UNKNOWN + FACE_ERROR_UNKNOWN, }) @Retention(RetentionPolicy.SOURCE) @interface FaceError {} diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl index 1ad0452dd837..4399207fcc27 100644 --- a/core/java/android/window/ITaskFragmentOrganizerController.aidl +++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl @@ -44,4 +44,10 @@ interface ITaskFragmentOrganizerController { * Unregisters remote animations per transition type for the organizer. */ void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer); + + /** + * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and + * only occupies a portion of Task bounds. + */ + boolean isActivityEmbedded(in IBinder activityToken); } diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java index b9bf009fb2bb..090dbff488e9 100644 --- a/core/java/android/window/SplashScreen.java +++ b/core/java/android/window/SplashScreen.java @@ -43,6 +43,11 @@ import java.util.ArrayList; */ public interface SplashScreen { /** + * The splash screen style is not defined. + * @hide + */ + int SPLASH_SCREEN_STYLE_UNDEFINED = -1; + /** * Force splash screen to be empty. * @hide */ @@ -55,6 +60,7 @@ public interface SplashScreen { /** @hide */ @IntDef(prefix = { "SPLASH_SCREEN_STYLE_" }, value = { + SPLASH_SCREEN_STYLE_UNDEFINED, SPLASH_SCREEN_STYLE_EMPTY, SPLASH_SCREEN_STYLE_ICON }) diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java index 7e7d37083b5b..9c2fde04e4d2 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -216,4 +216,17 @@ public class TaskFragmentOrganizer extends WindowOrganizer { return null; } } + + /** + * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and + * only occupies a portion of Task bounds. + * @hide + */ + public boolean isActivityEmbedded(@NonNull IBinder activityToken) { + try { + return getController().isActivityEmbedded(activityToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 3c7f0267d943..166d6abd1809 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1666,6 +1666,8 @@ <string name="face_setup_notification_title">Set up Face Unlock</string> <!-- Contents of a notification that directs the user to set up face unlock by enrolling their face. [CHAR LIMIT=NONE] --> <string name="face_setup_notification_content">Unlock your phone by looking at it</string> + <!-- Error message indicating that the camera privacy sensor has been turned on [CHAR LIMIT=NONE] --> + <string name="face_sensor_privacy_enabled">To use Face Unlock, turn on <b>Camera access</b> in Settings > Privacy</string> <!-- Title of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] --> <string name="fingerprint_setup_notification_title">Set up more ways to unlock</string> <!-- Contents of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a23816e8174a..54282bef00f0 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2570,6 +2570,7 @@ <java-symbol type="string" name="face_recalibrate_notification_name" /> <java-symbol type="string" name="face_recalibrate_notification_title" /> <java-symbol type="string" name="face_recalibrate_notification_content" /> + <java-symbol type="string" name="face_sensor_privacy_enabled" /> <java-symbol type="string" name="face_error_unable_to_process" /> <java-symbol type="string" name="face_error_hw_not_available" /> <java-symbol type="string" name="face_error_no_space" /> diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index fe6c7ba3b24c..af19bd0a79ac 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -91,11 +91,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ public void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent intent, @Nullable Bundle options, @NonNull SplitRule sideRule, - @NonNull Consumer<Exception> failureCallback) { + @Nullable Consumer<Exception> failureCallback) { try { mPresenter.startActivityToSide(launchingActivity, intent, options, sideRule); } catch (Exception e) { - failureCallback.accept(e); + if (failureCallback != null) { + failureCallback.accept(e); + } } } @@ -858,4 +860,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen launchingContainer.getTaskFragmentToken()); } } + + /** + * Checks if an activity is embedded and its presentation is customized by a + * {@link android.window.TaskFragmentOrganizer} to only occupy a portion of Task bounds. + */ + public boolean isActivityEmbedded(@NonNull Activity activity) { + return mPresenter.isActivityEmbedded(activity.getActivityToken()); + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java index 8c8ef92b80dc..46bdf6d0e689 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java @@ -16,6 +16,7 @@ package androidx.window.extensions.embedding; +import static android.os.Process.THREAD_PRIORITY_DISPLAY; import static android.view.RemoteAnimationTarget.MODE_CLOSING; import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN; @@ -29,7 +30,7 @@ import android.animation.Animator; import android.animation.ValueAnimator; import android.graphics.Rect; import android.os.Handler; -import android.os.Looper; +import android.os.HandlerThread; import android.os.RemoteException; import android.util.Log; import android.view.IRemoteAnimationFinishedCallback; @@ -50,10 +51,14 @@ import java.util.function.BiFunction; class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub { private static final String TAG = "TaskFragAnimationRunner"; - private final Handler mHandler = new Handler(Looper.myLooper()); + private final Handler mHandler; private final TaskFragmentAnimationSpec mAnimationSpec; TaskFragmentAnimationRunner() { + HandlerThread animationThread = new HandlerThread( + "androidx.window.extensions.embedding", THREAD_PRIORITY_DISPLAY); + animationThread.start(); + mHandler = animationThread.getThreadHandler(); mAnimationSpec = new TaskFragmentAnimationSpec(mHandler); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index a1a53bc93781..4d2d0551d828 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -103,7 +103,7 @@ class TaskFragmentContainer { ActivityThread activityThread = ActivityThread.currentActivityThread(); for (IBinder token : mInfo.getActivities()) { Activity activity = activityThread.getActivity(token); - if (activity != null && !allActivities.contains(activity)) { + if (activity != null && !activity.isFinishing() && !allActivities.contains(activity)) { allActivities.add(activity); } } diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar Binary files differindex d6678bf9b320..f54ab08d8a8a 100644 --- a/libs/WindowManager/Jetpack/window-extensions-release.aar +++ b/libs/WindowManager/Jetpack/window-extensions-release.aar diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index 82e827398bb7..da4bbe81a3e6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -189,7 +189,7 @@ public class PipMenuView extends FrameLayout { mEnterSplitButton = findViewById(R.id.enter_split); mEnterSplitButton.setAlpha(0); mEnterSplitButton.setOnClickListener(v -> { - if (mMenuContainer.getAlpha() != 0) { + if (mEnterSplitButton.getAlpha() != 0) { enterSplit(); } }); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index db301f698753..aca4fe456195 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -25,6 +25,7 @@ import static android.provider.Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT; import static android.provider.Settings.SET_ALL_RESULT_DISABLED; import static android.provider.Settings.SET_ALL_RESULT_FAILURE; import static android.provider.Settings.SET_ALL_RESULT_SUCCESS; +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER; import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY; @@ -3585,7 +3586,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 204; + private static final int SETTINGS_VERSION = 205; private final int mUserId; @@ -5227,6 +5228,30 @@ public class SettingsProvider extends ContentProvider { currentVersion = 204; } + if (currentVersion == 204) { + // Version 204: Reset the + // Secure#ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT as enabled + // status for showing the tooltips. + final SettingsState secureSettings = getSecureSettingsLocked(userId); + final Setting accessibilityButtonMode = secureSettings.getSettingLocked( + Secure.ACCESSIBILITY_BUTTON_MODE); + if (!accessibilityButtonMode.isNull() + && accessibilityButtonMode.getValue().equals( + String.valueOf(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU))) { + if (isGestureNavigateEnabled() + && hasValueInA11yButtonTargets(secureSettings)) { + secureSettings.insertSettingLocked( + Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT, + /* enabled */ "1", + /* tag= */ null, + /* makeDefault= */ false, + SettingsState.SYSTEM_PACKAGE_NAME); + } + } + + currentVersion = 205; + } + // vXXX: Add new settings above this point. if (currentVersion != newVersion) { diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml index 4fc38a813e8a..16010430df11 100644 --- a/packages/SystemUI/res-keyguard/values/strings.xml +++ b/packages/SystemUI/res-keyguard/values/strings.xml @@ -219,6 +219,9 @@ <!-- Face hint message when finger was not recognized. [CHAR LIMIT=20] --> <string name="kg_face_not_recognized">Not recognized</string> + <!-- Error message indicating that the camera privacy sensor has been turned on [CHAR LIMIT=NONE] --> + <string name="kg_face_sensor_privacy_enabled">To use Face Unlock, turn on <b>Camera access</b> in Settings > Privacy</string> + <!-- Instructions telling the user remaining times when enter SIM PIN view. --> <plurals name="kg_password_default_pin_message"> <item quantity="one">Enter SIM PIN. You have <xliff:g id="number">%d</xliff:g> remaining diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml index 1e0ce0026d8e..46cbc253175e 100644 --- a/packages/SystemUI/res/layout/auth_credential_password_view.xml +++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml @@ -18,64 +18,71 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="vertical" - android:gravity="center_horizontal" - android:elevation="@dimen/biometric_dialog_elevation"> + android:elevation="@dimen/biometric_dialog_elevation" + android:orientation="vertical"> - <Space - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_weight="1"/> + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="match_parent"> - <ImageView - android:id="@+id/icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content"/> + <LinearLayout + android:id="@+id/auth_credential_header" + style="@style/AuthCredentialHeaderStyle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentTop="true"> - <TextView - android:id="@+id/title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@style/TextAppearance.AuthCredential.Title"/> + <ImageView + android:id="@+id/icon" + android:layout_width="48dp" + android:layout_height="48dp" + android:contentDescription="@null" /> - <TextView - android:id="@+id/subtitle" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@style/TextAppearance.AuthCredential.Subtitle"/> + <TextView + android:id="@+id/title" + style="@style/TextAppearance.AuthCredential.Title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> - <TextView - android:id="@+id/description" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@style/TextAppearance.AuthCredential.Description"/> + <TextView + android:id="@+id/subtitle" + style="@style/TextAppearance.AuthCredential.Subtitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> - <Space - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_weight="1"/> + <TextView + android:id="@+id/description" + style="@style/TextAppearance.AuthCredential.Description" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> - <ImeAwareEditText - android:id="@+id/lockPassword" - android:layout_width="208dp" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:minHeight="48dp" - android:gravity="center" - android:inputType="textPassword" - android:maxLength="500" - android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii" - style="@style/TextAppearance.AuthCredential.PasswordEntry"/> + </LinearLayout> - <TextView - android:id="@+id/error" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@style/TextAppearance.AuthCredential.Error"/> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:orientation="vertical" + android:layout_alignParentBottom="true"> + + <ImeAwareEditText + android:id="@+id/lockPassword" + style="@style/TextAppearance.AuthCredential.PasswordEntry" + android:layout_width="208dp" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii" + android:inputType="textPassword" + android:minHeight="48dp" /> + + <TextView + android:id="@+id/error" + style="@style/TextAppearance.AuthCredential.Error" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> - <Space - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_weight="5"/> + </RelativeLayout> </com.android.systemui.biometrics.AuthCredentialPasswordView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml index 4939ea2c99ee..470298ea0b13 100644 --- a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml +++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml @@ -22,76 +22,81 @@ android:gravity="center_horizontal" android:elevation="@dimen/biometric_dialog_elevation"> - <Space - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_weight="1"/> - - <ImageView - android:id="@+id/icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content"/> - - <TextView - android:id="@+id/title" + <RelativeLayout android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@style/TextAppearance.AuthCredential.Title"/> + android:layout_height="match_parent" + android:orientation="vertical"> - <TextView - android:id="@+id/subtitle" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@style/TextAppearance.AuthCredential.Subtitle"/> + <LinearLayout + android:id="@+id/auth_credential_header" + style="@style/AuthCredentialHeaderStyle" + android:layout_width="match_parent" + android:layout_height="wrap_content"> - <TextView - android:id="@+id/description" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@style/TextAppearance.AuthCredential.Description"/> + <ImageView + android:id="@+id/icon" + android:layout_width="48dp" + android:layout_height="48dp" + android:contentDescription="@null" /> - <Space - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_weight="1"/> + <TextView + android:id="@+id/title" + style="@style/TextAppearance.AuthCredential.Title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:gravity="center" - android:paddingLeft="0dp" - android:paddingRight="0dp" - android:paddingTop="0dp" - android:paddingBottom="16dp" - android:clipToPadding="false"> - - <FrameLayout - android:layout_width="wrap_content" - android:layout_height="0dp" - android:layout_weight="1" - style="@style/LockPatternContainerStyle"> - - <com.android.internal.widget.LockPatternView - android:id="@+id/lockPattern" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_gravity="center" - style="@style/LockPatternStyleBiometricPrompt"/> + <TextView + android:id="@+id/subtitle" + style="@style/TextAppearance.AuthCredential.Subtitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <TextView + android:id="@+id/description" + style="@style/TextAppearance.AuthCredential.Description" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/auth_credential_header" + android:gravity="center" + android:orientation="vertical" + android:paddingBottom="16dp" + android:paddingTop="60dp"> + + <FrameLayout + style="@style/LockPatternContainerStyle" + android:layout_width="wrap_content" + android:layout_height="0dp" + android:layout_weight="1"> + + <com.android.internal.widget.LockPatternView + android:id="@+id/lockPattern" + style="@style/LockPatternStyle" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center" /> - </FrameLayout> + </FrameLayout> - <TextView - android:id="@+id/error" + </LinearLayout> + + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - style="@style/TextAppearance.AuthCredential.Error"/> + android:layout_alignParentBottom="true"> + + <TextView + android:id="@+id/error" + style="@style/TextAppearance.AuthCredential.Error" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> - </LinearLayout> + </LinearLayout> - <Space - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_weight="1"/> + </RelativeLayout> </com.android.systemui.biometrics.AuthCredentialPatternView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml index e90a6446c47b..c575855d9af8 100644 --- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml +++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml @@ -67,7 +67,6 @@ <ProgressBar android:id="@+id/wifi_searching_progress" - android:indeterminate="true" android:layout_width="340dp" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 4dc52e7540be..e1afd3fe6a30 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -630,7 +630,7 @@ <!-- QuickSettings: Brightness dialog title [CHAR LIMIT=NONE] --> <string name="quick_settings_brightness_dialog_title">Brightness</string> <!-- QuickSettings: Label for the toggle that controls whether display inversion is enabled. [CHAR LIMIT=NONE] --> - <string name="quick_settings_inversion_label">Invert colors</string> + <string name="quick_settings_inversion_label">Color inversion</string> <!-- QuickSettings: Label for the toggle that controls whether display color correction is enabled. [CHAR LIMIT=NONE] --> <!-- QuickSettings: Control panel: Label for button that navigates to settings. [CHAR LIMIT=NONE] --> <string name="quick_settings_more_settings">More settings</string> @@ -2037,8 +2037,8 @@ <string name="magnification_mode_switch_click_label">Switch</string> <!-- Accessibility floating menu strings --> - <!-- Message for the accessibility floating button migration tooltip. It shows when the user use gestural navigation then upgrade their system. It will tell the user the accessibility gesture had been replaced by accessibility floating button. [CHAR LIMIT=100] --> - <string name="accessibility_floating_button_migration_tooltip">Accessibility button replaced the accessibility gesture\n\n<annotation id="link">View settings</annotation></string> + <!-- Message for the accessibility floating button migration tooltip. It shows when the user use gestural navigation then upgrade their system. It will tell the user they could customize or replace the floating button in Settings. [CHAR LIMIT=100] --> + <string name="accessibility_floating_button_migration_tooltip">Tap to open accessibility features. Customize or replace this button in Settings.\n\n<annotation id="link">View settings</annotation></string> <!-- Message for the accessibility floating button docking tooltip. It shows when the user first time drag the button. It will tell the user about docking behavior. [CHAR LIMIT=70] --> <string name="accessibility_floating_button_docking_tooltip">Move button to the edge to hide it temporarily</string> <!-- Action in accessibility menu to move the accessibility floating button to the top left of the screen. [CHAR LIMIT=30] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 92985ded8a91..0cfd8d6d0716 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -200,9 +200,9 @@ <style name="TextAppearance.DeviceManagementDialog.Title" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle"/> - <style name="TextAppearance.AuthCredential"> + <style name="TextAppearance.AuthCredential" + parent="@android:style/TextAppearance.DeviceDefault"> <item name="android:accessibilityLiveRegion">polite</item> - <item name="android:gravity">center_horizontal</item> <item name="android:textAlignment">gravity</item> <item name="android:layout_gravity">top</item> <item name="android:textColor">?android:attr/textColorPrimary</item> @@ -210,44 +210,57 @@ <style name="TextAppearance.AuthCredential.Title"> <item name="android:fontFamily">google-sans</item> - <item name="android:paddingTop">12dp</item> - <item name="android:paddingHorizontal">24dp</item> - <item name="android:textSize">24sp</item> + <item name="android:layout_marginTop">20dp</item> + <item name="android:textSize">36sp</item> </style> <style name="TextAppearance.AuthCredential.Subtitle"> <item name="android:fontFamily">google-sans</item> - <item name="android:paddingTop">8dp</item> - <item name="android:paddingHorizontal">24dp</item> - <item name="android:textSize">16sp</item> + <item name="android:layout_marginTop">20dp</item> + <item name="android:textSize">18sp</item> </style> <style name="TextAppearance.AuthCredential.Description"> <item name="android:fontFamily">google-sans</item> - <item name="android:paddingTop">8dp</item> - <item name="android:paddingHorizontal">24dp</item> - <item name="android:textSize">14sp</item> + <item name="android:layout_marginTop">20dp</item> + <item name="android:textSize">16sp</item> </style> <style name="TextAppearance.AuthCredential.Error"> <item name="android:paddingTop">6dp</item> + <item name="android:paddingBottom">18dp</item> <item name="android:paddingHorizontal">24dp</item> <item name="android:textSize">14sp</item> <item name="android:textColor">?android:attr/colorError</item> + <item name="android:gravity">center</item> </style> - <style name="TextAppearance.AuthCredential.PasswordEntry" parent="@android:style/TextAppearance.DeviceDefault"> + <style name="TextAppearance.AuthCredential.PasswordEntry"> <item name="android:gravity">center</item> <item name="android:singleLine">true</item> <item name="android:textColor">?android:attr/colorForeground</item> <item name="android:textSize">24sp</item> </style> + <style name="AuthCredentialHeaderStyle"> + <item name="android:paddingStart">48dp</item> + <item name="android:paddingEnd">24dp</item> + <item name="android:paddingTop">28dp</item> + <item name="android:paddingBottom">20dp</item> + <item name="android:orientation">vertical</item> + <item name="android:layout_gravity">top</item> + </style> + <style name="DeviceManagementDialogTitle"> <item name="android:gravity">center</item> <item name="android:textAppearance">@style/TextAppearance.DeviceManagementDialog.Title</item> </style> + <style name="AuthCredentialPasswordTheme" parent="@style/Theme.MaterialComponents.DayNight"> + <item name="colorPrimary">?android:attr/colorPrimary</item> + <item name="colorPrimaryDark">?android:attr/colorPrimary</item> + </style> + <style name="TextAppearance.DeviceManagementDialog.Content" parent="@*android:style/TextAppearance.DeviceDefault.Subhead"/> <style name="BaseBrightnessDialogContainer" parent="@style/Theme.SystemUI"> @@ -307,9 +320,8 @@ <item name="android:maxWidth">420dp</item> <item name="android:minHeight">0dp</item> <item name="android:minWidth">0dp</item> - <item name="android:paddingBottom">0dp</item> - <item name="android:paddingHorizontal">44dp</item> - <item name="android:paddingTop">0dp</item> + <item name="android:paddingHorizontal">60dp</item> + <item name="android:paddingBottom">40dp</item> </style> <style name="LockPatternStyle"> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index ba6771644db1..237ca71027b5 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -51,6 +51,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.database.ContentObserver; +import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; @@ -335,6 +336,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mLockIconPressed; private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private final Executor mBackgroundExecutor; + private SensorPrivacyManager mSensorPrivacyManager; + private int mFaceAuthUserId; /** * Short delay before restarting fingerprint authentication after a successful try. This should @@ -1016,6 +1019,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // Error is always the end of authentication lifecycle mFaceCancelSignal = null; + boolean cameraPrivacyEnabled = false; + if (mSensorPrivacyManager != null) { + cameraPrivacyEnabled = mSensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, + mFaceAuthUserId); + } if (msgId == FaceManager.FACE_ERROR_CANCELED && mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) { @@ -1025,7 +1034,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab setFaceRunningState(BIOMETRIC_STATE_STOPPED); } - if (msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE + final boolean isHwUnavailable = msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE; + + if (isHwUnavailable || msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS) { if (mHardwareFaceUnavailableRetryCount < HAL_ERROR_RETRY_MAX) { mHardwareFaceUnavailableRetryCount++; @@ -1041,6 +1052,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab requireStrongAuthIfAllLockedOut(); } + if (isHwUnavailable && cameraPrivacyEnabled) { + errString = mContext.getString(R.string.kg_face_sensor_privacy_enabled); + } + for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -1816,6 +1831,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLockPatternUtils = lockPatternUtils; mAuthController = authController; dumpManager.registerDumpable(getClass().getName(), this); + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); mHandler = new Handler(mainLooper) { @Override @@ -2517,6 +2533,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // This would need to be updated for multi-sensor devices final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty() && mFaceSensorProperties.get(0).supportsFaceDetection; + mFaceAuthUserId = userId; if (isEncryptedOrLockdown(userId) && supportsFaceDetection) { mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, userId); } else { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 1226dca1f306..d8f6a01398d1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -31,6 +31,7 @@ import android.content.IntentFilter; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.PointF; +import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator.Modality; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager.Authenticators; @@ -89,6 +90,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, private static final String TAG = "AuthController"; private static final boolean DEBUG = true; + private static final int SENSOR_PRIVACY_DELAY = 500; private final Handler mHandler = new Handler(Looper.getMainLooper()); private final CommandQueue mCommandQueue; @@ -122,6 +124,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, @Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps; @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser; + private SensorPrivacyManager mSensorPrivacyManager; private class BiometricTaskStackListener extends TaskStackListener { @Override @@ -492,6 +495,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); context.registerReceiver(mBroadcastReceiver, filter); + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); } private void updateFingerprintLocation() { @@ -642,10 +646,16 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT) || (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT); + boolean isCameraPrivacyEnabled = false; + if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE + && mSensorPrivacyManager.isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, + mCurrentDialogArgs.argi1 /* userId */)) { + isCameraPrivacyEnabled = true; + } // TODO(b/141025588): Create separate methods for handling hard and soft errors. final boolean isSoftError = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED - || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT); - + || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT + || isCameraPrivacyEnabled); if (mCurrentDialog != null) { if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) { if (DEBUG) Log.d(TAG, "onBiometricError, lockout"); @@ -655,12 +665,23 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, ? mContext.getString(R.string.biometric_not_recognized) : getErrorString(modality, error, vendorCode); if (DEBUG) Log.d(TAG, "onBiometricError, soft error: " + errorMessage); - mCurrentDialog.onAuthenticationFailed(modality, errorMessage); + // The camera privacy error can return before the prompt initializes its state, + // causing the prompt to appear to endlessly authenticate. Add a small delay + // to stop this. + if (isCameraPrivacyEnabled) { + mHandler.postDelayed(() -> { + mCurrentDialog.onAuthenticationFailed(modality, + mContext.getString(R.string.face_sensor_privacy_enabled)); + }, SENSOR_PRIVACY_DELAY); + } else { + mCurrentDialog.onAuthenticationFailed(modality, errorMessage); + } } else { final String errorMessage = getErrorString(modality, error, vendorCode); if (DEBUG) Log.d(TAG, "onBiometricError, hard error: " + errorMessage); mCurrentDialog.onError(modality, errorMessage); } + } else { Log.w(TAG, "onBiometricError callback but dialog is gone"); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index 26c89ff83076..ba4257f52892 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -78,7 +78,7 @@ public class InternetDialog extends SystemUIDialog implements private static final String TAG = "InternetDialog"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - static final long PROGRESS_DELAY_MS = 2000L; + static final long PROGRESS_DELAY_MS = 1500L; private final Handler mHandler; private final Executor mBackgroundExecutor; @@ -137,6 +137,8 @@ public class InternetDialog extends SystemUIDialog implements protected WifiEntry mConnectedWifiEntry; @VisibleForTesting protected int mWifiEntriesCount; + @VisibleForTesting + protected boolean mHasMoreEntry; // Wi-Fi scanning progress bar protected boolean mIsProgressBarVisible; @@ -464,8 +466,7 @@ public class InternetDialog extends SystemUIDialog implements } mWifiRecyclerView.setMinimumHeight(mWifiNetworkHeight * getWifiListMaxCount()); mWifiRecyclerView.setVisibility(View.VISIBLE); - final boolean showSeeAll = mConnectedWifiEntry != null || mWifiEntriesCount > 0; - mSeeAllLayout.setVisibility(showSeeAll ? View.VISIBLE : View.INVISIBLE); + mSeeAllLayout.setVisibility(mHasMoreEntry ? View.VISIBLE : View.INVISIBLE); } @VisibleForTesting @@ -549,9 +550,13 @@ public class InternetDialog extends SystemUIDialog implements } private void setProgressBarVisible(boolean visible) { + if (mIsProgressBarVisible == visible) { + return; + } mIsProgressBarVisible = visible; - mProgressBar.setVisibility(mIsProgressBarVisible ? View.VISIBLE : View.GONE); - mDivider.setVisibility(mIsProgressBarVisible ? View.GONE : View.VISIBLE); + mProgressBar.setVisibility(visible ? View.VISIBLE : View.GONE); + mProgressBar.setIndeterminate(visible); + mDivider.setVisibility(visible ? View.GONE : View.VISIBLE); mInternetDialogSubTitle.setText(getSubtitleText()); } @@ -651,13 +656,14 @@ public class InternetDialog extends SystemUIDialog implements @Override @WorkerThread public void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries, - @Nullable WifiEntry connectedEntry) { + @Nullable WifiEntry connectedEntry, boolean hasMoreEntry) { // Should update the carrier network layout when it is connected under airplane mode ON. boolean shouldUpdateCarrierNetwork = mMobileNetworkLayout.getVisibility() == View.VISIBLE && mInternetDialogController.isAirplaneModeEnabled(); mHandler.post(() -> { mConnectedWifiEntry = connectedEntry; mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size(); + mHasMoreEntry = hasMoreEntry; updateDialog(shouldUpdateCarrierNetwork /* shouldUpdateMobileNetwork */); mAdapter.setWifiEntries(wifiEntries, mWifiEntriesCount); mAdapter.notifyDataSetChanged(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index 6f63a08c6c0f..1fee1b430073 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -348,6 +348,10 @@ public class InternetDialogController implements AccessPointController.AccessPoi return mContext.getText(SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS); } + if (isCarrierNetworkActive()) { + return mContext.getText(SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE); + } + // Sub-Title: // show non_carrier_network_unavailable // - while Wi-Fi on + no Wi-Fi item @@ -879,20 +883,25 @@ public class InternetDialogController implements AccessPointController.AccessPoi mConnectedEntry = null; mWifiEntriesCount = 0; if (mCallback != null) { - mCallback.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */); + mCallback.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */, + false /* hasMoreEntry */); } return; } + boolean hasMoreEntry = false; int count = MAX_WIFI_ENTRY_COUNT; if (mHasEthernet) { count -= 1; } - if (hasActiveSubId()) { + if (hasActiveSubId() || isCarrierNetworkActive()) { count -= 1; } - if (count > accessPoints.size()) { - count = accessPoints.size(); + final int wifiTotalCount = accessPoints.size(); + if (count > wifiTotalCount) { + count = wifiTotalCount; + } else if (count < wifiTotalCount) { + hasMoreEntry = true; } WifiEntry connectedEntry = null; @@ -909,7 +918,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi mWifiEntriesCount = wifiEntries.size(); if (mCallback != null) { - mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry); + mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry, hasMoreEntry); } } @@ -1060,7 +1069,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi void dismissDialog(); void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries, - @Nullable WifiEntry connectedEntry); + @Nullable WifiEntry connectedEntry, boolean hasMoreEntry); } void makeOverlayToast(int stringId) { diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt index c50365f1bf38..71c5fad5322e 100644 --- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt @@ -15,7 +15,8 @@ import com.android.systemui.statusbar.phone.SystemUIDialog class SensorUseDialog( context: Context, val sensor: Int, - val clickListener: DialogInterface.OnClickListener + val clickListener: DialogInterface.OnClickListener, + val dismissListener: DialogInterface.OnDismissListener ) : SystemUIDialog(context) { // TODO move to onCreate (b/200815309) @@ -69,6 +70,8 @@ class SensorUseDialog( context.getString(com.android.internal.R.string .cancel), clickListener) + setOnDismissListener(dismissListener) + setCancelable(false) } } diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt index b0071d92481d..dae375ad7cc7 100644 --- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt @@ -50,7 +50,7 @@ class SensorUseStartedActivity @Inject constructor( private val keyguardStateController: KeyguardStateController, private val keyguardDismissUtil: KeyguardDismissUtil, @Background private val bgHandler: Handler -) : Activity(), DialogInterface.OnClickListener { +) : Activity(), DialogInterface.OnClickListener, DialogInterface.OnDismissListener { companion object { private val LOG_TAG = SensorUseStartedActivity::class.java.simpleName @@ -120,7 +120,7 @@ class SensorUseStartedActivity @Inject constructor( } } - mDialog = SensorUseDialog(this, sensor, this) + mDialog = SensorUseDialog(this, sensor, this, this) mDialog!!.show() } @@ -212,4 +212,8 @@ class SensorUseStartedActivity @Inject constructor( .suppressSensorPrivacyReminders(sensor, suppressed) } } + + override fun onDismiss(dialog: DialogInterface?) { + finish() + } } 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 5477c19261d1..9167b08ecd0c 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 @@ -682,6 +682,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable boolean showFooterView = (showDismissView || getVisibleNotificationCount() > 0) && mIsCurrentUserSetup // see: b/193149550 && mStatusBarState != StatusBarState.KEYGUARD + && mQsExpansionFraction != 1 && !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying() && !mIsRemoteInputActive; boolean showHistory = Settings.Secure.getIntForUser(mContext.getContentResolver(), @@ -4766,6 +4767,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setQsExpansionFraction(float qsExpansionFraction) { + boolean footerAffected = mQsExpansionFraction != qsExpansionFraction + && (mQsExpansionFraction == 1 || qsExpansionFraction == 1); mQsExpansionFraction = qsExpansionFraction; updateUseRoundedRectClipping(); @@ -4774,6 +4777,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (mOwnScrollY > 0) { setOwnScrollY((int) MathUtils.lerp(mOwnScrollY, 0, mQsExpansionFraction)); } + if (footerAffected) { + updateFooter(); + } } @ShadeViewRefactor(RefactorComponent.COORDINATOR) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java index 95e7a98c0a60..6dca2a73b57b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java @@ -159,7 +159,6 @@ public class InternetDialogControllerTest extends SysuiTestCase { mAccessPoints.add(mWifiEntry1); when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry); when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID}); - when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry); when(mToastFactory.createToast(any(), anyString(), anyString(), anyInt(), anyInt())) .thenReturn(mSystemUIToast); when(mSystemUIToast.getView()).thenReturn(mToastView); @@ -335,6 +334,17 @@ public class InternetDialogControllerTest extends SysuiTestCase { } @Test + public void getSubtitleText_withCarrierNetworkActiveOnly_returnNoOtherAvailable() { + fakeAirplaneModeEnabled(false); + when(mWifiManager.isWifiEnabled()).thenReturn(true); + mInternetDialogController.onAccessPointsChanged(null /* accessPoints */); + when(mMergedCarrierEntry.isDefaultNetwork()).thenReturn(true); + + assertThat(mInternetDialogController.getSubtitleText(false)) + .isEqualTo(getResourcesString("non_carrier_network_unavailable")); + } + + @Test public void getWifiDetailsSettingsIntent_withNoKey_returnNull() { assertThat(mInternetDialogController.getWifiDetailsSettingsIntent(null)).isNull(); } @@ -400,7 +410,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { mInternetDialogController.onAccessPointsChanged(null /* accessPoints */); - verify(mInternetDialogCallback, never()).onAccessPointsChanged(any(), any()); + verify(mInternetDialogCallback, never()).onAccessPointsChanged(any(), any(), anyBoolean()); } @Test @@ -409,8 +419,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mInternetDialogController.onAccessPointsChanged(null /* accessPoints */); - verify(mInternetDialogCallback) - .onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */); + verify(mInternetDialogCallback).onAccessPointsChanged(null /* wifiEntries */, + null /* connectedEntry */, false /* hasMoreEntry */); } @Test @@ -423,7 +433,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mInternetDialogController.onAccessPointsChanged(mAccessPoints); mWifiEntries.clear(); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + false /* hasMoreEntry */); } @Test @@ -437,8 +448,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mWifiEntries.clear(); mWifiEntries.add(mWifiEntry1); - verify(mInternetDialogCallback) - .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, + null /* connectedEntry */, false /* hasMoreEntry */); } @Test @@ -453,7 +464,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mWifiEntries.clear(); mWifiEntries.add(mWifiEntry1); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + false /* hasMoreEntry */); } @Test @@ -470,7 +482,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mWifiEntries.clear(); mWifiEntries.add(mWifiEntry1); mWifiEntries.add(mWifiEntry2); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + false /* hasMoreEntry */); } @Test @@ -489,7 +502,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mWifiEntries.add(mWifiEntry1); mWifiEntries.add(mWifiEntry2); mWifiEntries.add(mWifiEntry3); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + false /* hasMoreEntry */); // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one. reset(mInternetDialogCallback); @@ -498,7 +512,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mInternetDialogController.onAccessPointsChanged(mAccessPoints); mWifiEntries.remove(mWifiEntry3); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + true /* hasMoreEntry */); } @Test @@ -518,7 +533,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mWifiEntries.add(mWifiEntry1); mWifiEntries.add(mWifiEntry2); mWifiEntries.add(mWifiEntry3); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + true /* hasMoreEntry */); // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one. reset(mInternetDialogCallback); @@ -527,7 +543,38 @@ public class InternetDialogControllerTest extends SysuiTestCase { mInternetDialogController.onAccessPointsChanged(mAccessPoints); mWifiEntries.remove(mWifiEntry3); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + true /* hasMoreEntry */); + } + + @Test + public void onAccessPointsChanged_oneCarrierWifiAndFourOthers_callbackCutMore() { + reset(mInternetDialogCallback); + fakeAirplaneModeEnabled(true); + when(mMergedCarrierEntry.isDefaultNetwork()).thenReturn(true); + mAccessPoints.clear(); + mAccessPoints.add(mWifiEntry1); + mAccessPoints.add(mWifiEntry2); + mAccessPoints.add(mWifiEntry3); + mAccessPoints.add(mWifiEntry4); + + mInternetDialogController.onAccessPointsChanged(mAccessPoints); + + mWifiEntries.clear(); + mWifiEntries.add(mWifiEntry1); + mWifiEntries.add(mWifiEntry2); + mWifiEntries.add(mWifiEntry3); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, + null /* connectedEntry */, true /* hasMoreEntry */); + + // Turn off airplane mode to has carrier WiFi, then Wi-Fi entries will keep the same. + reset(mInternetDialogCallback); + fakeAirplaneModeEnabled(false); + + mInternetDialogController.onAccessPointsChanged(mAccessPoints); + + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, + null /* connectedEntry */, true /* hasMoreEntry */); } @Test @@ -547,8 +594,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mWifiEntries.add(mWifiEntry2); mWifiEntries.add(mWifiEntry3); mWifiEntries.add(mWifiEntry4); - verify(mInternetDialogCallback) - .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, + null /* connectedEntry */, false /* hasMoreEntry */); // If the Ethernet exists, then Wi-Fi entries will cut last one. reset(mInternetDialogCallback); @@ -557,8 +604,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mInternetDialogController.onAccessPointsChanged(mAccessPoints); mWifiEntries.remove(mWifiEntry4); - verify(mInternetDialogCallback) - .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, + null /* connectedEntry */, true /* hasMoreEntry */); // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one. reset(mInternetDialogCallback); @@ -567,8 +614,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mInternetDialogController.onAccessPointsChanged(mAccessPoints); mWifiEntries.remove(mWifiEntry3); - verify(mInternetDialogCallback) - .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, + null /* connectedEntry */, true /* hasMoreEntry */); } @Test @@ -584,8 +631,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mWifiEntries.clear(); mWifiEntries.add(mWifiEntry1); - verify(mInternetDialogCallback) - .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, + null /* connectedEntry */, false /* hasMoreEntry */); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java index 0cf063f5db39..651bcdef9978 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java @@ -316,6 +316,20 @@ public class InternetDialogTest extends SysuiTestCase { } @Test + public void updateDialog_wifiOnAndOneWifiEntry_showWifiListAndSeeAllArea() { + // The precondition WiFi ON is already in setUp() + mInternetDialog.mConnectedWifiEntry = null; + mInternetDialog.mWifiEntriesCount = 1; + + mInternetDialog.updateDialog(false); + + assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE); + // Show a blank block to fix the dialog height even if there is no WiFi list + assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE); + } + + @Test public void updateDialog_wifiOnAndHasConnectedWifi_showAllWifiAndSeeAllArea() { // The preconditions WiFi ON and WiFi entries are already in setUp() mInternetDialog.mWifiEntriesCount = 0; @@ -325,13 +339,15 @@ public class InternetDialogTest extends SysuiTestCase { assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE); // Show a blank block to fix the dialog height even if there is no WiFi list assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE); } @Test - public void updateDialog_wifiOnAndHasWifiList_showWifiListAndSeeAll() { + public void updateDialog_wifiOnAndHasMaxWifiList_showWifiListAndSeeAll() { // The preconditions WiFi ON and WiFi entries are already in setUp() mInternetDialog.mConnectedWifiEntry = null; + mInternetDialog.mWifiEntriesCount = MAX_WIFI_ENTRY_COUNT; + mInternetDialog.mHasMoreEntry = true; mInternetDialog.updateDialog(false); @@ -343,6 +359,8 @@ public class InternetDialogTest extends SysuiTestCase { @Test public void updateDialog_wifiOnAndHasBothWifiEntry_showBothWifiEntryAndSeeAll() { // The preconditions WiFi ON and WiFi entries are already in setUp() + mInternetDialog.mWifiEntriesCount = MAX_WIFI_ENTRY_COUNT - 1; + mInternetDialog.mHasMoreEntry = true; mInternetDialog.updateDialog(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt index 793851160dc2..89435ae164b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -6,6 +6,7 @@ import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import android.util.DisplayMetrics import com.android.systemui.ExpandHelper +import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.media.MediaHierarchyManager @@ -81,6 +82,8 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { mDependency, TestableLooper.get(this)) row = helper.createRow() + context.getOrCreateTestableResources() + .addOverride(R.bool.config_use_split_notification_shade, false) transitionController = LockscreenShadeTransitionController( statusBarStateController = statusbarStateController, lockscreenGestureLogger = lockscreenGestureLogger, diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index f42870b4b734..758cf7a7d430 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -1036,7 +1036,8 @@ public class BiometricService extends SystemService { promptInfo.setAuthenticators(authenticators); return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors, - userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */); + userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */, + getContext()); } /** @@ -1375,7 +1376,8 @@ public class BiometricService extends SystemService { try { final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo, - opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists()); + opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(), + getContext()); final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus(); @@ -1383,8 +1385,11 @@ public class BiometricService extends SystemService { + "), status(" + preAuthStatus.second + "), preAuthInfo: " + preAuthInfo + " requestId: " + requestId + " promptInfo.isIgnoreEnrollmentState: " + promptInfo.isIgnoreEnrollmentState()); - - if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) { + // BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED is added so that BiometricPrompt can + // be shown for this case. + if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS + || preAuthStatus.second + == BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED) { // If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but // CREDENTIAL is requested and available, set the bundle to only request // CREDENTIAL. diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java index a5a3542f49c7..05c3f68f355b 100644 --- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java +++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java @@ -26,6 +26,8 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.app.admin.DevicePolicyManager; import android.app.trust.ITrustManager; +import android.content.Context; +import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.PromptInfo; @@ -59,6 +61,7 @@ class PreAuthInfo { static final int CREDENTIAL_NOT_ENROLLED = 9; static final int BIOMETRIC_LOCKOUT_TIMED = 10; static final int BIOMETRIC_LOCKOUT_PERMANENT = 11; + static final int BIOMETRIC_SENSOR_PRIVACY_ENABLED = 12; @IntDef({AUTHENTICATOR_OK, BIOMETRIC_NO_HARDWARE, BIOMETRIC_DISABLED_BY_DEVICE_POLICY, @@ -69,7 +72,8 @@ class PreAuthInfo { BIOMETRIC_NOT_ENABLED_FOR_APPS, CREDENTIAL_NOT_ENROLLED, BIOMETRIC_LOCKOUT_TIMED, - BIOMETRIC_LOCKOUT_PERMANENT}) + BIOMETRIC_LOCKOUT_PERMANENT, + BIOMETRIC_SENSOR_PRIVACY_ENABLED}) @Retention(RetentionPolicy.SOURCE) @interface AuthenticatorStatus {} @@ -84,13 +88,15 @@ class PreAuthInfo { final boolean credentialAvailable; final boolean confirmationRequested; final boolean ignoreEnrollmentState; + final int userId; + final Context context; static PreAuthInfo create(ITrustManager trustManager, DevicePolicyManager devicePolicyManager, BiometricService.SettingObserver settingObserver, List<BiometricSensor> sensors, int userId, PromptInfo promptInfo, String opPackageName, - boolean checkDevicePolicyManager) + boolean checkDevicePolicyManager, Context context) throws RemoteException { final boolean confirmationRequested = promptInfo.isConfirmationRequested(); @@ -116,14 +122,22 @@ class PreAuthInfo { devicePolicyManager, settingObserver, sensor, userId, opPackageName, checkDevicePolicyManager, requestedStrength, promptInfo.getAllowedSensorIds(), - promptInfo.isIgnoreEnrollmentState()); + promptInfo.isIgnoreEnrollmentState(), + context); Slog.d(TAG, "Package: " + opPackageName + " Sensor ID: " + sensor.id + " Modality: " + sensor.modality + " Status: " + status); - if (status == AUTHENTICATOR_OK) { + // A sensor with privacy enabled will still be eligible to + // authenticate with biometric prompt. This is so the framework can display + // a sensor privacy error message to users after briefly showing the + // Biometric Prompt. + // + // Note: if only a certain sensor is required and the privacy is enabled, + // canAuthenticate() will return false. + if (status == AUTHENTICATOR_OK || status == BIOMETRIC_SENSOR_PRIVACY_ENABLED) { eligibleSensors.add(sensor); } else { ineligibleSensors.add(new Pair<>(sensor, status)); @@ -133,7 +147,7 @@ class PreAuthInfo { return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested, eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested, - promptInfo.isIgnoreEnrollmentState()); + promptInfo.isIgnoreEnrollmentState(), userId, context); } /** @@ -149,7 +163,7 @@ class PreAuthInfo { BiometricSensor sensor, int userId, String opPackageName, boolean checkDevicePolicyManager, int requestedStrength, @NonNull List<Integer> requestedSensorIds, - boolean ignoreEnrollmentState) { + boolean ignoreEnrollmentState, Context context) { if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) { return BIOMETRIC_NO_HARDWARE; @@ -175,6 +189,16 @@ class PreAuthInfo { && !ignoreEnrollmentState) { return BIOMETRIC_NOT_ENROLLED; } + final SensorPrivacyManager sensorPrivacyManager = context + .getSystemService(SensorPrivacyManager.class); + + if (sensorPrivacyManager != null && sensor.modality == TYPE_FACE) { + if (sensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId)) { + return BIOMETRIC_SENSOR_PRIVACY_ENABLED; + } + } + final @LockoutTracker.LockoutMode int lockoutMode = sensor.impl.getLockoutModeForUser(userId); @@ -243,7 +267,8 @@ class PreAuthInfo { private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested, boolean credentialRequested, List<BiometricSensor> eligibleSensors, List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable, - boolean confirmationRequested, boolean ignoreEnrollmentState) { + boolean confirmationRequested, boolean ignoreEnrollmentState, int userId, + Context context) { mBiometricRequested = biometricRequested; mBiometricStrengthRequested = biometricStrengthRequested; this.credentialRequested = credentialRequested; @@ -253,6 +278,8 @@ class PreAuthInfo { this.credentialAvailable = credentialAvailable; this.confirmationRequested = confirmationRequested; this.ignoreEnrollmentState = ignoreEnrollmentState; + this.userId = userId; + this.context = context; } private Pair<BiometricSensor, Integer> calculateErrorByPriority() { @@ -280,15 +307,35 @@ class PreAuthInfo { private Pair<Integer, Integer> getInternalStatus() { @AuthenticatorStatus final int status; @BiometricAuthenticator.Modality int modality = TYPE_NONE; + + final SensorPrivacyManager sensorPrivacyManager = context + .getSystemService(SensorPrivacyManager.class); + + boolean cameraPrivacyEnabled = false; + if (sensorPrivacyManager != null) { + cameraPrivacyEnabled = sensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId); + } + if (mBiometricRequested && credentialRequested) { if (credentialAvailable || !eligibleSensors.isEmpty()) { - status = AUTHENTICATOR_OK; - if (credentialAvailable) { - modality |= TYPE_CREDENTIAL; - } for (BiometricSensor sensor : eligibleSensors) { modality |= sensor.modality; } + + if (credentialAvailable) { + modality |= TYPE_CREDENTIAL; + status = AUTHENTICATOR_OK; + } else if (modality == TYPE_FACE && cameraPrivacyEnabled) { + // If the only modality requested is face, credential is unavailable, + // and the face sensor privacy is enabled then return + // BIOMETRIC_SENSOR_PRIVACY_ENABLED. + // + // Note: This sensor will still be eligible for calls to authenticate. + status = BIOMETRIC_SENSOR_PRIVACY_ENABLED; + } else { + status = AUTHENTICATOR_OK; + } } else { // Pick the first sensor error if it exists if (!ineligibleSensors.isEmpty()) { @@ -302,10 +349,18 @@ class PreAuthInfo { } } else if (mBiometricRequested) { if (!eligibleSensors.isEmpty()) { - status = AUTHENTICATOR_OK; - for (BiometricSensor sensor : eligibleSensors) { - modality |= sensor.modality; - } + for (BiometricSensor sensor : eligibleSensors) { + modality |= sensor.modality; + } + if (modality == TYPE_FACE && cameraPrivacyEnabled) { + // If the only modality requested is face and the privacy is enabled + // then return BIOMETRIC_SENSOR_PRIVACY_ENABLED. + // + // Note: This sensor will still be eligible for calls to authenticate. + status = BIOMETRIC_SENSOR_PRIVACY_ENABLED; + } else { + status = AUTHENTICATOR_OK; + } } else { // Pick the first sensor error if it exists if (!ineligibleSensors.isEmpty()) { @@ -326,9 +381,9 @@ class PreAuthInfo { Slog.e(TAG, "No authenticators requested"); status = BIOMETRIC_NO_HARDWARE; } - Slog.d(TAG, "getCanAuthenticateInternal Modality: " + modality + " AuthenticatorStatus: " + status); + return new Pair<>(modality, status); } @@ -362,6 +417,7 @@ class PreAuthInfo { case CREDENTIAL_NOT_ENROLLED: case BIOMETRIC_LOCKOUT_TIMED: case BIOMETRIC_LOCKOUT_PERMANENT: + case BIOMETRIC_SENSOR_PRIVACY_ENABLED: break; case BIOMETRIC_DISABLED_BY_DEVICE_POLICY: diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index 4f7c6b012c23..0e2582c23b86 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -33,6 +33,7 @@ import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_LOCKOUT_TIMED; import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENABLED_FOR_APPS; import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENROLLED; import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NO_HARDWARE; +import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_SENSOR_PRIVACY_ENABLED; import static com.android.server.biometrics.PreAuthInfo.CREDENTIAL_NOT_ENROLLED; import android.annotation.NonNull; @@ -278,6 +279,9 @@ public class Utils { case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT: biometricManagerCode = BiometricManager.BIOMETRIC_SUCCESS; break; + case BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED: + biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE; + break; default: Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode); biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE; @@ -337,7 +341,8 @@ public class Utils { case BIOMETRIC_LOCKOUT_PERMANENT: return BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT; - + case BIOMETRIC_SENSOR_PRIVACY_ENABLED: + return BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED; case BIOMETRIC_DISABLED_BY_DEVICE_POLICY: case BIOMETRIC_HARDWARE_NOT_DETECTED: case BIOMETRIC_NOT_ENABLED_FOR_APPS: diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index 97d791b7e1c9..4131ae127ab2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.app.NotificationManager; import android.content.Context; import android.content.res.Resources; +import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFaceConstants; @@ -56,6 +57,7 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements @NonNull private final LockoutCache mLockoutCache; @Nullable private final NotificationManager mNotificationManager; @Nullable private ICancellationSignal mCancellationSignal; + @Nullable private SensorPrivacyManager mSensorPrivacyManager; private final int[] mBiometricPromptIgnoreList; private final int[] mBiometricPromptIgnoreListVendor; @@ -81,6 +83,7 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements mUsageStats = usageStats; mLockoutCache = lockoutCache; mNotificationManager = context.getSystemService(NotificationManager.class); + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); final Resources resources = getContext().getResources(); mBiometricPromptIgnoreList = resources.getIntArray( @@ -108,7 +111,16 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements @Override protected void startHalOperation() { try { - mCancellationSignal = getFreshDaemon().authenticate(mOperationId); + if (mSensorPrivacyManager != null + && mSensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, + getTargetUserId())) { + onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, + 0 /* vendorCode */); + mCallback.onClientFinished(this, false /* success */); + } else { + mCancellationSignal = getFreshDaemon().authenticate(mOperationId); + } } catch (RemoteException e) { Slog.e(TAG, "Remote exception when requesting auth", e); onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java index 2ef0911658b1..2158dfe7bde5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java @@ -19,6 +19,8 @@ package com.android.server.biometrics.sensors.face.aidl; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.hardware.SensorPrivacyManager; +import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.face.ISession; @@ -41,6 +43,7 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det private final boolean mIsStrongBiometric; @Nullable private ICancellationSignal mCancellationSignal; + @Nullable private SensorPrivacyManager mSensorPrivacyManager; public FaceDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token, long requestId, @@ -51,6 +54,7 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient); setRequestId(requestId); mIsStrongBiometric = isStrongBiometric; + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); } @Override @@ -73,6 +77,14 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det @Override protected void startHalOperation() { + if (mSensorPrivacyManager != null + && mSensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, getTargetUserId())) { + onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); + mCallback.onClientFinished(this, false /* success */); + return; + } + try { mCancellationSignal = getFreshDaemon().detectInteraction(); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java index 40f2801541d3..7548d2871a15 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java @@ -19,6 +19,7 @@ package com.android.server.biometrics.sensors.face.hidl; import android.annotation.NonNull; import android.content.Context; import android.content.res.Resources; +import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFaceConstants; @@ -55,6 +56,7 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { private final int[] mKeyguardIgnoreListVendor; private int mLastAcquire; + private SensorPrivacyManager mSensorPrivacyManager; FaceAuthenticationClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @@ -71,6 +73,7 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { isKeyguardBypassEnabled); setRequestId(requestId); mUsageStats = usageStats; + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); final Resources resources = getContext().getResources(); mBiometricPromptIgnoreList = resources.getIntArray( @@ -97,6 +100,15 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { @Override protected void startHalOperation() { + + if (mSensorPrivacyManager != null + && mSensorPrivacyManager + .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, getTargetUserId())) { + onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */); + mCallback.onClientFinished(this, false /* success */); + return; + } + try { getFreshDaemon().authenticate(mOperationId); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 7d5125f2e988..b469a80a3a2f 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -6565,6 +6565,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_ICON) { return false; } + // Choose the default behavior for Launcher and SystemUI when the SplashScreen style is + // not specified in the ActivityOptions. + if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME) { + return false; + } else if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEMUI) { + return true; + } } if (sourceRecord == null) { sourceRecord = searchCandidateLaunchingActivity(); @@ -6574,14 +6581,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return sourceRecord.mSplashScreenStyleEmpty; } - // If this activity was launched from a system surface, never use an empty splash screen + // If this activity was launched from Launcher or System for first start, never use an + // empty splash screen. // Need to check sourceRecord before in case this activity is launched from service. - if (launchedFromSystemSurface()) { - return false; - } - - // Otherwise use empty. - return true; + return !startActivity || !(mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEM + || mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME); } private int getSplashscreenTheme() { diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index b9353e1f9a0c..421a1c916fdc 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -1634,21 +1634,21 @@ public class AppTransition implements Dump { } @TransitionType int getKeyguardTransition() { + if (mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_GOING_AWAY) != -1) { + return TRANSIT_KEYGUARD_GOING_AWAY; + } + final int unoccludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_UNOCCLUDE); + final int occludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_OCCLUDE); + // No keyguard related transition requests. + if (unoccludeIndex == -1 && occludeIndex == -1) { + return TRANSIT_NONE; + } // In case we unocclude Keyguard and occlude it again, meaning that we never actually // unoccclude/occlude Keyguard, but just run a normal transition. - final int occludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_UNOCCLUDE); - if (occludeIndex != -1 - && occludeIndex < mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_OCCLUDE)) { + if (unoccludeIndex != -1 && unoccludeIndex < occludeIndex) { return TRANSIT_NONE; } - - for (int i = 0; i < mNextAppTransitionRequests.size(); ++i) { - final @TransitionType int transit = mNextAppTransitionRequests.get(i); - if (isKeyguardTransit(transit)) { - return transit; - } - } - return TRANSIT_NONE; + return unoccludeIndex != -1 ? TRANSIT_KEYGUARD_UNOCCLUDE : TRANSIT_KEYGUARD_OCCLUDE; } @TransitionType int getFirstAppTransition() { diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index cc97990f608d..7bcf8a9f274f 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -2191,14 +2191,13 @@ class TaskFragment extends WindowContainer<WindowContainer> { TaskFragmentInfo getTaskFragmentInfo() { List<IBinder> childActivities = new ArrayList<>(); for (int i = 0; i < getChildCount(); i++) { - WindowContainer wc = getChildAt(i); - if (mTaskFragmentOrganizerUid != INVALID_UID - && wc.asActivityRecord() != null - && wc.asActivityRecord().info.processName.equals( - mTaskFragmentOrganizerProcessName) - && wc.asActivityRecord().getUid() == mTaskFragmentOrganizerUid) { + final WindowContainer wc = getChildAt(i); + final ActivityRecord ar = wc.asActivityRecord(); + if (mTaskFragmentOrganizerUid != INVALID_UID && ar != null + && ar.info.processName.equals(mTaskFragmentOrganizerProcessName) + && ar.getUid() == mTaskFragmentOrganizerUid && !ar.finishing) { // Only includes Activities that belong to the organizer process for security. - childActivities.add(wc.asActivityRecord().appToken); + childActivities.add(ar.appToken); } } final Point positionInParent = new Point(); diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 29c27f9f3af6..c7fdefc412cc 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -24,6 +24,7 @@ import static com.android.server.wm.WindowOrganizerController.configurationsAreE import android.annotation.IntDef; import android.annotation.Nullable; import android.content.res.Configuration; +import android.graphics.Rect; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; @@ -579,4 +580,26 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr event.mException); } } + + // TODO(b/204399167): change to push the embedded state to the client side + @Override + public boolean isActivityEmbedded(IBinder activityToken) { + synchronized (mGlobalLock) { + final ActivityRecord activity = ActivityRecord.forTokenLocked(activityToken); + if (activity == null) { + return false; + } + final TaskFragment taskFragment = activity.getOrganizedTaskFragment(); + if (taskFragment == null) { + return false; + } + final Task parentTask = taskFragment.getTask(); + if (parentTask != null) { + final Rect taskBounds = parentTask.getBounds(); + final Rect taskFragBounds = taskFragment.getBounds(); + return !taskBounds.equals(taskFragBounds) && taskBounds.contains(taskFragBounds); + } + return false; + } + } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java index b3f7587df612..b255a35c512e 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java @@ -302,6 +302,65 @@ public class AuthSessionTest { testInvokesCancel(session -> session.onDialogDismissed(DISMISSED_REASON_NEGATIVE, null)); } + // TODO (b/208484275) : Enable these tests + // @Test + // public void testPreAuth_canAuthAndPrivacyDisabled() throws Exception { + // SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class); + // when(manager + // .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt())) + // .thenReturn(false); + // when(mContext.getSystemService(SensorPrivacyManager.class)) + // .thenReturn(manager); + // setupFace(1 /* id */, false /* confirmationAlwaysRequired */, + // mock(IBiometricAuthenticator.class)); + // final PromptInfo promptInfo = createPromptInfo(Authenticators.BIOMETRIC_STRONG); + // final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false); + // assertEquals(BiometricManager.BIOMETRIC_SUCCESS, preAuthInfo.getCanAuthenticateResult()); + // for (BiometricSensor sensor : preAuthInfo.eligibleSensors) { + // assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState()); + // } + // } + + // @Test + // public void testPreAuth_cannotAuthAndPrivacyEnabled() throws Exception { + // SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class); + // when(manager + // .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt())) + // .thenReturn(true); + // when(mContext.getSystemService(SensorPrivacyManager.class)) + // .thenReturn(manager); + // setupFace(1 /* id */, false /* confirmationAlwaysRequired */, + // mock(IBiometricAuthenticator.class)); + // final PromptInfo promptInfo = createPromptInfo(Authenticators.BIOMETRIC_STRONG); + // final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false); + // assertEquals(BiometricManager.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED, + // preAuthInfo.getCanAuthenticateResult()); + // // Even though canAuth returns privacy enabled, we should still be able to authenticate. + // for (BiometricSensor sensor : preAuthInfo.eligibleSensors) { + // assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState()); + // } + // } + + // @Test + // public void testPreAuth_canAuthAndPrivacyEnabledCredentialEnabled() throws Exception { + // SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class); + // when(manager + // .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt())) + // .thenReturn(true); + // when(mContext.getSystemService(SensorPrivacyManager.class)) + // .thenReturn(manager); + // setupFace(1 /* id */, false /* confirmationAlwaysRequired */, + // mock(IBiometricAuthenticator.class)); + // final PromptInfo promptInfo = + // createPromptInfo(Authenticators.BIOMETRIC_STRONG + // | Authenticators. DEVICE_CREDENTIAL); + // final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false); + // assertEquals(BiometricManager.BIOMETRIC_SUCCESS, preAuthInfo.getCanAuthenticateResult()); + // for (BiometricSensor sensor : preAuthInfo.eligibleSensors) { + // assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState()); + // } + // } + private void testInvokesCancel(Consumer<AuthSession> sessionConsumer) throws RemoteException { final IBiometricAuthenticator faceAuthenticator = mock(IBiometricAuthenticator.class); @@ -331,7 +390,8 @@ public class AuthSessionTest { userId, promptInfo, TEST_PACKAGE, - checkDevicePolicyManager); + checkDevicePolicyManager, + mContext); } private AuthSession createAuthSession(List<BiometricSensor> sensors, diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java index c0959d311ed5..2ea7fdaf6348 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java @@ -22,6 +22,9 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED; import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY; +import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; +import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; +import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE; @@ -92,6 +95,7 @@ public class AppTransitionTests extends WindowTestsBase { final ActivityRecord activity = createActivityRecord(dc); mDc.prepareAppTransition(TRANSIT_OPEN); + mDc.prepareAppTransition(TRANSIT_KEYGUARD_OCCLUDE); mDc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY); mDc.mOpeningApps.add(activity); assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY, @@ -102,6 +106,22 @@ public class AppTransitionTests extends WindowTestsBase { } @Test + public void testKeyguardUnoccludeOcclude() { + final DisplayContent dc = createNewDisplay(Display.STATE_ON); + final ActivityRecord activity = createActivityRecord(dc); + + mDc.prepareAppTransition(TRANSIT_KEYGUARD_UNOCCLUDE); + mDc.prepareAppTransition(TRANSIT_KEYGUARD_OCCLUDE); + mDc.mOpeningApps.add(activity); + assertEquals(TRANSIT_NONE, + AppTransitionController.getTransitCompatType(mDc.mAppTransition, + mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, + mDisplayContent.mChangingContainers, null /* wallpaperTarget */, + null /* oldWallpaper */, false /*skipAppTransitionAnimation*/)); + + } + + @Test public void testKeyguardKeep() { final DisplayContent dc = createNewDisplay(Display.STATE_ON); final ActivityRecord activity = createActivityRecord(dc); |