OmniControl: add qs settings grid item

Change-Id: I540eb2359c83dc3bcd789b982802624a79b48976
diff --git a/.idea/misc.xml b/.idea/misc.xml
index b7a738d..753add3 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -28,6 +28,8 @@
         <entry key="app/src/main/res/drawable/ic_launcher_background.xml" value="0.3067708333333333" />
         <entry key="app/src/main/res/drawable/ic_launcher_foreground.xml" value="0.2869791666666667" />
         <entry key="app/src/main/res/drawable/ic_lockscreen_tile.xml" value="0.5130208333333334" />
+        <entry key="app/src/main/res/drawable/ic_qs_tile.xml" value="0.4231111111111111" />
+        <entry key="app/src/main/res/drawable/ic_quickbar.xml" value="0.4231111111111111" />
         <entry key="app/src/main/res/drawable/ic_remote.xml" value="0.4436936936936937" />
         <entry key="app/src/main/res/drawable/ic_settings_buttons.xml" value="0.3078125" />
         <entry key="app/src/main/res/drawable/ic_settings_leds.xml" value="4.979166666666667E15" />
diff --git a/app/src/main/java/org/omnirom/control/GridViewFragment.kt b/app/src/main/java/org/omnirom/control/GridViewFragment.kt
index cb9777f..0054a7d 100644
--- a/app/src/main/java/org/omnirom/control/GridViewFragment.kt
+++ b/app/src/main/java/org/omnirom/control/GridViewFragment.kt
@@ -126,6 +126,14 @@
                 OverlaysFragment()
             )
         )
