Settings: Use seekbar to allow setting arbitrary animation values

frap129: Reworked for Pie, set default scale to 0.5f, and improved
the preference summaries so they match AOSP while keeping translations.

[jaysonedson: Fix fc when not using English and preference]

Change-Id: I29d89e532d07a325f6e568b21e4da4ce7e5761ac
diff --git a/res/layout/preference_dialog_animation_scale.xml b/res/layout/preference_dialog_animation_scale.xml
new file mode 100644
index 0000000..5723ad0
--- /dev/null
+++ b/res/layout/preference_dialog_animation_scale.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Pure Nexus 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:padding="12dp">
+
+        <!-- Static height enough to accommodate the text views in their biggest possible size,
+        without having the dialog resize itself at any point. -->
+        <LinearLayout android:id="@+id/container"
+                 android:orientation="vertical"
+                 android:layout_width="match_parent"
+                 android:layout_height="64dp"
+                 android:gravity="center_horizontal|center_vertical">
+
+                <TextView android:id="@+id/scale"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:textAppearance="?android:textAppearanceLarge" />
+
+        </LinearLayout>
+
+        <com.android.settings.IntervalSeekBar android:id="@+id/scale_seekbar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:padding="8dp"
+                android:layout_below="@+id/container"
+                settings:minI="0"
+                settings:maxI="1.5"
+                settings:defaultValue="1.0"
+                settings:digits="2" />
+
+</RelativeLayout>
diff --git a/res/values/leaf_attrs.xml b/res/values/leaf_attrs.xml
new file mode 100644
index 0000000..c8441a0
--- /dev/null
+++ b/res/values/leaf_attrs.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2024 LeafOS 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.
+-->
+<resources>
+
+    <!-- Interval seekbar -->
+    <declare-styleable name="IntervalSeekBar">
+        <attr name="minI" format="float" />
+        <attr name="maxI" format="float" />
+        <attr name="defaultValuePure" format="float" />
+        <attr name="digits" format="integer" />
+    </declare-styleable>
+
+</resources>
diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml
index c0b6560..cb95a8a 100644
--- a/res/xml/development_settings.xml
+++ b/res/xml/development_settings.xml
@@ -521,19 +521,19 @@
             android:title="@string/force_rtl_layout_all_locales"
             android:summary="@string/force_rtl_layout_all_locales_summary" />
 
-        <ListPreference
+        <com.android.settings.AnimationScalePreference
             android:key="window_animation_scale"
             android:title="@string/window_animation_scale_title"
             android:entries="@array/window_animation_scale_entries"
             android:entryValues="@array/window_animation_scale_values" />
 
-        <ListPreference
+        <com.android.settings.AnimationScalePreference
             android:key="transition_animation_scale"
             android:title="@string/transition_animation_scale_title"
             android:entries="@array/transition_animation_scale_entries"
             android:entryValues="@array/transition_animation_scale_values" />
 
-        <ListPreference
+        <com.android.settings.AnimationScalePreference
             android:key="animator_duration_scale"
             android:title="@string/animator_duration_scale_title"
             android:entries="@array/animator_duration_scale_entries"
