Refactor choose lock password scrolling

Replace the negative margin + inset background hack with a overriding
call to requestRectangleOnScreen, which will also make a best effort
to show all the password requirements if there are multiple of them.

Bug: 38236574
Test: Added ScrollToParentEditTextTest
Change-Id: Id570652b64434b2d0b25c231540375d93884aaa9
This commit is contained in:
Maurice Lam
2017-04-27 19:04:46 -07:00
parent 6f33a52d32
commit c41e408aea
9 changed files with 246 additions and 126 deletions

View File

@@ -22,7 +22,6 @@
<include layout="@layout/confirm_lock_background_base" />
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/topLayout"
android:orientation="vertical"
android:layout_width="match_parent"
@@ -45,18 +44,12 @@
android:layout_marginEnd="24dp"
android:layout_marginTop="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
android:layout_height="wrap_content"/>
<FrameLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:orientation="horizontal">
android:layout_height="0dp"
android:layout_weight="1">
<Button
style="@android:style/Widget.Material.Button.Borderless"
@@ -69,24 +62,31 @@
android:layout_marginEnd="8dp"
android:layout_marginBottom="14dp"/>
<EditText android:id="@+id/password_entry"
android:layout_width="208dp"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="26dp"
android:inputType="textPassword"
android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
android:gravity="center"
android:textSize="16sp"
style="@style/TextAppearance.PasswordEntry"/>
android:layout_gravity="center_vertical"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView style="@style/TextAppearance.ConfirmDeviceCredentialsErrorText"
android:accessibilityLiveRegion="polite"
android:id="@+id/errorText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="10dp"/>
<com.android.settings.widget.ScrollToParentEditText
android:id="@+id/password_entry"
android:layout_width="208dp"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
android:gravity="center"
android:textSize="16sp"
style="@style/TextAppearance.PasswordEntry"/>
<TextView style="@style/TextAppearance.ConfirmDeviceCredentialsErrorText"
android:accessibilityLiveRegion="polite"
android:id="@+id/errorText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"/>
</LinearLayout>
<ImageView
android:id="@+id/fingerprintIcon"

View File

@@ -42,29 +42,30 @@
android:lines="2"
android:textAppearance="?android:attr/textAppearanceMedium"/>
<!-- Password entry field -->
<EditText android:id="@+id/password_entry"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:inputType="textPassword"
android:imeOptions="actionNext|flagNoExtractUi|flagForceAscii"
android:textSize="24sp"
style="@style/TextAppearance.PasswordEntry"/>
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
android:paddingBottom="8dp">
<LinearLayout
android:id="@+id/bottom_container"
<!-- Password entry field -->
<com.android.settings.widget.ScrollToParentEditText
android:id="@+id/password_entry"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical">
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:inputType="textPassword"
android:imeOptions="actionNext|flagNoExtractUi|flagForceAscii"
android:textSize="24sp"
style="@style/TextAppearance.PasswordEntry"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/password_requirements_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
android:id="@+id/password_requirements_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>

View File

@@ -47,8 +47,7 @@
android:layout_marginEnd="?attr/confirmDeviceCredentialsSideMargin"
android:layout_marginTop="12dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
android:layout_height="wrap_content" />
<Button
style="@android:style/Widget.Material.Button.Borderless"
@@ -64,29 +63,36 @@
android:layout_height="0dp"
android:layout_weight="1"/>
<EditText
android:id="@+id/password_entry"
android:layout_width="208dp"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="-40dp"
android:inputType="textPassword"
android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
android:gravity="center"
android:textSize="16sp"
style="@style/TextAppearance.PasswordEntry"/>
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
style="@style/TextAppearance.ConfirmDeviceCredentialsErrorText"
android:accessibilityLiveRegion="polite"
android:id="@+id/errorText"
android:layout_width="wrap_content"
<com.android.settings.widget.ScrollToParentEditText
android:id="@+id/password_entry"
android:layout_width="208dp"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
android:gravity="center"
android:textSize="16sp"
style="@style/TextAppearance.PasswordEntry"/>
<TextView
style="@style/TextAppearance.ConfirmDeviceCredentialsErrorText"
android:accessibilityLiveRegion="polite"
android:id="@+id/errorText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"/>
</LinearLayout>
<View android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_gravity="center_horizontal"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:gravity="center_vertical"/>
android:layout_weight="1"/>
<ImageView
android:id="@+id/fingerprintIcon"