+        gridItems.add(
+            FragmentGridItem(
+                R.string.qs_settings_title,
+                R.string.qs_settings_summary,
+                R.drawable.ic_qs_tile,
+                QSSettingsFragment()
+            )
+        )
         if (Utils.isAvailableApp(requireContext(), "org.omnirom.omnistyle")) {
             val intent = Intent()
             intent.component = ComponentName(
diff --git a/app/src/main/java/org/omnirom/control/QSSettingsFragment.kt b/app/src/main/java/org/omnirom/control/QSSettingsFragment.kt
new file mode 100644
index 0000000..bb4ce2e
--- /dev/null
+++ b/app/src/main/java/org/omnirom/control/QSSettingsFragment.kt
@@ -0,0 +1,103 @@
+/*
+ *  Copyright (C) 2021 The OmniROM Project
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.omnirom.control
+
+import android.os.Bundle
+import android.provider.Settings
+import androidx.preference.Preference
+import org.omnirom.omnilib.preference.SeekBarPreference
+
+
+class QSSettingsFragment : AbstractSettingsFragment() {
+
+    override fun getFragmentTitle(): String {
+        return resources.getString(R.string.qs_settings_title)
+    }
+
+    override fun getFragmentSummary(): String {
+        return resources.getString(R.string.qs_settings_summary)
+    }
+
+    override fun getFragmentIcon(): Int {
+        return R.drawable.ic_qs_tile
+    }
+
+    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+        setPreferencesFromResource(R.xml.qs_settings_preferences, rootKey)
+
+        val pm = requireContext().packageManager
+        val resources = pm.getResourcesForApplication("com.android.systemui")
+
+        var defPortraitColumns = 2
+        val portraitColumns = findPreference<SeekBarPreference>("qs_layout_columns")
+        if (portraitColumns != null) {
+            var id =
+                resources.getIdentifier(
+                    "quick_settings_num_columns",
+                    "integer",
+                    "com.android.systemui"
+                )
+            if (id != 0) {
+                defPortraitColumns = resources.getInteger(id)
+            }
+            val settingsPortraitColumns = Settings.System.getInt(
+                requireContext().contentResolver, Settings.System.OMNI_QS_LAYOUT_COLUMNS,
+                defPortraitColumns
+            )
+            portraitColumns.setValue(settingsPortraitColumns)
+            portraitColumns.onPreferenceChangeListener =
+                Preference.OnPreferenceChangeListener { _, newValue ->
+                    Settings.System.putInt(
+                        requireContext().contentResolver, Settings.System.OMNI_QS_LAYOUT_COLUMNS,
+                        newValue as Int
+                    )
+                    true
+                }
+        }
+        val landscapeColumns = findPreference<SeekBarPreference>("qs_layout_columns_landscape")
+        if (landscapeColumns != null) {
+            var defLandspaceColumns = defPortraitColumns
+            var id =
+                resources.getIdentifier(
+                    "quick_settings_num_columns_landscape",
+                    "integer",
+                    "com.android.systemui"
+                )
+            if (id != 0) {
+                defLandspaceColumns = resources.getInteger(id)
+            }
+            val settingsLandscapeColumns = Settings.System.getInt(
+                requireContext().contentResolver, Settings.System.OMNI_QS_LAYOUT_COLUMNS_LANDSCAPE,
+                defLandspaceColumns
+            )
+            landscapeColumns.setValue(settingsLandscapeColumns)
+            landscapeColumns.onPreferenceChangeListener =
+                Preference.OnPreferenceChangeListener { _, newValue ->
+                    Settings.System.putInt(
+                        requireContext().contentResolver, Settings.System.OMNI_QS_LAYOUT_COLUMNS_LANDSCAPE,
+                        newValue as Int
+                    )
+                    true
+                }
+        }
+    }
+
+    override fun onPreferenceTreeClick(preference: Preference?): Boolean {
+        return super.onPreferenceTreeClick(preference)
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/omnirom/omnilib/preference/SeekBarPreference.java b/app/src/main/java/org/omnirom/omnilib/preference/SeekBarPreference.java
new file mode 100644
index 0000000..49b62ca
--- /dev/null
+++ b/app/src/main/java/org/omnirom/omnilib/preference/SeekBarPreference.java
@@ -0,0 +1,221 @@
+/*
+ ** Copyright 2013, The ChameleonOS Open Source Project
+ ** Copyright 2016, The OmniROM 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 org.omnirom.omnilib.preference;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.TypedValue;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+
+import org.omnirom.omnilib.R;
+
+public class SeekBarPreference extends Preference implements OnSeekBarChangeListener {
+
+    private final String TAG = getClass().getName();
+
+    private static final String ANDROIDNS = "http://schemas.android.com/apk/res/android";
+    private static final int DEFAULT_VALUE = 50;
+
+    private int mMaxValue = 100;
+    private int mMinValue = 0;
+    private int mInterval = 1;
+    private int mCurrentValue;
+    private String mUnitsLeft = "";
+    private String mUnitsRight = "";
+    private SeekBar mSeekBar;
+    private TextView mStatusText;
+
+    public SeekBarPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initPreference(context, attrs);
+    }
+
+    public SeekBarPreference(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        initPreference(context, attrs);
+    }
+
+    private void initPreference(Context context, AttributeSet attrs) {
+        setValuesFromXml(context, attrs);
+        setLayoutResource(R.layout.preference_seek_bar);
+    }
+
+    private void setValuesFromXml(Context context, AttributeSet attrs) {
+        mMaxValue = attrs.getAttributeIntValue(ANDROIDNS, "max", 100);
+
+        final TypedArray attributes = context.obtainStyledAttributes(attrs,
+                R.styleable.SeekBarPreference);
+
+        TypedValue minAttr =
+                attributes.peekValue(R.styleable.SeekBarPreference_min);
+        if (minAttr != null && minAttr.type == TypedValue.TYPE_INT_DEC) {
+            mMinValue = minAttr.data;
+        }
+
+        TypedValue unitsLeftAttr =
+                attributes.peekValue(R.styleable.SeekBarPreference_unitsLeft);
+        CharSequence data = null;
+        if (unitsLeftAttr != null && unitsLeftAttr.type == TypedValue.TYPE_STRING) {
+            if (unitsLeftAttr.resourceId != 0) {
+                data = context.getText(unitsLeftAttr.resourceId);
+            } else {
+                data = unitsLeftAttr.string;
+            }
+        }
+        mUnitsLeft = (data == null) ? "" : data.toString();
+
+        TypedValue unitsRightAttr =
+                attributes.peekValue(R.styleable.SeekBarPreference_unitsRight);
+        data = null;
+        if (unitsRightAttr != null && unitsRightAttr.type == TypedValue.TYPE_STRING) {
+            if (unitsRightAttr.resourceId != 0) {
+                data = context.getText(unitsRightAttr.resourceId);
+            } else {
+                data = unitsRightAttr.string;
+            }
+        }
+        mUnitsRight = (data == null) ? "" : data.toString();
+
+        TypedValue intervalAttr =
+                attributes.peekValue(R.styleable.SeekBarPreference_interval);
+        if (intervalAttr != null && intervalAttr.type == TypedValue.TYPE_INT_DEC) {
+            mInterval = intervalAttr.data;
+        }
+
+        attributes.recycle();
+    }
+
+    @Override
+    public void onDependencyChanged(Preference dependency, boolean disableDependent) {
+        super.onDependencyChanged(dependency, disableDependent);
+        this.setShouldDisableView(true);
+        if (mSeekBar != null) {
+            mSeekBar.setEnabled(!disableDependent);
+        }
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+
+        mSeekBar = (SeekBar) holder.findViewById(R.id.seekbar);
+        mSeekBar.setMax(mMaxValue - mMinValue);
+        mSeekBar.setProgress(mCurrentValue - mMinValue);
+        mSeekBar.setOnSeekBarChangeListener(this);
+        mSeekBar.setEnabled(isEnabled());
+
+        mStatusText = (TextView) holder.findViewById(R.id.seekBarPrefValue);
+        mStatusText.setText(String.valueOf(mCurrentValue));
+        mStatusText.setMinimumWidth(30);
+
+        TextView unitsRight = (TextView) holder.findViewById(R.id.seekBarPrefUnitsRight);
+        unitsRight.setText(mUnitsRight);
+        TextView unitsLeft = (TextView) holder.findViewById(R.id.seekBarPrefUnitsLeft);
+        unitsLeft.setText(mUnitsLeft);
+    }
+
+    @Override
+    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+        int newValue = progress + mMinValue;
+
+        if (newValue > mMaxValue) {
+            newValue = mMaxValue;
+        } else if (newValue < mMinValue) {
+            newValue = mMinValue;
+        } else if (mInterval != 1 && newValue % mInterval != 0) {
+            newValue = Math.round(((float) newValue) / mInterval) * mInterval;
+        }
+
+        // change rejected, revert to the previous value
+        if (!callChangeListener(newValue)) {
+            seekBar.setProgress(mCurrentValue - mMinValue);
+            return;
+        }
+
+        // change accepted, store it
+        mCurrentValue = newValue;
+        mStatusText.setText(String.valueOf(newValue));
+        persistInt(newValue);
+    }
+
+    @Override
+    public void onStartTrackingTouch(SeekBar seekBar) {
+    }
+
+    @Override
+    public void onStopTrackingTouch(SeekBar seekBar) {
+        notifyChanged();
+    }
+
+    @Override
+    protected Object onGetDefaultValue(TypedArray ta, int index) {
+        int defaultValue = ta.getInt(index, DEFAULT_VALUE);
+        return defaultValue;
+    }
+
+    @Override
+    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+        if (restoreValue) {
+            mCurrentValue = getPersistedInt(mCurrentValue);
+        } else {
+            int temp = 0;
+            try {
+                temp = (Integer) defaultValue;
+            } catch (Exception ex) {
+                Log.e(TAG, "Invalid default value: " + defaultValue.toString());
+            }
+            persistInt(temp);
+            mCurrentValue = temp;
+        }
+    }
+
+    public void setValue(int value) {
+        mCurrentValue = value;
+    }
+
+    public void setMaxValue(int value) {
+        mMaxValue = value;
+        if (mSeekBar != null) {
+            mSeekBar.setMax(mMaxValue - mMinValue);
+        }
+    }
+
+    public void setMinValue(int value) {
+        mMinValue = value;
+        if (mSeekBar != null) {
+            mSeekBar.setMax(mMaxValue - mMinValue);
+        }
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        if (mSeekBar != null) {
+            mSeekBar.setEnabled(enabled);
+        }
+        super.setEnabled(enabled);
+    }
+}
diff --git a/app/src/main/res/drawable/ic_qs_tile.xml b/app/src/main/res/drawable/ic_qs_tile.xml
new file mode 100644
index 0000000..657260a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_qs_tile.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2018 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?android:textColorPrimary"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M4,5v13h17L21,5L4,5zM14,7v3.5h-3L11,7h3zM6,7h3v3.5L6,10.5L6,7zM6,16v-3.5h3L9,16L6,16zM11,16v-3.5h3L14,16h-3zM19,16h-3v-3.5h3L19,16zM16,10.5L16,7h3v3.5h-3z" />
+</vector>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a1a6494..161a88e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -102,4 +102,19 @@
 
     <string name="status_bar_bt_battery_title">Show Bluetooth battery level</string>
     <string name="status_bar_bt_battery_summary">For supported devices show battery level also in status bar</string>
+
+    <string name="qs_settings_title">Quick settings</string>
+    <string name="qs_settings_summary">Layout options</string>
+    <string name="qs_layout_title">Layout</string>
+    <string name="qs_contents_title">Contents</string>
+    <string name="qs_tile_vertical_layout_title">Vertical layout</string>
+    <string name="qs_tile_vertical_layout_summary">Align icon and text vertical</string>
+    <string name="qs_show_brightness_title">Brightness slider</string>
+    <string name="qs_show_brightness_summary">Show brightness slider in expanded quick settings</string>
+    <string name="qs_layout_columns_title">Portrait columns</string>
+    <string name="qs_layout_columns_summary"></string>
+    <string name="qs_layout_columns_landscape_title">Landscape columns</string>
+    <string name="qs_layout_columns_landscape_summary"></string>
+    <string name="qs_tile_label_hide_title">Hide labels</string>
+    <string name="qs_tile_label_hide_summary">Do not show text</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/xml/qs_settings_preferences.xml b/app/src/main/res/xml/qs_settings_preferences.xml
new file mode 100644
index 0000000..ac049e6
--- /dev/null
+++ b/app/src/main/res/xml/qs_settings_preferences.xml
@@ -0,0 +1,51 @@
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:key="bars_settings">
+    <PreferenceCategory
+        android:key="qs_layout_category"
+        android:title="@string/qs_layout_title">
+
+        <org.omnirom.omnilib.preference.SystemSettingSwitchPreference
+                android:key="qs_tile_vertical_layout"
+                android:title="@string/qs_tile_vertical_layout_title"
+                android:summary="@string/qs_tile_vertical_layout_summary"
+                android:defaultValue="false" />
+
+        <org.omnirom.omnilib.preference.SystemSettingSwitchPreference
+            android:key="qs_tile_label_hide"
+            android:title="@string/qs_tile_label_hide_title"
+            android:summary="@string/qs_tile_label_hide_summary"
+            android:defaultValue="false" />
+
+        <org.omnirom.omnilib.preference.SeekBarPreference
+            android:key="qs_layout_columns"
+            android:title="@string/qs_layout_columns_title"
+            android:summary="@string/qs_layout_columns_summary"
+            android:max="10"
+            app:min="2"
+            android:persistent="false" />
+
+        <org.omnirom.omnilib.preference.SeekBarPreference
+            android:key="qs_layout_columns_landscape"
+            android:title="@string/qs_layout_columns_landscape_title"
+            android:summary="@string/qs_layout_columns_landscape_summary"
+            android:max="10"
+            app:min="2"
+            android:persistent="false" />
+
+    </PreferenceCategory>
+
+    <PreferenceCategory
+        android:key="qs_contents_category"
+        android:title="@string/qs_contents_title">
+
+        <org.omnirom.omnilib.preference.SecureSettingSwitchPreference
+            android:key="qs_show_brightness"
+            android:title="@string/qs_show_brightness_title"
+            android:summary="@string/qs_show_brightness_summary"
+            android:defaultValue="true"/>
+
+    </PreferenceCategory>
+</PreferenceScreen>
+
+