From ce4990aed923fead83098bcc938f42bffbbaddd9 Mon Sep 17 00:00:00 2001 From: Vadim Caen Date: Thu, 7 Apr 2022 16:38:04 +0200 Subject: [PATCH] Add Settings for back animation developer option Bug: 228936326 Test: BackAnimationPreferenceController Change-Id: I48516c1f68de799473eac99d35a086046d7e6de9 --- res/xml/development_settings.xml | 5 + .../BackAnimationPreferenceController.java | 89 +++++++++++ .../BackAnimationPreferenceDialog.java | 79 +++++++++ .../DevelopmentSettingsDashboardFragment.java | 1 + ...BackAnimationPreferenceControllerTest.java | 151 ++++++++++++++++++ 5 files changed, 325 insertions(+) create mode 100644 src/com/android/settings/development/BackAnimationPreferenceController.java create mode 100644 src/com/android/settings/development/BackAnimationPreferenceDialog.java create mode 100644 tests/unit/src/com/android/settings/development/BackAnimationPreferenceControllerTest.java diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index 89e5167a11b..6319763f579 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -649,6 +649,11 @@ android:title="@string/enable_non_resizable_multi_window" android:summary="@string/enable_non_resizable_multi_window_summary" /> + + diff --git a/src/com/android/settings/development/BackAnimationPreferenceController.java b/src/com/android/settings/development/BackAnimationPreferenceController.java new file mode 100644 index 00000000000..003b1f92850 --- /dev/null +++ b/src/com/android/settings/development/BackAnimationPreferenceController.java @@ -0,0 +1,89 @@ +/* + * 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.development; + +import android.content.Context; +import android.provider.Settings; + +import androidx.preference.Preference; +import androidx.preference.SwitchPreference; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.development.DeveloperOptionsPreferenceController; + +import java.util.Objects; + +/** + * PreferenceController for enabling/disabling animation related to back button and back gestures. + */ +public class BackAnimationPreferenceController extends DeveloperOptionsPreferenceController + implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin { + + private static final String BACK_NAVIGATION_ANIMATION_KEY = + "back_navigation_animation"; + + private static final int SETTING_VALUE_OFF = 0; + private static final int SETTING_VALUE_ON = 1; + private final DevelopmentSettingsDashboardFragment mFragment; + + @VisibleForTesting + BackAnimationPreferenceController(Context context) { + super(context); + mFragment = null; + } + + + public BackAnimationPreferenceController(Context context, + DevelopmentSettingsDashboardFragment fragment) { + super(context); + Objects.requireNonNull(fragment); + mFragment = fragment; + } + + @Override + public String getPreferenceKey() { + return BACK_NAVIGATION_ANIMATION_KEY; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final boolean isEnabled = (Boolean) newValue; + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.ENABLE_BACK_ANIMATION, + isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF); + if (mFragment != null && isEnabled) { + BackAnimationPreferenceDialog.show(mFragment); + } + return true; + } + + @Override + public void updateState(Preference preference) { + final int mode = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF); + ((SwitchPreference) mPreference).setChecked(mode != SETTING_VALUE_OFF); + } + + @Override + protected void onDeveloperOptionsSwitchDisabled() { + super.onDeveloperOptionsSwitchDisabled(); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF); + ((SwitchPreference) mPreference).setChecked(false); + } +} diff --git a/src/com/android/settings/development/BackAnimationPreferenceDialog.java b/src/com/android/settings/development/BackAnimationPreferenceDialog.java new file mode 100644 index 00000000000..8347e8dea95 --- /dev/null +++ b/src/com/android/settings/development/BackAnimationPreferenceDialog.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2022 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.development; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.settings.SettingsEnums; +import android.content.DialogInterface; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; + +import com.android.settings.R; +import com.android.settings.core.instrumentation.InstrumentedDialogFragment; + +/** + * Information dialog shown when enabling back animations + */ +public class BackAnimationPreferenceDialog extends InstrumentedDialogFragment + implements DialogInterface.OnClickListener { + + public static final String TAG = "BackAnimationDlg"; + + private BackAnimationPreferenceDialog() { + } + + /** + * Show this dialog. + */ + public static void show(@NonNull Fragment host) { + FragmentActivity activity = host.getActivity(); + if (activity == null) { + return; + } + FragmentManager manager = activity.getSupportFragmentManager(); + if (manager.findFragmentByTag(TAG) == null) { + BackAnimationPreferenceDialog dialog = new BackAnimationPreferenceDialog(); + dialog.setTargetFragment(host, 0 /* requestCode */); + dialog.show(manager, TAG); + } + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.DIALOG_BACK_ANIMATIONS; + } + + @Override + @NonNull + public Dialog onCreateDialog(Bundle savedInstanceState) { + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.back_navigation_animation) + .setMessage(R.string.back_navigation_animation_dialog) + .setPositiveButton(android.R.string.ok, this /* onClickListener */) + .create(); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + // Do nothing + } +} diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index 722e94e409b..3fbea5249a0 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -607,6 +607,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra controllers.add(new OverlaySettingsPreferenceController(context)); controllers.add(new StylusHandwritingPreferenceController(context)); controllers.add(new IngressRateLimitPreferenceController((context))); + controllers.add(new BackAnimationPreferenceController(context, fragment)); return controllers; } diff --git a/tests/unit/src/com/android/settings/development/BackAnimationPreferenceControllerTest.java b/tests/unit/src/com/android/settings/development/BackAnimationPreferenceControllerTest.java new file mode 100644 index 00000000000..36693583fe9 --- /dev/null +++ b/tests/unit/src/com/android/settings/development/BackAnimationPreferenceControllerTest.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2022 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.development; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.app.Instrumentation; +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.provider.Settings; + +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidJUnit4.class) +public class BackAnimationPreferenceControllerTest { + + private static final int SETTING_VALUE_OFF = 0; + private static final int SETTING_VALUE_ON = 1; + + private SwitchPreference mPreference; + + private Context mContext; + private BackAnimationPreferenceController mController; + private Looper mLooper; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + mContext = instrumentation.getTargetContext(); + mController = new BackAnimationPreferenceController(mContext); + mPreference = new SwitchPreference(mContext); + if (Looper.myLooper() == null) { + Looper.prepare(); + } + mLooper = Looper.myLooper(); + + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.ENABLE_BACK_ANIMATION, -1); + + final PreferenceManager preferenceManager = new PreferenceManager(mContext); + final PreferenceScreen screen = preferenceManager.createPreferenceScreen(mContext); + mPreference.setKey(mController.getPreferenceKey()); + screen.addPreference(mPreference); + mController.displayPreference(screen); + } + + @Test + public void onPreferenceChange_switchEnabled_shouldEnableBackAnimations() { + mController.onPreferenceChange(mPreference, true /* new value */); + + final int mode = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.ENABLE_BACK_ANIMATION, -1 /* default */); + assertThat(mode).isEqualTo(SETTING_VALUE_ON); + } + + @Test + public void onPreferenceChange_switchDisabled_shouldDisableBackAnimations() { + mController.onPreferenceChange(mPreference, false /* new value */); + + final int mode = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.ENABLE_BACK_ANIMATION, -1 /* default */); + assertThat(mode).isEqualTo(SETTING_VALUE_OFF); + } + + @Test + public void updateState_settingEnabled_preferenceShouldBeChecked() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_ON); + mController.updateState(mPreference); + assertTrue(mPreference.isChecked()); + } + + @Test + public void updateState_settingDisabled_preferenceShouldNotBeChecked() { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF); + + mController.updateState(mPreference); + assertFalse(mPreference.isChecked()); + } + + @Test + public void onDeveloperOptionsSwitchDisabled_shouldDisablePreference() + throws InterruptedException { + ContentResolver contentResolver = mContext.getContentResolver(); + int mode = doAndWaitForSettingChange(() -> mController.onDeveloperOptionsSwitchDisabled(), + contentResolver); + assertThat(mode).isEqualTo(SETTING_VALUE_OFF); + assertFalse(mPreference.isEnabled()); + assertFalse(mPreference.isChecked()); + } + + private int doAndWaitForSettingChange(Runnable runnable, ContentResolver contentResolver) { + CountDownLatch countDownLatch = new CountDownLatch(1); + ContentObserver settingsObserver = + new ContentObserver(new Handler(mLooper)) { + @Override + public void onChange(boolean selfChange, Uri uri) { + countDownLatch.countDown(); + } + }; + contentResolver.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.ENABLE_BACK_ANIMATION), + false, settingsObserver, UserHandle.USER_SYSTEM + ); + runnable.run(); + try { + countDownLatch.await(500, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + Assert.fail(e.getMessage()); + } + return Settings.Global.getInt(contentResolver, + Settings.Global.ENABLE_BACK_ANIMATION, -1 /* default */); + } +}