diff --git a/src/com/android/settings/AnimationScalePreference.java b/src/com/android/settings/AnimationScalePreference.java
new file mode 100644
index 0000000..28d2c0c
--- /dev/null
+++ b/src/com/android/settings/AnimationScalePreference.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 The Pure Nexus 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;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import com.android.settingslib.CustomDialogPreferenceCompat;
+
+public class AnimationScalePreference extends CustomDialogPreferenceCompat
+    implements SeekBar.OnSeekBarChangeListener {
+
+    private TextView mScaleText;
+    private IntervalSeekBar mSeekBar;
+
+    private float mScale = 0.5f;
+
+    public AnimationScalePreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setPositiveButtonText(android.R.string.ok);
+        setNegativeButtonText(android.R.string.cancel);
+
+        setDialogLayoutResource(R.layout.preference_dialog_animation_scale);
+    }
+
+    @Override
+    protected void onBindDialogView(View view) {
+        super.onBindDialogView(view);
+
+        mScaleText = (TextView) view.findViewById(R.id.scale);
+        mScaleText.setText(String.valueOf(mScale) + "x");
+
+        mSeekBar = (IntervalSeekBar) view.findViewById(R.id.scale_seekbar);
+        mSeekBar.setProgressFloat(mScale);
+        mSeekBar.setOnSeekBarChangeListener(this);
+    }
+
+    public void setScale(float scale) {
+        mScale = scale;
+        setSummary(String.valueOf(scale) + "x");
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        if (positiveResult) {
+            callChangeListener(mSeekBar.getProgressFloat());
+        }
+    }
+
+    @Override
+    protected void onClick() {
+        // Ignore this until an explicit call to click()
+    }
+
+    public void click() {
+        super.onClick();
+    }
+
+    @Override
+    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+        mScaleText.setText(String.valueOf(mSeekBar.getProgressFloat()) + "x");
+    }
+
+    // Not used
+    @Override
+    public void onStartTrackingTouch(SeekBar seekBar) {
+    }
+
+    @Override
+    public void onStopTrackingTouch(SeekBar seekBar) {
+    }
+}
diff --git a/src/com/android/settings/IntervalSeekBar.java b/src/com/android/settings/IntervalSeekBar.java
new file mode 100644
index 0000000..0997c59
--- /dev/null
+++ b/src/com/android/settings/IntervalSeekBar.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 The Pure Nexus 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;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.SeekBar;
+
+/**
+ * Custom SeekBar that allows setting both a minimum and maximum value.
+ * This also handles floating point values (to 2 decimal places) through
+ * integer conversions.
+ */
+public class IntervalSeekBar extends SeekBar {
+    private float mMin;
+    private float mMax;
+    private float mDefault;
+    private float mMultiplier;
+
+    public IntervalSeekBar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray seekBarType = context.obtainStyledAttributes(attrs,
+                R.styleable.IntervalSeekBar, 0, 0);
+
+        mMax = seekBarType.getFloat(R.styleable.IntervalSeekBar_maxI, 1.5f);
+        mMin = seekBarType.getFloat(R.styleable.IntervalSeekBar_minI, 0.0f);
+        mDefault = seekBarType.getFloat(R.styleable.IntervalSeekBar_defaultValuePure, 1.0f);
+
+        int digits = seekBarType.getInt(R.styleable.IntervalSeekBar_digits, 0);
+        mMultiplier = (float) Math.pow(10, digits);
+
+        if (mMin > mMax) {
+            float temp = mMax;
+            mMax = mMin;
+            mMin = temp;
+        }
+
+        setMax(convertFloatToProgress(mMax));
+        setProgressFloat(mDefault);
+
+        seekBarType.recycle();
+    }
+
+    /*
+     * Converts from SeekBar units (which the SeekBar uses), to scale units
+     *  (which are saved).
+     *  This operation is the inverse of setFontScaling.
+     */
+    public float getProgressFloat() {
+        return (getProgress() / mMultiplier) + mMin;
+    }
+
+    /*
+     * Converts from scale units (which are saved), to SeekBar units
+     * (which the SeekBar uses). This also sets the SeekBar progress.
+     * This operation is the inverse of getProgressFloat.
+     */
+    public void setProgressFloat(float progress) {
+        setProgress(convertFloatToProgress(progress));
+    }
+
+    private int convertFloatToProgress(float value) {
+        return Math.round((value - mMin) * mMultiplier);
+    }
+
+    public float getMinimum() {
+        return mMin;
+    }
+
+    public float getMaximum() {
+        return mMax;
+    }
+
+    public float getDefault() {
+        return mDefault;
+    }
+
+    public void setMaximum(float max) {
+        mMax = max;
+        setMax(convertFloatToProgress(mMax));
+    }
+
+    public void setMinimum(float min) {
+        mMin = min;
+    }
+}
diff --git a/src/com/android/settings/development/AnimatorDurationScalePreferenceController.java b/src/com/android/settings/development/AnimatorDurationScalePreferenceController.java
index 114b121..3fbabf4 100644
--- a/src/com/android/settings/development/AnimatorDurationScalePreferenceController.java
+++ b/src/com/android/settings/development/AnimatorDurationScalePreferenceController.java
@@ -22,21 +22,22 @@
 import android.view.IWindowManager;
 
 import androidx.annotation.VisibleForTesting;
-import androidx.preference.ListPreference;
 import androidx.preference.Preference;
 
+import com.android.settings.AnimationScalePreference;
 import com.android.settings.core.PreferenceControllerMixin;
 import com.android.settingslib.development.DeveloperOptionsPreferenceController;
 
 public class AnimatorDurationScalePreferenceController extends DeveloperOptionsPreferenceController