View File

@@ -19,7 +19,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:icon="@drawable/ic_lock"
android:layout="@layout/suw_glif_blank_template"
android:importantForAutofill="noExcludeDescendants">
<com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient
@@ -28,19 +27,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/suw_layout_icon"
style="@style/SuwGlifIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_lock" />
<TextView
android:id="@+id/headerText"
style="@style/SuwGlifHeaderTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/detailsText"
style="@style/SuwDescription.Glif"
@@ -54,29 +40,36 @@
android:layout_height="0dp"
android:layout_weight="1" />
<EditText
android:id="@+id/password_entry"
android:layout_width="208dp"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="-40dp"
android:inputType="textPassword"
android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
android:gravity="center"
android:textSize="16sp"
style="@style/TextAppearance.PasswordEntry"/>
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
style="@style/TextAppearance.ConfirmDeviceCredentialsErrorText"
android:accessibilityLiveRegion="polite"
android:id="@+id/errorText"
android:layout_width="wrap_content"
<com.android.settings.widget.ScrollToParentEditText
android:id="@+id/password_entry"
android:layout_width="208dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:inputType="textPassword"
android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
android:gravity="center"
android:textSize="16sp"
style="@style/TextAppearance.PasswordEntry"/>
<TextView
android:id="@+id/errorText"
style="@style/TextAppearance.ConfirmDeviceCredentialsErrorText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:accessibilityLiveRegion="polite"/>
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_gravity="center_horizontal"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:gravity="center_vertical"/>
android:layout_weight="1" />
<ImageView
android:id="@+id/fingerprintIcon"

View File

@@ -300,9 +300,6 @@
<dimen name="support_spacer_height">8dp</dimen>
<dimen name="password_requirement_textsize">14sp</dimen>
<!-- Visible vertical space we want to show below password edittext field when ime is shown.
The unit is sp as it is related to the text size of password requirement item. -->
<dimen name="visible_vertical_space_below_password">20sp</dimen>
<!-- Padding for the escalation card in normal dimens -->
<dimen name="support_escalation_card_padding_start">40dp</dimen>

View File

