diff --git a/packages/SystemUI/res-keyguard/drawable/keyguard_user_switcher_header_bg.xml b/packages/SystemUI/res-keyguard/drawable/keyguard_user_switcher_header_bg.xml new file mode 100644 index 0000000000000..177f69590882b --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/keyguard_user_switcher_header_bg.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + diff --git a/packages/SystemUI/res-keyguard/drawable/keyguard_user_switcher_popup_bg.xml b/packages/SystemUI/res-keyguard/drawable/keyguard_user_switcher_popup_bg.xml new file mode 100644 index 0000000000000..96a2d1534ebe3 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/keyguard_user_switcher_popup_bg.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml new file mode 100644 index 0000000000000..a2b8bf6d3d4ce --- /dev/null +++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml @@ -0,0 +1,55 @@ + + + + + + + + + + > + + + diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher_item.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher_item.xml new file mode 100644 index 0000000000000..b08e1ff4c4722 --- /dev/null +++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher_item.xml @@ -0,0 +1,25 @@ + + + diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml index a946318cb3136..94566c78cc933 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml @@ -49,8 +49,6 @@ android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom" androidprv:layout_constraintEnd_toEndOf="parent" androidprv:layout_constraintStart_toStartOf="parent" - - androidprv:layout_constraintTop_toTopOf="parent" androidprv:layout_constraintBottom_toTopOf="@id/key1" androidprv:layout_constraintVertical_bias="0.0"> diff --git a/packages/SystemUI/res-keyguard/values-sw720dp/bools.xml b/packages/SystemUI/res-keyguard/values-sw720dp/bools.xml index 4daa6488a6603..54bb1fcab54b3 100644 --- a/packages/SystemUI/res-keyguard/values-sw720dp/bools.xml +++ b/packages/SystemUI/res-keyguard/values-sw720dp/bools.xml @@ -18,7 +18,7 @@ - true + false diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index 89dd741e2898c..95330400145fa 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -106,4 +106,13 @@ opacity is zero), but this controls how much motion will actually be applied to it while animating. Larger values will cause it to move "faster" while fading out/in. --> 120dp + + + 24sp + 18sp + 300dp + 250dp + 32dp + 24dp + 15dp diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml index b0bdc72f986ad..a7b2b47a4cdad 100644 --- a/packages/SystemUI/res-keyguard/values/styles.xml +++ b/packages/SystemUI/res-keyguard/values/styles.xml @@ -139,4 +139,23 @@ + + + + + + diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java index 40190c18935ba..7eae7295547f6 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java @@ -48,10 +48,6 @@ public abstract class KeyguardInputView extends LinearLayout { abstract CharSequence getTitle(); - void animateForIme(float interpolatedFraction, boolean appearingAnim) { - return; - } - boolean disallowInterceptTouch(MotionEvent event) { return false; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index 3a3d30861132c..bc366ab9e1762 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -156,8 +156,7 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { setAlpha(0f); animate() .alpha(1f) - .setDuration(500) - .setStartDelay(300) + .setDuration(300) .start(); setTranslationY(0f); @@ -219,15 +218,6 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { return true; } - - @Override - public void animateForIme(float interpolatedFraction, boolean appearingAnim) { - animate().cancel(); - setAlpha(appearingAnim - ? Math.max(interpolatedFraction, getAlpha()) - : 1 - interpolatedFraction); - } - @Override public CharSequence getTitle() { return getResources().getString( diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 172c7f62100f5..95567ec947946 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -23,17 +23,23 @@ import static java.lang.Integer.max; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; +import android.content.res.Configuration; import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; import android.provider.Settings; import android.util.AttributeSet; import android.util.Log; import android.util.MathUtils; import android.util.TypedValue; import android.view.Gravity; +import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; @@ -44,7 +50,10 @@ import android.view.WindowInsetsAnimation; import android.view.WindowManager; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; +import android.widget.AdapterView; import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; import androidx.annotation.IntDef; import androidx.annotation.NonNull; @@ -56,12 +65,17 @@ import androidx.dynamicanimation.animation.SpringAnimation; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; +import com.android.internal.util.UserIcons; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.Gefingerpoken; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; +import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.shared.system.SysUiStatsLog; +import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.systemui.statusbar.policy.UserSwitcherController.BaseUserAdapter; +import com.android.systemui.statusbar.policy.UserSwitcherController.UserRecord; import com.android.systemui.util.settings.GlobalSettings; import java.util.ArrayList; @@ -110,6 +124,8 @@ public class KeyguardSecurityContainer extends FrameLayout { @VisibleForTesting KeyguardSecurityViewFlipper mSecurityViewFlipper; private GlobalSettings mGlobalSettings; + private FalsingCollector mFalsingCollector; + private UserSwitcherController mUserSwitcherController; private AlertDialog mAlertDialog; private boolean mSwipeUpToRetry; @@ -124,7 +140,7 @@ public class KeyguardSecurityContainer extends FrameLayout { private float mStartTouchY = -1; private boolean mDisappearAnimRunning; private SwipeListener mSwipeListener; - private ModeLogic mModeLogic = new DefaultModeLogic(); + private ViewMode mViewMode = new DefaultViewMode(); private @Mode int mCurrentMode = MODE_DEFAULT; private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback = @@ -173,8 +189,11 @@ public class KeyguardSecurityContainer extends FrameLayout { interpolatedFraction); translationY += paddingBottom; } - mSecurityViewFlipper.animateForIme(translationY, interpolatedFraction, - !mDisappearAnimRunning); + + float alpha = mDisappearAnimRunning + ? 1 - interpolatedFraction + : Math.max(interpolatedFraction, getAlpha()); + updateChildren(translationY, alpha); return windowInsets; } @@ -183,12 +202,19 @@ public class KeyguardSecurityContainer extends FrameLayout { public void onEnd(WindowInsetsAnimation animation) { if (!mDisappearAnimRunning) { endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_APPEAR); - mSecurityViewFlipper.animateForIme(0, /* interpolatedFraction */ 1f, - true /* appearingAnim */); + updateChildren(0 /* translationY */, 1f /* alpha */); } else { endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR); } } + + private void updateChildren(int translationY, float alpha) { + for (int i = 0; i < KeyguardSecurityContainer.this.getChildCount(); ++i) { + View child = KeyguardSecurityContainer.this.getChildAt(i); + child.setTranslationY(translationY); + child.setAlpha(alpha); + } + } }; // Used to notify the container when something interesting happens. @@ -270,9 +296,12 @@ public class KeyguardSecurityContainer extends FrameLayout { void onResume(SecurityMode securityMode, boolean faceAuthEnabled) { mSecurityViewFlipper.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback); updateBiometricRetry(securityMode, faceAuthEnabled); + + setupViewMode(); } - void initMode(@Mode int mode, GlobalSettings globalSettings) { + void initMode(@Mode int mode, GlobalSettings globalSettings, FalsingCollector falsingCollector, + UserSwitcherController userSwitcherController) { if (mCurrentMode == mode) return; Log.i(TAG, "Switching mode from " + modeToString(mCurrentMode) + " to " + modeToString(mode)); @@ -280,16 +309,18 @@ public class KeyguardSecurityContainer extends FrameLayout { switch (mode) { case MODE_ONE_HANDED: - mModeLogic = new OneHandedModeLogic(); + mViewMode = new OneHandedViewMode(); break; case MODE_USER_SWITCHER: - mModeLogic = new UserSwitcherModeLogic(); + mViewMode = new UserSwitcherViewMode(); break; default: - mModeLogic = new DefaultModeLogic(); + mViewMode = new DefaultViewMode(); } mGlobalSettings = globalSettings; - finishSetup(); + mFalsingCollector = falsingCollector; + mUserSwitcherController = userSwitcherController; + setupViewMode(); } private String modeToString(@Mode int mode) { @@ -305,10 +336,14 @@ public class KeyguardSecurityContainer extends FrameLayout { } } - private void finishSetup() { - if (mSecurityViewFlipper == null || mGlobalSettings == null) return; + private void setupViewMode() { + if (mSecurityViewFlipper == null || mGlobalSettings == null + || mFalsingCollector == null || mUserSwitcherController == null) { + return; + } - mModeLogic.init(this, mGlobalSettings, mSecurityViewFlipper); + mViewMode.init(this, mGlobalSettings, mSecurityViewFlipper, mFalsingCollector, + mUserSwitcherController); } @Mode int getMode() { @@ -321,13 +356,13 @@ public class KeyguardSecurityContainer extends FrameLayout { * that the user last interacted with. */ void updatePositionByTouchX(float x) { - mModeLogic.updatePositionByTouchX(x); + mViewMode.updatePositionByTouchX(x); } /** Returns whether the inner SecurityViewFlipper is left-aligned when in one-handed mode. */ public boolean isOneHandedModeLeftAligned() { return mCurrentMode == MODE_ONE_HANDED - && ((OneHandedModeLogic) mModeLogic).isLeftAligned(); + && ((OneHandedViewMode) mViewMode).isLeftAligned(); } public void onPause() { @@ -336,6 +371,7 @@ public class KeyguardSecurityContainer extends FrameLayout { mAlertDialog = null; } mSecurityViewFlipper.setWindowInsetsAnimationCallback(null); + mViewMode.reset(); } @Override @@ -428,7 +464,7 @@ public class KeyguardSecurityContainer extends FrameLayout { } } else { if (!mIsDragging) { - mModeLogic.handleTap(event); + mViewMode.handleTap(event); } } } @@ -453,8 +489,19 @@ public class KeyguardSecurityContainer extends FrameLayout { .animateToFinalPosition(0); } + /** + * Runs after a succsssful authentication only + */ public void startDisappearAnimation(SecurityMode securitySelection) { mDisappearAnimRunning = true; + mViewMode.startDisappearAnimation(securitySelection); + } + + /** + * This will run when the bouncer shows in all cases except when the user drags the bouncer up. + */ + public void startAppearAnimation(SecurityMode securityMode) { + mViewMode.startAppearAnimation(securityMode); } private void beginJankInstrument(int cuj) { @@ -490,8 +537,6 @@ public class KeyguardSecurityContainer extends FrameLayout { public void onFinishInflate() { super.onFinishInflate(); mSecurityViewFlipper = findViewById(R.id.view_flipper); - - finishSetup(); } @Override @@ -562,10 +607,7 @@ public class KeyguardSecurityContainer extends FrameLayout { for (int i = 0; i < getChildCount(); i++) { final View view = getChildAt(i); if (view.getVisibility() != GONE) { - int updatedWidthMeasureSpec = widthMeasureSpec; - if (view == mSecurityViewFlipper) { - updatedWidthMeasureSpec = mModeLogic.getChildWidthMeasureSpec(widthMeasureSpec); - } + int updatedWidthMeasureSpec = mViewMode.getChildWidthMeasureSpec(widthMeasureSpec); measureChildWithMargins(view, updatedWidthMeasureSpec, 0, heightMeasureSpec, 0); final LayoutParams lp = (LayoutParams) view.getLayoutParams(); @@ -595,7 +637,13 @@ public class KeyguardSecurityContainer extends FrameLayout { // After a layout pass, we need to re-place the inner bouncer, as our bounds may have // changed. - mModeLogic.updateSecurityViewLocation(); + mViewMode.updateSecurityViewLocation(); + } + + @Override + protected void onConfigurationChanged(Configuration config) { + super.onConfigurationChanged(config); + mViewMode.updateSecurityViewLocation(); } void showAlmostAtWipeDialog(int attempts, int remaining, int userType) { @@ -643,10 +691,12 @@ public class KeyguardSecurityContainer extends FrameLayout { /** * Enscapsulates the differences between bouncer modes for the container. */ - private interface ModeLogic { + interface ViewMode { - default void init(ViewGroup v, GlobalSettings globalSettings, - KeyguardSecurityViewFlipper viewFlipper) {}; + default void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings, + @NonNull KeyguardSecurityViewFlipper viewFlipper, + @NonNull FalsingCollector falsingCollector, + @NonNull UserSwitcherController userSwitcherController) {}; /** Reinitialize the location */ default void updateSecurityViewLocation() {}; @@ -657,19 +707,33 @@ public class KeyguardSecurityContainer extends FrameLayout { /** A tap on the container, outside of the ViewFlipper */ default void handleTap(MotionEvent event) {}; + /** Called when the view needs to reset or hides */ + default void reset() {}; + + /** On a successful auth, optionally handle how the view disappears */ + default void startDisappearAnimation(SecurityMode securityMode) {}; + + /** On notif tap, this animation will run */ + default void startAppearAnimation(SecurityMode securityMode) {}; + /** Override to alter the width measure spec to perhaps limit the ViewFlipper size */ default int getChildWidthMeasureSpec(int parentWidthMeasureSpec) { return parentWidthMeasureSpec; } } - private static class DefaultModeLogic implements ModeLogic { + /** + * Default bouncer is centered within the space + */ + static class DefaultViewMode implements ViewMode { private ViewGroup mView; private KeyguardSecurityViewFlipper mViewFlipper; @Override - public void init(ViewGroup v, GlobalSettings globalSettings, - KeyguardSecurityViewFlipper viewFlipper) { + public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings, + @NonNull KeyguardSecurityViewFlipper viewFlipper, + @NonNull FalsingCollector falsingCollector, + @NonNull UserSwitcherController userSwitcherController) { mView = v; mViewFlipper = viewFlipper; @@ -682,7 +746,6 @@ public class KeyguardSecurityContainer extends FrameLayout { (FrameLayout.LayoutParams) mViewFlipper.getLayoutParams(); lp.gravity = Gravity.CENTER_HORIZONTAL; mViewFlipper.setLayoutParams(lp); - mViewFlipper.setTranslationX(0); } } @@ -691,13 +754,171 @@ public class KeyguardSecurityContainer extends FrameLayout { * User switcher mode will display both the current user icon as well as * a user switcher, in both portrait and landscape modes. */ - private static class UserSwitcherModeLogic implements ModeLogic { + static class UserSwitcherViewMode implements ViewMode { private ViewGroup mView; + private ViewGroup mUserSwitcherViewGroup; + private KeyguardSecurityViewFlipper mViewFlipper; + private ImageView mUserIconView; + private TextView mUserSwitcher; + private FalsingCollector mFalsingCollector; + private UserSwitcherController mUserSwitcherController; + private KeyguardUserSwitcherPopupMenu mPopup; @Override - public void init(ViewGroup v, GlobalSettings globalSettings, - KeyguardSecurityViewFlipper viewFlipper) { + public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings, + @NonNull KeyguardSecurityViewFlipper viewFlipper, + @NonNull FalsingCollector falsingCollector, + @NonNull UserSwitcherController userSwitcherController) { mView = v; + mViewFlipper = viewFlipper; + mFalsingCollector = falsingCollector; + mUserSwitcherController = userSwitcherController; + + if (mUserSwitcherViewGroup == null) { + LayoutInflater.from(v.getContext()).inflate( + R.layout.keyguard_bouncer_user_switcher, + mView, + true); + mUserSwitcherViewGroup = mView.findViewById(R.id.keyguard_bouncer_user_switcher); + } + + mUserIconView = mView.findViewById(R.id.user_icon); + Drawable icon = UserIcons.getDefaultUserIcon(v.getContext().getResources(), 0, false); + mUserIconView.setImageDrawable(icon); + + updateSecurityViewLocation(); + + mUserSwitcher = mView.findViewById(R.id.user_switcher_header); + setupUserSwitcher(); + } + + @Override + public void reset() { + if (mPopup != null) { + mPopup.dismiss(); + mPopup = null; + } + } + + @Override + public void startAppearAnimation(SecurityMode securityMode) { + // IME insets animations handle alpha and translation + if (securityMode == SecurityMode.Password) { + return; + } + + mUserSwitcherViewGroup.setAlpha(0f); + ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA, + 1f); + alphaAnim.setInterpolator(Interpolators.ALPHA_IN); + alphaAnim.setDuration(500); + alphaAnim.start(); + } + + @Override + public void startDisappearAnimation(SecurityMode securityMode) { + // IME insets animations handle alpha and translation + if (securityMode == SecurityMode.Password) { + return; + } + + int yTranslation = mView.getContext().getResources().getDimensionPixelSize( + R.dimen.disappear_y_translation); + + AnimatorSet anims = new AnimatorSet(); + ObjectAnimator yAnim = ObjectAnimator.ofFloat(mView, View.TRANSLATION_Y, yTranslation); + ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mView, View.ALPHA, 0f); + + anims.setInterpolator(Interpolators.STANDARD_ACCELERATE); + anims.playTogether(alphaAnim, yAnim); + anims.start(); + } + + private void setupUserSwitcher() { + String currentUserName = mUserSwitcherController.getCurrentUserName(); + mUserSwitcher.setText(currentUserName); + + ViewGroup anchor = mView.findViewById(R.id.user_switcher_anchor); + BaseUserAdapter adapter = new BaseUserAdapter(mUserSwitcherController) { + @Override + public View getView(int position, View convertView, ViewGroup parent) { + UserRecord item = getItem(position); + TextView view = (TextView) convertView; + if (view == null) { + view = (TextView) LayoutInflater.from(parent.getContext()).inflate( + R.layout.keyguard_bouncer_user_switcher_item, + parent, + false); + } + view.setText(getName(parent.getContext(), item)); + return view; + } + }; + + if (adapter.getCount() < 2) { + // The drop down arrow is at index 1 + ((LayerDrawable) mUserSwitcher.getBackground()).getDrawable(1).setAlpha(0); + anchor.setClickable(false); + return; + } else { + ((LayerDrawable) mUserSwitcher.getBackground()).getDrawable(1).setAlpha(255); + } + + anchor.setClickable(true); + anchor.setOnTouchListener((v, ev) -> { + if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { + mFalsingCollector.avoidGesture(); + mPopup = new KeyguardUserSwitcherPopupMenu(v.getContext(), + mFalsingCollector); + mPopup.setAnchorView(anchor); + mPopup.setAdapter(adapter); + mPopup.setOnItemClickListener(new AdapterView.OnItemClickListener() { + public void onItemClick(AdapterView parent, View view, int pos, + long id) { + mFalsingCollector.avoidGesture(); + + // - 1 to account for the header view + UserRecord user = adapter.getItem(pos - 1); + if (!user.isCurrent) { + adapter.onUserListItemClicked(user); + } + mPopup.dismiss(); + mPopup = null; + } + }); + mPopup.show(); + } + return true; + }); + } + + /** + * Each view will get half the width. Yes, it would be easier to use something other than + * FrameLayout but it was too disruptive to downstream projects to change. + */ + @Override + public int getChildWidthMeasureSpec(int parentWidthMeasureSpec) { + return MeasureSpec.makeMeasureSpec( + MeasureSpec.getSize(parentWidthMeasureSpec) / 2, + MeasureSpec.getMode(parentWidthMeasureSpec)); + } + + @Override + public void updateSecurityViewLocation() { + if (mView.getContext().getResources().getConfiguration().orientation + == Configuration.ORIENTATION_PORTRAIT) { + updateViewGravity(mViewFlipper, Gravity.CENTER_HORIZONTAL); + updateViewGravity(mUserSwitcherViewGroup, Gravity.CENTER_HORIZONTAL); + } else { + updateViewGravity(mViewFlipper, Gravity.RIGHT | Gravity.BOTTOM); + updateViewGravity(mUserSwitcherViewGroup, Gravity.LEFT | Gravity.TOP); + } + } + + private void updateViewGravity(View v, int gravity) { + FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams(); + lp.gravity = gravity; + v.setLayoutParams(lp); } } @@ -705,7 +926,7 @@ public class KeyguardSecurityContainer extends FrameLayout { * Logic to enabled one-handed bouncer mode. Supports animating the bouncer * between alternate sides of the display. */ - private static class OneHandedModeLogic implements ModeLogic { + static class OneHandedViewMode implements ViewMode { @Nullable private ValueAnimator mRunningOneHandedAnimator; private ViewGroup mView; private KeyguardSecurityViewFlipper mViewFlipper; @@ -713,7 +934,9 @@ public class KeyguardSecurityContainer extends FrameLayout { @Override public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings, - @NonNull KeyguardSecurityViewFlipper viewFlipper) { + @NonNull KeyguardSecurityViewFlipper viewFlipper, + @NonNull FalsingCollector falsingCollector, + @NonNull UserSwitcherController userSwitcherController) { mView = v; mViewFlipper = viewFlipper; mGlobalSettings = globalSettings; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 40352294ad881..6b73a324eff3f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -54,6 +54,7 @@ import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.util.ViewController; import com.android.systemui.util.settings.GlobalSettings; @@ -78,6 +79,7 @@ public class KeyguardSecurityContainerController extends ViewController { + int height = -v.getMeasuredHeight() + getAnchorView().getHeight(); + if (height != mLastHeight) { + mLastHeight = height; + setVerticalOffset(height); + KeyguardUserSwitcherPopupMenu.super.show(); + } + }; + + public KeyguardUserSwitcherPopupMenu(@NonNull Context context, + @NonNull FalsingCollector falsingCollector) { + super(context); + mContext = context; + mFalsingCollector = falsingCollector; + Resources res = mContext.getResources(); + setBackgroundDrawable( + res.getDrawable(R.drawable.keyguard_user_switcher_popup_bg, context.getTheme())); + setModal(true); + setOverlapAnchor(true); + } + + /** + * Show the dialog. + */ + @Override + public void show() { + // need to call show() first in order to construct the listView + super.show(); + ListView listView = getListView(); + + // This will force the popupwindow to show upward instead of drop down + listView.addOnLayoutChangeListener(mLayoutListener); + + TextView header = (TextView) LayoutInflater.from(mContext).inflate( + R.layout.keyguard_bouncer_user_switcher_item, listView, false); + header.setText(mContext.getResources().getString( + R.string.accessibility_multi_user_switch_switcher)); + listView.addHeaderView(header); + + listView.setOnTouchListener((v, ev) -> { + if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { + mFalsingCollector.avoidGesture(); + } + return false; + }); + } + + @Override + public void dismiss() { + getListView().removeOnLayoutChangeListener(mLayoutListener); + super.dismiss(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 17dd26a9c89b8..cac19aaa9486e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -836,6 +836,11 @@ public class UserSwitcherController implements Dumpable { mRootView = notificationShadeWindowView; } + @VisibleForTesting + public KeyguardStateController getKeyguardStateController() { + return mKeyguardStateController; + } + public static abstract class BaseUserAdapter extends BaseAdapter { final UserSwitcherController mController; @@ -843,7 +848,7 @@ public class UserSwitcherController implements Dumpable { protected BaseUserAdapter(UserSwitcherController controller) { mController = controller; - mKeyguardStateController = controller.mKeyguardStateController; + mKeyguardStateController = controller.getKeyguardStateController(); controller.addAdapter(new WeakReference<>(this)); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index 030464a0bd95b..98ce138db857b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -50,6 +50,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.util.settings.GlobalSettings; import org.junit.Before; @@ -111,6 +112,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { private FalsingCollector mFalsingCollector; @Mock private GlobalSettings mGlobalSettings; + @Mock + private UserSwitcherController mUserSwitcherController; private Configuration mConfiguration; private KeyguardSecurityContainerController mKeyguardSecurityContainerController; @@ -144,8 +147,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils, mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger, mKeyguardStateController, mKeyguardSecurityViewFlipperController, - mConfigurationController, mFalsingCollector, mGlobalSettings) - .create(mSecurityCallback); + mConfigurationController, mFalsingCollector, mUserSwitcherController, + mGlobalSettings).create(mSecurityCallback); } @Test @@ -182,13 +185,15 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { public void onResourcesUpdate_callsThroughOnRotationChange() { // Rotation is the same, shouldn't cause an update mKeyguardSecurityContainerController.updateResources(); - verify(mView, never()).initMode(MODE_DEFAULT, mGlobalSettings); + verify(mView, never()).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingCollector, + mUserSwitcherController); // Update rotation. Should trigger update mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE; mKeyguardSecurityContainerController.updateResources(); - verify(mView).initMode(MODE_DEFAULT, mGlobalSettings); + verify(mView).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingCollector, + mUserSwitcherController); } private void touchDownLeftSide() { @@ -245,7 +250,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController); mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern); - verify(mView).initMode(MODE_DEFAULT, mGlobalSettings); + verify(mView).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingCollector, + mUserSwitcherController); } @Test @@ -256,7 +262,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController); mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern); - verify(mView).initMode(MODE_ONE_HANDED, mGlobalSettings); + verify(mView).initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingCollector, + mUserSwitcherController); } @Test @@ -267,6 +274,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController); mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password); - verify(mView).initMode(MODE_DEFAULT, mGlobalSettings); + verify(mView).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingCollector, + mUserSwitcherController); } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index c75108186f4e2..ea7940a58366b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -16,21 +16,27 @@ package com.android.keyguard; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.systemBars; import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT; import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.pm.UserInfo; +import android.content.res.Configuration; import android.graphics.Insets; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; @@ -39,17 +45,26 @@ import android.widget.FrameLayout; import androidx.test.filters.SmallTest; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.classifier.FalsingCollector; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.systemui.statusbar.policy.UserSwitcherController.UserRecord; import com.android.systemui.util.settings.GlobalSettings; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.ArrayList; + @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper() @@ -67,28 +82,43 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { private KeyguardSecurityViewFlipper mSecurityViewFlipper; @Mock private GlobalSettings mGlobalSettings; + @Mock + private FalsingCollector mFalsingCollector; + @Mock + private UserSwitcherController mUserSwitcherController; + @Mock + private KeyguardStateController mKeyguardStateController; + @Captor + private ArgumentCaptor mLayoutCaptor; private KeyguardSecurityContainer mKeyguardSecurityContainer; + private FrameLayout.LayoutParams mSecurityViewFlipperLayoutParams; @Before public void setup() { // Needed here, otherwise when mKeyguardSecurityContainer is created below, it'll cache // the real references (rather than the TestableResources that this call creates). mContext.ensureTestableResources(); - FrameLayout.LayoutParams securityViewFlipperLayoutParams = new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + mSecurityViewFlipperLayoutParams = new FrameLayout.LayoutParams( + MATCH_PARENT, MATCH_PARENT); when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController); - when(mSecurityViewFlipper.getLayoutParams()).thenReturn(securityViewFlipperLayoutParams); + when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams); mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext()); mKeyguardSecurityContainer.mSecurityViewFlipper = mSecurityViewFlipper; mKeyguardSecurityContainer.addView(mSecurityViewFlipper, new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + when(mUserSwitcherController.getCurrentUserName()).thenReturn("Test User"); + when(mUserSwitcherController.getKeyguardStateController()) + .thenReturn(mKeyguardStateController); + when(mKeyguardStateController.isShowing()).thenReturn(true); } @Test public void onMeasure_usesHalfWidthWithOneHandedModeEnabled() { - mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings); + mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingCollector, + mUserSwitcherController); int halfWidthMeasureSpec = View.MeasureSpec.makeMeasureSpec(SCREEN_WIDTH / 2, View.MeasureSpec.EXACTLY); @@ -99,7 +129,8 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { @Test public void onMeasure_usesFullWidthWithOneHandedModeDisabled() { - mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings); + mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingCollector, + mUserSwitcherController); mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); @@ -110,7 +141,8 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { int imeInsetAmount = 100; int systemBarInsetAmount = 10; - mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings); + mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingCollector, + mUserSwitcherController); Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount); Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount); @@ -134,7 +166,8 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { int imeInsetAmount = 0; int systemBarInsetAmount = 10; - mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings); + mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingCollector, + mUserSwitcherController); Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount); Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount); @@ -154,7 +187,8 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { private void setupForUpdateKeyguardPosition(boolean oneHandedMode) { int mode = oneHandedMode ? MODE_ONE_HANDED : MODE_DEFAULT; - mKeyguardSecurityContainer.initMode(mode, mGlobalSettings); + mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingCollector, + mUserSwitcherController); mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC); mKeyguardSecurityContainer.layout(0, 0, SCREEN_WIDTH, SCREEN_WIDTH); @@ -189,4 +223,92 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { mKeyguardSecurityContainer.updatePositionByTouchX(1f); verify(mSecurityViewFlipper, never()).setTranslationX(anyInt()); } + + @Test + public void testUserSwitcherModeViewGravityLandscape() { + // GIVEN one user has been setup and in landscape + when(mUserSwitcherController.getUsers()).thenReturn(buildUserRecords(1)); + Configuration config = new Configuration(); + config.orientation = Configuration.ORIENTATION_LANDSCAPE; + when(getContext().getResources().getConfiguration()).thenReturn(config); + + // WHEN UserSwitcherViewMode is initialized and config has changed + setupUserSwitcher(); + reset(mSecurityViewFlipper); + when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams); + mKeyguardSecurityContainer.onConfigurationChanged(config); + + // THEN views are oriented side by side + verify(mSecurityViewFlipper).setLayoutParams(mLayoutCaptor.capture()); + assertThat(mLayoutCaptor.getValue().gravity).isEqualTo(Gravity.RIGHT | Gravity.BOTTOM); + ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById( + R.id.keyguard_bouncer_user_switcher); + assertThat(((FrameLayout.LayoutParams) userSwitcher.getLayoutParams()).gravity) + .isEqualTo(Gravity.LEFT | Gravity.TOP); + } + + @Test + public void testUserSwitcherModeViewGravityPortrait() { + // GIVEN one user has been setup and in landscape + when(mUserSwitcherController.getUsers()).thenReturn(buildUserRecords(1)); + Configuration config = new Configuration(); + config.orientation = Configuration.ORIENTATION_PORTRAIT; + when(getContext().getResources().getConfiguration()).thenReturn(config); + + // WHEN UserSwitcherViewMode is initialized and config has changed + setupUserSwitcher(); + reset(mSecurityViewFlipper); + when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams); + mKeyguardSecurityContainer.onConfigurationChanged(config); + + // THEN views are both centered horizontally + verify(mSecurityViewFlipper).setLayoutParams(mLayoutCaptor.capture()); + assertThat(mLayoutCaptor.getValue().gravity).isEqualTo(Gravity.CENTER_HORIZONTAL); + ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById( + R.id.keyguard_bouncer_user_switcher); + assertThat(((FrameLayout.LayoutParams) userSwitcher.getLayoutParams()).gravity) + .isEqualTo(Gravity.CENTER_HORIZONTAL); + } + + @Test + public void testLessThanTwoUsersDoesNotAllowDropDown() { + // GIVEN one user has been setup + when(mUserSwitcherController.getUsers()).thenReturn(buildUserRecords(1)); + + // WHEN UserSwitcherViewMode is initialized + setupUserSwitcher(); + + // THEN the UserSwitcher anchor should not be clickable + ViewGroup anchor = mKeyguardSecurityContainer.findViewById(R.id.user_switcher_anchor); + assertThat(anchor.isClickable()).isFalse(); + } + + @Test + public void testTwoOrMoreUsersDoesAllowDropDown() { + // GIVEN one user has been setup + when(mUserSwitcherController.getUsers()).thenReturn(buildUserRecords(2)); + + // WHEN UserSwitcherViewMode is initialized + setupUserSwitcher(); + + // THEN the UserSwitcher anchor should not be clickable + ViewGroup anchor = mKeyguardSecurityContainer.findViewById(R.id.user_switcher_anchor); + assertThat(anchor.isClickable()).isTrue(); + } + + private void setupUserSwitcher() { + mKeyguardSecurityContainer.initMode(KeyguardSecurityContainer.MODE_USER_SWITCHER, + mGlobalSettings, mFalsingCollector, mUserSwitcherController); + } + + private ArrayList buildUserRecords(int count) { + ArrayList users = new ArrayList<>(); + for (int i = 0; i < count; ++i) { + UserInfo info = new UserInfo(i /* id */, "Name: " + i, null /* iconPath */, + 0 /* flags */); + users.add(new UserRecord(info, null, false /* isGuest */, false /* isCurrent */, + false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */)); + } + return users; + } }