-        implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin {
+        implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener,
+        PreferenceControllerMixin {
 
     private static final String ANIMATOR_DURATION_SCALE_KEY = "animator_duration_scale";
 
     @VisibleForTesting
     static final int ANIMATOR_DURATION_SCALE_SELECTOR = 2;
     @VisibleForTesting
-    static final float DEFAULT_VALUE = 1;
+    static final float DEFAULT_VALUE = 1.0f;
 
     private final IWindowManager mWindowManager;
     private final String[] mListValues;
@@ -88,19 +89,17 @@
     private void updateAnimationScaleValue() {
         try {
             final float scale = mWindowManager.getAnimationScale(ANIMATOR_DURATION_SCALE_SELECTOR);
-            int index = 0; // default
-            for (int i = 0; i < mListValues.length; i++) {
-                float val = Float.parseFloat(mListValues[i]);
-                if (scale <= val) {
-                    index = i;
-                    break;
-                }
-            }
-            final ListPreference listPreference = (ListPreference) mPreference;
-            listPreference.setValue(mListValues[index]);
-            listPreference.setSummary(mListSummaries[index]);
+            final AnimationScalePreference durationPreference = (AnimationScalePreference) mPreference;
+            durationPreference.setOnPreferenceClickListener(this);
+            durationPreference.setScale(scale);
         } catch (RemoteException e) {
             // intentional no-op
         }
     }
+
+    @Override
+    public boolean onPreferenceClick(Preference preference) {
+        ((AnimationScalePreference) preference).click();
+        return false;
+    }
 }
diff --git a/src/com/android/settings/development/TransitionAnimationScalePreferenceController.java b/src/com/android/settings/development/TransitionAnimationScalePreferenceController.java
index ac68364..56d57ef 100644
--- a/src/com/android/settings/development/TransitionAnimationScalePreferenceController.java
+++ b/src/com/android/settings/development/TransitionAnimationScalePreferenceController.java
@@ -22,22 +22,22 @@
 import android.view.IWindowManager;
 
 import androidx.annotation.VisibleForTesting;
-import androidx.preference.ListPreference;
 import androidx.preference.Preference;
 
+import com.android.settings.AnimationScalePreference;
 import com.android.settings.core.PreferenceControllerMixin;
 import com.android.settingslib.development.DeveloperOptionsPreferenceController;
 
 public class TransitionAnimationScalePreferenceController extends
         DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener,
-        PreferenceControllerMixin {
+        Preference.OnPreferenceClickListener, PreferenceControllerMixin {
 
     private static final String TRANSITION_ANIMATION_SCALE_KEY = "transition_animation_scale";
 
     @VisibleForTesting
     static final int TRANSITION_ANIMATION_SCALE_SELECTOR = 1;
     @VisibleForTesting
-    static final float DEFAULT_VALUE = 1;
+    static final float DEFAULT_VALUE = 1.0f;
 
     private final IWindowManager mWindowManager;
     private final String[] mListValues;
@@ -90,19 +90,17 @@
         try {
             final float scale = mWindowManager.getAnimationScale(
                     TRANSITION_ANIMATION_SCALE_SELECTOR);
-            int index = 0; // default
-            for (int i = 0; i < mListValues.length; i++) {
-                float val = Float.parseFloat(mListValues[i]);
-                if (scale <= val) {
-                    index = i;
-                    break;
-                }
-            }
-            final ListPreference listPreference = (ListPreference) mPreference;
-            listPreference.setValue(mListValues[index]);
-            listPreference.setSummary(mListSummaries[index]);
+            final AnimationScalePreference transitionPreference = (AnimationScalePreference) mPreference;
+            transitionPreference.setOnPreferenceClickListener(this);
+            transitionPreference.setScale(scale);
         } catch (RemoteException e) {
             // intentional no-op
         }
     }
+
+    @Override
+    public boolean onPreferenceClick(Preference preference) {
+        ((AnimationScalePreference) preference).click();
+        return false;
+    }
 }
diff --git a/src/com/android/settings/development/WindowAnimationScalePreferenceController.java b/src/com/android/settings/development/WindowAnimationScalePreferenceController.java
index d8575a0..207c5cf 100644
--- a/src/com/android/settings/development/WindowAnimationScalePreferenceController.java
+++ b/src/com/android/settings/development/WindowAnimationScalePreferenceController.java
@@ -22,22 +22,22 @@
 import android.view.IWindowManager;
 
 import androidx.annotation.VisibleForTesting;
-import androidx.preference.ListPreference;
 import androidx.preference.Preference;
 
+import com.android.settings.AnimationScalePreference;
 import com.android.settings.core.PreferenceControllerMixin;
 import com.android.settingslib.development.DeveloperOptionsPreferenceController;
 
 public class WindowAnimationScalePreferenceController extends
         DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener,
-        PreferenceControllerMixin {
+        Preference.OnPreferenceClickListener, PreferenceControllerMixin {
 
     private static final String WINDOW_ANIMATION_SCALE_KEY = "window_animation_scale";
 
     @VisibleForTesting
     static final int WINDOW_ANIMATION_SCALE_SELECTOR = 0;
     @VisibleForTesting
-    static final float DEFAULT_VALUE = 1;
+    static final float DEFAULT_VALUE = 1.0f;
 
     private final IWindowManager mWindowManager;
     private final String[] mListValues;
@@ -89,19 +89,18 @@
     private void updateAnimationScaleValue() {
         try {
             final float scale = mWindowManager.getAnimationScale(WINDOW_ANIMATION_SCALE_SELECTOR);
-            int index = 0; // default
-            for (int i = 0; i < mListValues.length; i++) {
-                float val = Float.parseFloat(mListValues[i]);
-                if (scale <= val) {
-                    index = i;
-                    break;
-                }
-            }
-            final ListPreference listPreference = (ListPreference) mPreference;
-            listPreference.setValue(mListValues[index]);
-            listPreference.setSummary(mListSummaries[index]);
+            final AnimationScalePreference windowPreference = (AnimationScalePreference) mPreference;
+            windowPreference.setOnPreferenceClickListener(this);
+            windowPreference.setScale(scale);
+
         } catch (RemoteException e) {
             // intentional no-op
         }
     }
+
+    @Override
+    public boolean onPreferenceClick(Preference preference) {
+        ((AnimationScalePreference) preference).click();
+        return false;
+    }
 }