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;
+ }
}