diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index a29fcb17b083f..9603f4d43d8bf 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -13580,6 +13580,28 @@ public final class Settings { public static final String POWER_BUTTON_VERY_LONG_PRESS = "power_button_very_long_press"; + + /** + * Keyguard should be on the left hand side of the screen, for wide screen layouts. + * + * @hide + */ + public static final int ONE_HANDED_KEYGUARD_SIDE_LEFT = 0; + + /** + * Keyguard should be on the right hand side of the screen, for wide screen layouts. + * + * @hide + */ + public static final int ONE_HANDED_KEYGUARD_SIDE_RIGHT = 1; + /** + * In one handed mode, which side the keyguard should be on. Allowable values are one of + * the ONE_HANDED_KEYGUARD_SIDE_* constants. + * + * @hide + */ + public static final String ONE_HANDED_KEYGUARD_SIDE = "one_handed_keyguard_side"; + /** * Keys we no longer back up under the current schema, but want to continue to * process when restoring historical backup datasets. diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 367c9b93c4389..416bc84478693 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4708,4 +4708,7 @@ false + + + false diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 4d3f5eb36e54f..672aec62f6a6c 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4174,4 +4174,6 @@ + + diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index 66165b6d1ff2e..ad6a5312f1562 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -147,5 +147,10 @@ public class GlobalSettingsValidators { VALIDATORS.put(Global.DEVELOPMENT_SETTINGS_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.NOTIFICATION_FEEDBACK_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.RESTRICTED_NETWORKING_MODE, BOOLEAN_VALIDATOR); + VALIDATORS.put( + Global.ONE_HANDED_KEYGUARD_SIDE, + new InclusiveIntegerRangeValidator( + /* first= */Global.ONE_HANDED_KEYGUARD_SIDE_LEFT, + /* last= */Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT)); } } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 438cec8ef68f6..c11877a8a9631 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -283,6 +283,7 @@ public class SettingsBackupTest { Settings.Global.EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS, Settings.Global.EUICC_SWITCH_SLOT_TIMEOUT_MILLIS, Settings.Global.FANCY_IME_ANIMATIONS, + Settings.Global.ONE_HANDED_KEYGUARD_SIDE, Settings.Global.FORCE_ALLOW_ON_EXTERNAL, Settings.Global.FORCED_APP_STANDBY_ENABLED, Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED, diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml index 79868093fb127..b5f55af50bff5 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml @@ -24,7 +24,7 @@ + android:layout_width="match_parent" + android:layout_height="match_parent" /> diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml index 04e645bd0a321..c75ee51517d1d 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml @@ -41,13 +41,14 @@ android:layout_gravity="center"> diff --git a/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml b/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml new file mode 100644 index 0000000000000..e09bf7e37ed0c --- /dev/null +++ b/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml @@ -0,0 +1,20 @@ + + + + + true + diff --git a/packages/SystemUI/res-keyguard/values/config.xml b/packages/SystemUI/res-keyguard/values/config.xml index 8d9d6ee68c67d..6176f7c1dd0ac 100644 --- a/packages/SystemUI/res-keyguard/values/config.xml +++ b/packages/SystemUI/res-keyguard/values/config.xml @@ -22,4 +22,5 @@ false + false diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 30db341a061c8..a7ed67287fc73 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -29,12 +29,17 @@ import android.app.AlertDialog; import android.content.Context; import android.graphics.Insets; import android.graphics.Rect; +import android.provider.Settings; import android.util.AttributeSet; import android.util.MathUtils; import android.util.TypedValue; +import android.view.Gravity; import android.view.MotionEvent; +import android.view.OrientationEventListener; import android.view.VelocityTracker; +import android.view.View; import android.view.ViewConfiguration; +import android.view.ViewPropertyAnimator; import android.view.WindowInsets; import android.view.WindowInsetsAnimation; import android.view.WindowInsetsAnimationControlListener; @@ -55,6 +60,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import java.util.List; @@ -99,6 +105,12 @@ public class KeyguardSecurityContainer extends FrameLayout { private boolean mDisappearAnimRunning; private SwipeListener mSwipeListener; + private boolean mIsSecurityViewLeftAligned = true; + private boolean mOneHandedMode = false; + private SecurityMode mSecurityMode = SecurityMode.Invalid; + private ViewPropertyAnimator mRunningOneHandedAnimator; + private final OrientationEventListener mOrientationEventListener; + private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback = new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) { @@ -157,16 +169,20 @@ public class KeyguardSecurityContainer extends FrameLayout { // Used to notify the container when something interesting happens. public interface SecurityCallback { boolean dismiss(boolean authenticated, int targetUserId, boolean bypassSecondaryLockScreen); + void userActivity(); + void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput); /** - * @param strongAuth wheher the user has authenticated with strong authentication like - * pattern, password or PIN but not by trust agents or fingerprint + * @param strongAuth wheher the user has authenticated with strong authentication like + * pattern, password or PIN but not by trust agents or fingerprint * @param targetUserId a user that needs to be the foreground user at the finish completion. */ void finish(boolean strongAuth, int targetUserId); + void reset(); + void onCancelClicked(); } @@ -224,11 +240,119 @@ public class KeyguardSecurityContainer extends FrameLayout { super(context, attrs, defStyle); mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y); mViewConfiguration = ViewConfiguration.get(context); + + mOrientationEventListener = new OrientationEventListener(context) { + @Override + public void onOrientationChanged(int orientation) { + updateLayoutForSecurityMode(mSecurityMode); + } + }; } void onResume(SecurityMode securityMode, boolean faceAuthEnabled) { + mSecurityMode = securityMode; mSecurityViewFlipper.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback); updateBiometricRetry(securityMode, faceAuthEnabled); + + updateLayoutForSecurityMode(securityMode); + mOrientationEventListener.enable(); + } + + void updateLayoutForSecurityMode(SecurityMode securityMode) { + mSecurityMode = securityMode; + mOneHandedMode = canUseOneHandedBouncer(); + + if (mOneHandedMode) { + mIsSecurityViewLeftAligned = isOneHandedKeyguardLeftAligned(mContext); + } + + updateSecurityViewGravity(); + updateSecurityViewLocation(false); + } + + /** Return whether the one-handed keyguard should be enabled. */ + private boolean canUseOneHandedBouncer() { + // Is it enabled? + if (!getResources().getBoolean( + com.android.internal.R.bool.config_enableOneHandedKeyguard)) { + return false; + } + + if (!KeyguardSecurityModel.isSecurityViewOneHanded(mSecurityMode)) { + return false; + } + + return getResources().getBoolean(R.bool.can_use_one_handed_bouncer); + } + + /** Read whether the one-handed keyguard should be on the left/right from settings. */ + private boolean isOneHandedKeyguardLeftAligned(Context context) { + try { + return Settings.Global.getInt(context.getContentResolver(), + Settings.Global.ONE_HANDED_KEYGUARD_SIDE) + == Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT; + } catch (Settings.SettingNotFoundException ex) { + return true; + } + } + + private void updateSecurityViewGravity() { + View securityView = getChildAt(0); + + if (securityView == null) { + return; + } + + FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) securityView.getLayoutParams(); + + if (mOneHandedMode) { + lp.gravity = Gravity.LEFT | Gravity.BOTTOM; + } else { + lp.gravity = Gravity.CENTER; + } + + securityView.setLayoutParams(lp); + } + + /** + * Moves the inner security view to the correct location (in one handed mode) with animation. + * This is triggered when the user taps on the side of the screen that is not currently occupied + * by the security view . + */ + private void updateSecurityViewLocation(boolean animate) { + View securityView = getChildAt(0); + + if (securityView == null) { + return; + } + + if (!mOneHandedMode) { + securityView.setTranslationX(0); + return; + } + + if (mRunningOneHandedAnimator != null) { + mRunningOneHandedAnimator.cancel(); + mRunningOneHandedAnimator = null; + } + + int targetTranslation = mIsSecurityViewLeftAligned ? 0 : (int) (getMeasuredWidth() / 2f); + + if (animate) { + mRunningOneHandedAnimator = securityView.animate().translationX(targetTranslation); + mRunningOneHandedAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + mRunningOneHandedAnimator.setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRunningOneHandedAnimator = null; + } + }); + + mRunningOneHandedAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); + mRunningOneHandedAnimator.start(); + } else { + securityView.setTranslationX(targetTranslation); + } } public void onPause() { @@ -237,6 +361,7 @@ public class KeyguardSecurityContainer extends FrameLayout { mAlertDialog = null; } mSecurityViewFlipper.setWindowInsetsAnimationCallback(null); + mOrientationEventListener.disable(); } @Override @@ -318,19 +443,44 @@ public class KeyguardSecurityContainer extends FrameLayout { if (mSwipeListener != null) { mSwipeListener.onSwipeUp(); } + } else { + if (!mIsDragging) { + handleTap(event); + } } } return true; } + private void handleTap(MotionEvent event) { + // If we're using a fullscreen security mode, skip + if (!mOneHandedMode) { + return; + } + + // Did the tap hit the "other" side of the bouncer? + if ((mIsSecurityViewLeftAligned && (event.getX() > getWidth() / 2f)) + || (!mIsSecurityViewLeftAligned && (event.getX() < getWidth() / 2f))) { + mIsSecurityViewLeftAligned = !mIsSecurityViewLeftAligned; + + Settings.Global.putInt( + mContext.getContentResolver(), + Settings.Global.ONE_HANDED_KEYGUARD_SIDE, + mIsSecurityViewLeftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT + : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT); + + updateSecurityViewLocation(true); + } + } + void setSwipeListener(SwipeListener swipeListener) { mSwipeListener = swipeListener; } private void startSpringAnimation(float startVelocity) { mSpringAnimation - .setStartVelocity(startVelocity) - .animateToFinalPosition(0); + .setStartVelocity(startVelocity) + .animateToFinalPosition(0); } public void startDisappearAnimation(SecurityMode securitySelection) { @@ -440,18 +590,17 @@ public class KeyguardSecurityContainer extends FrameLayout { return insets.inset(0, 0, 0, inset); } - private void showDialog(String title, String message) { if (mAlertDialog != null) { mAlertDialog.dismiss(); } mAlertDialog = new AlertDialog.Builder(mContext) - .setTitle(title) - .setMessage(message) - .setCancelable(false) - .setNeutralButton(R.string.ok, null) - .create(); + .setTitle(title) + .setMessage(message) + .setCancelable(false) + .setNeutralButton(R.string.ok, null) + .create(); if (!(mContext instanceof Activity)) { mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); } @@ -489,6 +638,53 @@ public class KeyguardSecurityContainer extends FrameLayout { } } + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // This is a little hacky, but this element only ever has one wrap_content child, and is + // itself set to match_parent, so we can take a couple of shortcuts compared to + // FrameLayout#onMeasure + int maxHeight = 0; + int maxWidth = 0; + int childState = 0; + + int count = getChildCount(); + + int halfWidthMeasureSpec = MeasureSpec.makeMeasureSpec( + MeasureSpec.getSize(widthMeasureSpec) / 2, + MeasureSpec.getMode(widthMeasureSpec)); + + if (count > 1) { + throw new IllegalStateException("KeyguardSecurityContainer should only have one child"); + } + + if (count > 0) { + final View securityView = getChildAt(0); + if (securityView.getVisibility() != GONE) { + if (mOneHandedMode) { + measureChildWithMargins(securityView, halfWidthMeasureSpec, 0, + heightMeasureSpec, 0); + } else { + measureChildWithMargins(securityView, widthMeasureSpec, 0, + heightMeasureSpec, 0); + } + final LayoutParams lp = (LayoutParams) securityView.getLayoutParams(); + maxWidth = Math.max(maxWidth, + securityView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); + maxHeight = Math.max(maxHeight, + securityView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); + childState = combineMeasuredStates(childState, securityView.getMeasuredState()); + } + } + + // Check against our minimum height and width + maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); + maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); + + setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), + resolveSizeAndState(maxHeight, heightMeasureSpec, + childState << MEASURED_HEIGHT_STATE_SHIFT)); + } + void showAlmostAtWipeDialog(int attempts, int remaining, int userType) { String message = null; switch (userType) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 1a8d420fb3945..fdab8db67431f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -404,6 +404,7 @@ public class KeyguardSecurityContainerController extends ViewController