Bouncer - User switcher for large screens

Adds support for a default user icon and user switcher on the
bouncer. In landscape these will appear side by side, in portrait,
stacked on top of one another.

Bug: 206826027
Test: atest KeyguardSecurityContainerControllerTest KeyguardSecurityContainerTest

Change-Id: I1f89c5b9b5a58a456fe3ed192c54b09eca00b39b
This commit is contained in:
Matt Pietal
2021-11-29 10:52:19 -05:00
parent 6629b4dedb
commit 99ded224b1
18 changed files with 682 additions and 90 deletions

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:paddingMode="stack"
android:paddingStart="44dp"
android:paddingEnd="44dp"
android:paddingLeft="0dp"
android:paddingRight="0dp">
<item>
<shape android:shape="rectangle">
<solid android:color="?androidprv:attr/colorSurface" />
<corners android:radius="@dimen/keyguard_user_switcher_corner" />
</shape>
</item>
<item
android:drawable="@drawable/ic_ksh_key_down"
android:gravity="end|center_vertical"
android:width="32dp"
android:height="32dp"
android:end="12dp" />
</layer-list>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2021 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
<solid android:color="?androidprv:attr/colorSurface" />
<corners android:radius="@dimen/keyguard_user_switcher_popup_corner" />
</shape>

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
**
** Copyright 2021, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License")
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/keyguard_bouncer_user_switcher"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
android:gravity="center"
android:paddingTop="12dp"
android:importantForAccessibility="yes"> <!-- Needed because TYPE_WINDOW_STATE_CHANGED is sent
from this view when bouncer is shown -->
<ImageView
android:id="@+id/user_icon"
android:layout_width="@dimen/keyguard_user_switcher_icon_size"
android:layout_height="@dimen/keyguard_user_switcher_icon_size" />
<!-- need to keep this outer view in order to have a correctly sized anchor
for the dropdown menu, as well as dropdown background in the right place -->
<LinearLayout
android:id="@+id/user_switcher_anchor"
android:orientation="horizontal"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginTop="32dp"
android:minHeight="48dp">
<TextView
style="@style/Keyguard.UserSwitcher.Spinner.Header"
android:clickable="false"
android:id="@+id/user_switcher_header"
android:layout_width="@dimen/keyguard_user_switcher_width"
android:layout_height="wrap_content" />
</LinearLayout>>
</LinearLayout>

View File

@@ -0,0 +1,25 @@
<!--
~ Copyright (C) 2021 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Keyguard.UserSwitcher.Spinner.Item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:paddingStart="@dimen/control_menu_horizontal_padding"
android:paddingEnd="@dimen/control_menu_horizontal_padding"
android:textDirection="locale"/>

View File

@@ -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">

View File

@@ -18,7 +18,7 @@
<resources>
<!-- Allows PIN/Pattern to be drawn on one side of a display, and for the user to
switch sides -->
<bool name="can_use_one_handed_bouncer">true</bool>
<bool name="can_use_one_handed_bouncer">false</bool>
<!-- Will display the bouncer on one side of the display, and the current user icon and
user switcher on the other side -->

View File

@@ -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. -->
<dimen name="one_handed_bouncer_move_animation_translation">120dp</dimen>
<dimen name="keyguard_user_switcher_header_text_size">24sp</dimen>
<dimen name="keyguard_user_switcher_item_text_size">18sp</dimen>
<dimen name="keyguard_user_switcher_width">300dp</dimen>
<dimen name="keyguard_user_switcher_icon_size">250dp</dimen>
<dimen name="keyguard_user_switcher_corner">32dp</dimen>
<dimen name="keyguard_user_switcher_popup_corner">24dp</dimen>
<dimen name="keyguard_user_switcher_item_padding_vertical">15dp</dimen>
</resources>

View File

@@ -139,4 +139,23 @@
<style name="TextAppearance.Keyguard.BottomArea.Button">
<item name="android:shadowRadius">0</item>
</style>
<style name="Keyguard.UserSwitcher.Spinner" parent="@android:style/Widget.DeviceDefault.TextView">
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
<item name="android:singleLine">true</item>
<item name="android:ellipsize">end</item>
<item name="android:paddingTop">@dimen/keyguard_user_switcher_item_padding_vertical</item>
<item name="android:paddingBottom">@dimen/keyguard_user_switcher_item_padding_vertical</item>
</style>
<style name="Keyguard.UserSwitcher.Spinner.Header">
<item name="android:background">@drawable/keyguard_user_switcher_header_bg</item>
<item name="android:textSize">@dimen/keyguard_user_switcher_header_text_size</item>
</style>
<style name="Keyguard.UserSwitcher.Spinner.Item">
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:textSize">@dimen/keyguard_user_switcher_item_text_size</item>
</style>
</resources>

View File

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

View File

