From 693c14fbe028a5d13d2765c3567783eb2b81d18e Mon Sep 17 00:00:00 2001 From: Wilson Wu Date: Fri, 27 Aug 2021 10:21:51 +0800 Subject: [PATCH] Move PrimarySwitchPreference into Settingslib IME on-screen keyboard setting had 2 kinds of pages. One page is used to display current enabled IME. Another one is used to enable/disable IME by users. In T, we hope to combine these 2 pages into a single one page to support above functionality. Our goal is that InputMethodPreference can support two target preference as: - Tap left title into IME settings - Enable/disable IME by right switch Move PrimarySwitchPreference into SettingsLib so InputMethodPreference can make use of it directly. Basically this change shouldn't change any UI behavior. Bug: 197705032 Test: make RunSettingsLibRoboTests Change-Id: Idea3935d95b8d22b00ca89313e9eb3e979e4aa74 --- .../preference_widget_primary_switch.xml | 24 +++ ...icted_preference_widget_primary_switch.xml | 21 +++ .../settingslib/PrimarySwitchPreference.java | 160 ++++++++++++++++ .../PrimarySwitchPreferenceTest.java | 172 ++++++++++++++++++ 4 files changed, 377 insertions(+) create mode 100644 packages/SettingsLib/res/layout/preference_widget_primary_switch.xml create mode 100644 packages/SettingsLib/res/layout/restricted_preference_widget_primary_switch.xml create mode 100644 packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java create mode 100644 packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java diff --git a/packages/SettingsLib/res/layout/preference_widget_primary_switch.xml b/packages/SettingsLib/res/layout/preference_widget_primary_switch.xml new file mode 100644 index 0000000000000..5de268d529175 --- /dev/null +++ b/packages/SettingsLib/res/layout/preference_widget_primary_switch.xml @@ -0,0 +1,24 @@ + + + + \ No newline at end of file diff --git a/packages/SettingsLib/res/layout/restricted_preference_widget_primary_switch.xml b/packages/SettingsLib/res/layout/restricted_preference_widget_primary_switch.xml new file mode 100644 index 0000000000000..69df7516a82a9 --- /dev/null +++ b/packages/SettingsLib/res/layout/restricted_preference_widget_primary_switch.xml @@ -0,0 +1,21 @@ + + + + + + + + \ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java new file mode 100644 index 0000000000000..246fc8ddd4668 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java @@ -0,0 +1,160 @@ +/* + * 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.settingslib; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Switch; + +import androidx.annotation.Keep; +import androidx.annotation.Nullable; +import androidx.preference.PreferenceViewHolder; + +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; + +/** + * A custom preference that provides inline switch toggle. It has a mandatory field for title, and + * optional fields for icon and sub-text. And it can be restricted by admin state. + */ +public class PrimarySwitchPreference extends RestrictedPreference { + + private Switch mSwitch; + private boolean mChecked; + private boolean mCheckedSet; + private boolean mEnableSwitch = true; + + public PrimarySwitchPreference(Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public PrimarySwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public PrimarySwitchPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public PrimarySwitchPreference(Context context) { + super(context); + } + + @Override + protected int getSecondTargetResId() { + return R.layout.restricted_preference_widget_primary_switch; + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + final View switchWidget = holder.findViewById(R.id.switchWidget); + if (switchWidget != null) { + switchWidget.setVisibility(isDisabledByAdmin() ? View.GONE : View.VISIBLE); + switchWidget.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mSwitch != null && !mSwitch.isEnabled()) { + return; + } + setChecked(!mChecked); + if (!callChangeListener(mChecked)) { + setChecked(!mChecked); + } else { + persistBoolean(mChecked); + } + } + }); + + // Consumes move events to ignore drag actions. + switchWidget.setOnTouchListener((v, event) -> { + return event.getActionMasked() == MotionEvent.ACTION_MOVE; + }); + } + + mSwitch = (Switch) holder.findViewById(R.id.switchWidget); + if (mSwitch != null) { + mSwitch.setContentDescription(getTitle()); + mSwitch.setChecked(mChecked); + mSwitch.setEnabled(mEnableSwitch); + } + } + + public boolean isChecked() { + return mSwitch != null && mChecked; + } + + /** + * Used to validate the state of mChecked and mCheckedSet when testing, without requiring + * that a ViewHolder be bound to the object. + */ + @Keep + @Nullable + public Boolean getCheckedState() { + return mCheckedSet ? mChecked : null; + } + + /** + * Set the checked status to be {@code checked}. + * + * @param checked The new checked status + */ + public void setChecked(boolean checked) { + // Always set checked the first time; don't assume the field's default of false. + final boolean changed = mChecked != checked; + if (changed || !mCheckedSet) { + mChecked = checked; + mCheckedSet = true; + if (mSwitch != null) { + mSwitch.setChecked(checked); + } + } + } + + /** + * Set the Switch to be the status of {@code enabled}. + * + * @param enabled The new enabled status + */ + public void setSwitchEnabled(boolean enabled) { + mEnableSwitch = enabled; + if (mSwitch != null) { + mSwitch.setEnabled(enabled); + } + } + + /** + * If admin is not null, disables the switch. + * Otherwise, keep it enabled. + */ + public void setDisabledByAdmin(EnforcedAdmin admin) { + super.setDisabledByAdmin(admin); + setSwitchEnabled(admin == null); + } + + public Switch getSwitch() { + return mSwitch; + } + + @Override + protected boolean shouldHideSecondTarget() { + return getSecondTargetResId() == 0; + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java new file mode 100644 index 0000000000000..4e2b63b6be578 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java @@ -0,0 +1,172 @@ +/* + * 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.settingslib; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.view.LayoutInflater; +import android.widget.LinearLayout; +import android.widget.Switch; + +import androidx.preference.Preference.OnPreferenceChangeListener; +import androidx.preference.PreferenceViewHolder; + +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class PrimarySwitchPreferenceTest { + + private Context mContext; + private PrimarySwitchPreference mPreference; + private PreferenceViewHolder mHolder; + private LinearLayout mWidgetView; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mPreference = new PrimarySwitchPreference(mContext); + LayoutInflater inflater = LayoutInflater.from(mContext); + mHolder = PreferenceViewHolder.createInstanceForTests(inflater.inflate( + com.android.settingslib.R.layout.preference_two_target, null)); + mWidgetView = mHolder.itemView.findViewById(android.R.id.widget_frame); + inflater.inflate(R.layout.restricted_preference_widget_primary_switch, mWidgetView, true); + } + + @Test + public void createNewPreference_shouldSetLayout() { + assertThat(mPreference.getWidgetLayoutResource()) + .isEqualTo(R.layout.restricted_preference_widget_primary_switch); + } + + @Test + public void setChecked_shouldUpdateButtonCheckedState() { + final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget); + mPreference.onBindViewHolder(mHolder); + + mPreference.setChecked(true); + assertThat(toggle.isChecked()).isTrue(); + + mPreference.setChecked(false); + assertThat(toggle.isChecked()).isFalse(); + } + + @Test + public void setSwitchEnabled_shouldUpdateButtonEnabledState() { + final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget); + mPreference.onBindViewHolder(mHolder); + + mPreference.setSwitchEnabled(true); + assertThat(toggle.isEnabled()).isTrue(); + + mPreference.setSwitchEnabled(false); + assertThat(toggle.isEnabled()).isFalse(); + } + + @Test + public void setSwitchEnabled_shouldUpdateButtonEnabledState_beforeViewBound() { + final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget); + + mPreference.setSwitchEnabled(false); + mPreference.onBindViewHolder(mHolder); + assertThat(toggle.isEnabled()).isFalse(); + } + + @Test + public void clickWidgetView_shouldToggleButton() { + assertThat(mWidgetView).isNotNull(); + + final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget); + mPreference.onBindViewHolder(mHolder); + + toggle.performClick(); + assertThat(toggle.isChecked()).isTrue(); + + toggle.performClick(); + assertThat(toggle.isChecked()).isFalse(); + } + + @Test + public void clickWidgetView_shouldNotToggleButtonIfDisabled() { + assertThat(mWidgetView).isNotNull(); + + final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget); + mPreference.onBindViewHolder(mHolder); + toggle.setEnabled(false); + + mWidgetView.performClick(); + assertThat(toggle.isChecked()).isFalse(); + } + + @Test + public void clickWidgetView_shouldNotifyPreferenceChanged() { + + final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget); + + final OnPreferenceChangeListener listener = mock(OnPreferenceChangeListener.class); + mPreference.setOnPreferenceChangeListener(listener); + mPreference.onBindViewHolder(mHolder); + + mPreference.setChecked(false); + toggle.performClick(); + verify(listener).onPreferenceChange(mPreference, true); + + mPreference.setChecked(true); + toggle.performClick(); + verify(listener).onPreferenceChange(mPreference, false); + } + + @Test + public void setDisabledByAdmin_hasEnforcedAdmin_shouldDisableButton() { + final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget); + toggle.setEnabled(true); + mPreference.onBindViewHolder(mHolder); + + mPreference.setDisabledByAdmin(mock(EnforcedAdmin.class)); + assertThat(toggle.isEnabled()).isFalse(); + } + + @Test + public void setDisabledByAdmin_noEnforcedAdmin_shouldEnableButton() { + final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget); + toggle.setEnabled(false); + mPreference.onBindViewHolder(mHolder); + + mPreference.setDisabledByAdmin(null); + assertThat(toggle.isEnabled()).isTrue(); + } + + @Test + public void onBindViewHolder_toggleButtonShouldHaveContentDescription() { + final Switch toggle = (Switch) mHolder.findViewById(R.id.switchWidget); + final String label = "TestButton"; + mPreference.setTitle(label); + + mPreference.onBindViewHolder(mHolder); + + assertThat(toggle.getContentDescription()).isEqualTo(label); + } +}