@@ -16,12 +16,19 @@
package com.android.settings;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import android.app.Activity;
import android.app.Fragment;
import android.app.admin.DevicePolicyManager;
import android.app.admin.PasswordMetrics;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.graphics.drawable.InsetDrawable;
import android.os.Bundle;
import android.os.Handler;
@@ -34,13 +41,15 @@ import android.text.Selection;
import android.text.Spannable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
@@ -59,12 +68,6 @@ import com.android.setupwizardlib.GlifLayout;
import java.util.ArrayList;
import java.util.List;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
public class ChooseLockPassword extends SettingsActivity {
public static final String PASSWORD_MIN_KEY = "lockscreen.password_min";
public static final String PASSWORD_MAX_KEY = "lockscreen.password_max";
@@ -357,23 +360,6 @@ public class ChooseLockPassword extends SettingsActivity {
FRAGMENT_TAG_SAVE_AND_FINISH);
}
// Workaround to show one password requirement below EditText when IME is shown.
// By adding an inset to the edit text background, we make the EditText occupy more
// vertical space, and the keyboard will then avoid hiding it. We have also set
// negative margin in the layout below in order to have them show in the correct
// position.
final int visibleVerticalSpaceBelowPassword =
getResources().getDimensionPixelOffset(
R.dimen.visible_vertical_space_below_password);
InsetDrawable drawable =
new InsetDrawable(
mPasswordEntry.getBackground(), 0, 0, 0, visibleVerticalSpaceBelowPassword);
mPasswordEntry.setBackgroundDrawable(drawable);
LinearLayout bottomContainer = (LinearLayout) view.findViewById(R.id.bottom_container);
LinearLayout.LayoutParams bottomContainerLp =
(LinearLayout.LayoutParams) bottomContainer.getLayoutParams();
bottomContainerLp.setMargins(0, -visibleVerticalSpaceBelowPassword, 0, 0);
if (activity instanceof SettingsActivity) {
final SettingsActivity sa = (SettingsActivity) activity;
int id = mIsAlphaMode ? R.string.lockpassword_choose_your_password_header

View File

@@ -136,6 +136,9 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry);
mHeaderTextView = (TextView) view.findViewById(R.id.headerText);
if (mHeaderTextView == null) {
mHeaderTextView = view.findViewById(R.id.suw_layout_title);
}
mDetailsTextView = (TextView) view.findViewById(R.id.detailsText);
mErrorTextView = (TextView) view.findViewById(R.id.errorText);
mIsAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == storedQuality

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2017 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.settings.widget;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewParent;
import android.widget.EditText;
/**
* An EditText that, instead of scrolling to itself when focused, will request scrolling to its
* parent. This is used in ChooseLockPassword to do make a best effort for not hiding the error
* messages for why the password is invalid under the keyboard.
*/
public class ScrollToParentEditText extends EditText {
private Rect mRect = new Rect();
public ScrollToParentEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean requestRectangleOnScreen(Rect rectangle, boolean immediate) {
ViewParent parent = getParent();
if (parent instanceof View) {
// Request the entire parent view to be shown, which in ChooseLockPassword's case,
// will include messages for why the password is invalid (if any).
((View) parent).getDrawingRect(mRect);
return ((View) parent).requestRectangleOnScreen(mRect, immediate);
} else {
return super.requestRectangleOnScreen(rectangle, immediate);
}
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 2017 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.settings.widget;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.robolectric.RuntimeEnvironment.application;
import static org.robolectric.Shadows.shadowOf;
import android.graphics.Rect;
import android.widget.FrameLayout;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowView;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class ScrollToParentEditTextTest {
private static final int EDIT_TEXT_SIZE = 20;
private static final int PARENT_SIZE = 50;
private static final int SCROLL_RECT_SIZE = 30;
private ScrollToParentEditText mEditText;
private FrameLayout mParent;
@Before
public void setUp() {
mEditText = new ScrollToParentEditText(
application,
Robolectric.buildAttributeSet().build());
mEditText.layout(0, 0, EDIT_TEXT_SIZE, EDIT_TEXT_SIZE);
mParent = spy(new FrameLayout(application));
mParent.layout(0, 0, PARENT_SIZE, PARENT_SIZE);
doReturn(true).when(mParent).requestRectangleOnScreen(any(Rect.class), anyBoolean());
}
@Test
public void requestRectangleOnScreen_noParent_shouldScrollToItself() {
assertThat(mEditText.requestRectangleOnScreen(
new Rect(0, 0, SCROLL_RECT_SIZE, SCROLL_RECT_SIZE), true)).isFalse();
}
@Test
public void requestRectangleOnScreen_withParent_shouldScrollToParent() {
ShadowView shadowEditText = shadowOf(mEditText);
shadowEditText.setMyParent(mParent);
assertThat(mEditText.requestRectangleOnScreen(
new Rect(0, 0, SCROLL_RECT_SIZE, SCROLL_RECT_SIZE), true)).isTrue();
verify(mParent)
.requestRectangleOnScreen(eq(new Rect(0, 0, PARENT_SIZE, PARENT_SIZE)), eq(true));
}
}