@@ -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(

View File

@@ -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;

View File

@@ -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<Keyguard
private final SecurityCallback mSecurityCallback;
private final ConfigurationController mConfigurationController;
private final FalsingCollector mFalsingCollector;
private final UserSwitcherController mUserSwitcherController;
private final GlobalSettings mGlobalSettings;
private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
@@ -232,6 +234,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
KeyguardSecurityViewFlipperController securityViewFlipperController,
ConfigurationController configurationController,
FalsingCollector falsingCollector,
UserSwitcherController userSwitcherController,
GlobalSettings globalSettings) {
super(view);
mLockPatternUtils = lockPatternUtils;
@@ -247,6 +250,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mConfigurationController = configurationController;
mLastOrientation = getResources().getConfiguration().orientation;
mFalsingCollector = falsingCollector;
mUserSwitcherController = userSwitcherController;
mGlobalSettings = globalSettings;
}
@@ -343,14 +347,14 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
public void startAppearAnimation() {
if (mCurrentSecurityMode != SecurityMode.None) {
mView.startAppearAnimation(mCurrentSecurityMode);
getCurrentSecurityController().startAppearAnimation();
}
}
public boolean startDisappearAnimation(Runnable onFinishRunnable) {
mView.startDisappearAnimation(getCurrentSecurityMode());
if (mCurrentSecurityMode != SecurityMode.None) {
mView.startDisappearAnimation(mCurrentSecurityMode);
return getCurrentSecurityController().startDisappearAnimation(onFinishRunnable);
}
@@ -506,15 +510,16 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
}
private void configureMode() {
// One-handed mode and user-switcher are currently mutually exclusive, and enforced here
boolean useSimSecurity = mCurrentSecurityMode == SecurityMode.SimPin
|| mCurrentSecurityMode == SecurityMode.SimPuk;
int mode = KeyguardSecurityContainer.MODE_DEFAULT;
if (canDisplayUserSwitcher()) {
if (canDisplayUserSwitcher() && !useSimSecurity) {
mode = KeyguardSecurityContainer.MODE_USER_SWITCHER;
} else if (canUseOneHandedBouncer()) {
mode = KeyguardSecurityContainer.MODE_ONE_HANDED;
}
mView.initMode(mode, mGlobalSettings);
mView.initMode(mode, mGlobalSettings, mFalsingCollector, mUserSwitcherController);
}
public void reportFailedUnlockAttempt(int userId, int timeoutMs) {
@@ -605,6 +610,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
private final ConfigurationController mConfigurationController;
private final FalsingCollector mFalsingCollector;
private final GlobalSettings mGlobalSettings;
private final UserSwitcherController mUserSwitcherController;
@Inject
Factory(KeyguardSecurityContainer view,
@@ -619,6 +625,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
KeyguardSecurityViewFlipperController securityViewFlipperController,
ConfigurationController configurationController,
FalsingCollector falsingCollector,
UserSwitcherController userSwitcherController,
GlobalSettings globalSettings) {
mView = view;
mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory;
@@ -632,6 +639,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mConfigurationController = configurationController;
mFalsingCollector = falsingCollector;
mGlobalSettings = globalSettings;
mUserSwitcherController = userSwitcherController;
}
public KeyguardSecurityContainerController create(
@@ -640,7 +648,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
mKeyguardStateController, securityCallback, mSecurityViewFlipperController,
mConfigurationController, mFalsingCollector, mGlobalSettings);
mConfigurationController, mFalsingCollector, mUserSwitcherController,
mGlobalSettings);
}
}

View File

@@ -83,16 +83,6 @@ public class KeyguardSecurityViewFlipper extends ViewFlipper {
return "";
}
/**
* Translate the entire view, and optionally inform the wrapped view of the progress
* so it can animate with the parent.
*/
public void animateForIme(int translationY, float interpolatedFraction, boolean appearingAnim) {
super.setTranslationY(translationY);
KeyguardInputView v = getSecurityView();
if (v != null) v.animateForIme(interpolatedFraction, appearingAnim);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;

View File

@@ -91,11 +91,6 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView {
// noop.
}
@Override
public boolean startDisappearAnimation(Runnable finishRunnable) {
return false;
}
@Override
public CharSequence getTitle() {
return getContext().getString(

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.keyguard;
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ListPopupWindow;
import android.widget.ListView;
import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
/**
* Custom user-switcher for use on the bouncer.
*/
public class KeyguardUserSwitcherPopupMenu extends ListPopupWindow {
private Context mContext;
private FalsingCollector mFalsingCollector;
private int mLastHeight = -1;
private View.OnLayoutChangeListener mLayoutListener = (v, l, t, r, b, ol, ot, or, ob) -> {
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();
}
}

View File

@@ -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));
}

View File

@@ -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);
}
}

View File

@@ -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<FrameLayout.LayoutParams> 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<UserRecord> buildUserRecords(int count) {
ArrayList<UserRecord> 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;
}
}