Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f15a9660 authored by Sunny Goyal's avatar Sunny Goyal Committed by Android (Google) Code Review
Browse files

Merge "Moving developer settings to xml overrides instead of code swap" into main

parents d955d230 2eeee631
Loading
Loading
Loading
Loading
+212 −54
Original line number Diff line number Diff line
@@ -16,32 +16,98 @@

package com.android.launcher3.uioverrides.flags

import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Handler
import android.provider.DeviceConfig
import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
import android.text.Html
import android.util.AttributeSet
import android.view.inputmethod.EditorInfo
import android.widget.TextView
import android.widget.Toast
import androidx.core.widget.doAfterTextChanged
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceGroup
import androidx.preference.PreferenceViewHolder
import androidx.preference.SwitchPreference
import com.android.launcher3.ExtendedEditText
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.R
import com.android.launcher3.secondarydisplay.SecondaryDisplayLauncher
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapperImpl
import com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT
import com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_COUNT
import com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_SEEN
import com.android.launcher3.util.OnboardingPrefs.HOTSEAT_DISCOVERY_TIP_COUNT
import com.android.launcher3.util.OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN
import com.android.launcher3.util.OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP
import com.android.launcher3.util.PluginManagerWrapper
import com.android.quickstep.util.DeviceConfigHelper
import com.android.quickstep.util.DeviceConfigHelper.Companion.NAMESPACE_LAUNCHER
import com.android.quickstep.util.DeviceConfigHelper.DebugInfo
import com.android.systemui.shared.plugins.PluginEnabler
import com.android.systemui.shared.plugins.PluginPrefs
import java.util.Locale

/** Helper class to generate UI for Device Config */
class DevOptionsUiHelper {
class DevOptionsUiHelper(c: Context, attr: AttributeSet?) : PreferenceGroup(c, attr) {

    init {
        layoutResource = R.layout.developer_options_top_bar
        isPersistent = false
    }

    override fun onBindViewHolder(holder: PreferenceViewHolder) {
        super.onBindViewHolder(holder)

        // Initialize search
        (holder.findViewById(R.id.filter_box) as TextView?)?.doAfterTextChanged {
            val query: String = it.toString().lowercase(Locale.getDefault()).replace("_", " ")
            filterPreferences(query, this)
        }
    }

    private fun filterPreferences(query: String, pg: PreferenceGroup) {
        val count = pg.preferenceCount
        var visible = false
        for (i in 0 until count) {
            val preference = pg.getPreference(i)
            if (preference is PreferenceGroup) {
                filterPreferences(query, preference)
            } else {
                val title =
                    preference.title.toString().lowercase(Locale.getDefault()).replace("_", " ")
                preference.isVisible = query.isEmpty() || title.contains(query)
            }
            visible = visible or preference.isVisible
        }
        pg.isVisible = visible
    }

    override fun onAttached() {
        super.onAttached()

        removeAll()
        inflateServerFlags(newCategory("Server flags", "Long press to reset"))
        if (PluginPrefs.hasPlugins(context)) {
            inflatePluginPrefs(newCategory("Plugins"))
        }
        addIntentTargets()
        addOnboardingPrefsCategory()
    }

    private fun newCategory(titleText: String, subTitleText: String? = null) =
        PreferenceCategory(context).apply {
            title = titleText
            summary = subTitleText
            this@DevOptionsUiHelper.addPreference(this)
        }

    /** Inflates preferences for all server flags in the provider PreferenceGroup */
    fun inflateServerFlags(parent: PreferenceGroup) {
    private fun inflateServerFlags(parent: PreferenceGroup) {
        val prefs = DeviceConfigHelper.prefs
        // Sort the keys in the order of modified first followed by natural order
        val allProps =
@@ -59,25 +125,21 @@ class DevOptionsUiHelper {
            if (it.isInt) return@forEach
            val info = it as DebugInfo<Boolean>

            val preference =
                object : SwitchPreference(parent.context) {
                    override fun onBindViewHolder(holder: PreferenceViewHolder) {
                        super.onBindViewHolder(holder)
            val preference = CustomSwitchPref { holder, pref ->
                holder.itemView.setOnLongClickListener {
                            prefs.edit().remove(key).apply()
                            setChecked(info.getBoolValue())
                    prefs.edit().remove(pref.key).apply()
                    pref.setChecked(info.getBoolValue())
                    summary = info.getSummary()
                    true
                }
            }
                }
            preference.key = info.key
            preference.isPersistent = false
            preference.title = info.key
            preference.summary = info.getSummary()
            preference.setChecked(prefs.getBoolean(info.key, info.getBoolValue()))
            preference.setOnPreferenceChangeListener { _, newVal ->
                DeviceConfigHelper.prefs.edit().putBoolean(info.key, newVal as Boolean).apply()
                prefs.edit().putBoolean(info.key, newVal as Boolean).apply()
                preference.summary = info.getSummary()
                true
            }
@@ -89,19 +151,13 @@ class DevOptionsUiHelper {
            if (!it.isInt) return@forEach
            val info = it as DebugInfo<Int>

            val preference =
                object : Preference(parent.context) {
                    override fun onBindViewHolder(holder: PreferenceViewHolder) {
                        super.onBindViewHolder(holder)
            val preference = CustomPref { holder, pref ->
                val textView = holder.findViewById(R.id.pref_edit_text) as ExtendedEditText
                textView.setText(info.getIntValueAsString())
                textView.setOnEditorActionListener { _, actionId, _ ->
                    if (actionId == EditorInfo.IME_ACTION_DONE) {
                                DeviceConfigHelper.prefs
                                    .edit()
                                    .putInt(key, textView.text.toString().toInt())
                                    .apply()
                                Handler().post { summary = info.getSummary() }
                        prefs.edit().putInt(pref.key, textView.text.toString().toInt()).apply()
                        pref.summary = info.getSummary()
                        true
                    }
                    false
@@ -112,13 +168,12 @@ class DevOptionsUiHelper {
                }

                holder.itemView.setOnLongClickListener {
                            prefs.edit().remove(key).apply()
                    prefs.edit().remove(pref.key).apply()
                    textView.setText(info.getIntValueAsString())
                            summary = info.getSummary()
                    pref.summary = info.getSummary()
                    true
                }
            }
                }
            preference.key = info.key
            preference.isPersistent = false
            preference.title = info.key
@@ -158,8 +213,7 @@ class DevOptionsUiHelper {
     * corresponding plugins on the device. When a plugin-group is enabled/disabled we also need to
     * notify the pluginManager manually since the broadcast-mechanism only works in sysui process
     */
    fun inflatePluginPrefs(parent: PreferenceGroup) {
        val context = parent.context
    private fun inflatePluginPrefs(parent: PreferenceGroup) {
        val manager = PluginManagerWrapper.INSTANCE[context] as PluginManagerWrapperImpl
        val pm = context.packageManager

@@ -185,9 +239,7 @@ class DevOptionsUiHelper {
                val pluginInfo = infoList[0]!!
                val pluginUri = Uri.fromParts("package", pluginInfo.serviceInfo.packageName, null)

                object : SwitchPreference(context) {
                        override fun onBindViewHolder(holder: PreferenceViewHolder) {
                            super.onBindViewHolder(holder)
                CustomSwitchPref { holder, _ ->
                        holder.itemView.setOnLongClickListener {
                            context.startActivity(
                                Intent(ACTION_APPLICATION_DETAILS_SETTINGS, pluginUri)
@@ -195,7 +247,6 @@ class DevOptionsUiHelper {
                            true
                        }
                    }
                    }
                    .apply {
                        isPersistent = true
                        title = pluginInfo.loadLabel(pm)
@@ -232,6 +283,113 @@ class DevOptionsUiHelper {
            }
    }

    private fun addIntentTargets() {
        val launchSandboxIntent =
            Intent("com.android.quickstep.action.GESTURE_SANDBOX")
                .setPackage(context.packageName)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        newCategory("Gesture Navigation Sandbox").apply {
            addPreference(
                Preference(context).apply {
                    title = "Launch Gesture Tutorial Steps menu"
                    intent = Intent(launchSandboxIntent).putExtra("use_tutorial_menu", true)
                }
            )
            addPreference(
                Preference(context).apply {
                    title = "Launch Back Tutorial"
                    intent =
                        Intent(launchSandboxIntent)
                            .putExtra("use_tutorial_menu", false)
                            .putExtra("tutorial_steps", arrayOf("BACK_NAVIGATION"))
                }
            )
            addPreference(
                Preference(context).apply {
                    title = "Launch Home Tutorial"
                    intent =
                        Intent(launchSandboxIntent)
                            .putExtra("use_tutorial_menu", false)
                            .putExtra("tutorial_steps", arrayOf("HOME_NAVIGATION"))
                }
            )
            addPreference(
                Preference(context).apply {
                    title = "Launch Overview Tutorial"
                    intent =
                        Intent(launchSandboxIntent)
                            .putExtra("use_tutorial_menu", false)
                            .putExtra("tutorial_steps", arrayOf("OVERVIEW_NAVIGATION"))
                }
            )
        }

        newCategory("Other activity targets").apply {
            addPreference(
                Preference(context).apply {
                    title = "Launch Secondary Display"
                    intent =
                        Intent(context, SecondaryDisplayLauncher::class.java)
                            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                }
            )
        }
    }

    private fun addOnboardingPrefsCategory() {
        newCategory("Onboarding Flows").apply {
            summary = "Reset these if you want to see the education again."
            addOnboardPref(
                "All Apps Bounce",
                HOME_BOUNCE_SEEN.sharedPrefKey,
                HOME_BOUNCE_COUNT.sharedPrefKey
            )
            addOnboardPref(
                "Hybrid Hotseat Education",
                HOTSEAT_DISCOVERY_TIP_COUNT.sharedPrefKey,
                HOTSEAT_LONGPRESS_TIP_SEEN.sharedPrefKey
            )
            addOnboardPref("Taskbar Education", TASKBAR_EDU_TOOLTIP_STEP.sharedPrefKey)
            addOnboardPref("All Apps Visited Count", ALL_APPS_VISITED_COUNT.sharedPrefKey)
        }
    }

    private fun PreferenceCategory.addOnboardPref(title: String, vararg keys: String) =
        this.addPreference(
            Preference(context).also {
                it.title = title
                it.summary = "Tap to reset"
                setOnPreferenceClickListener { _ ->
                    LauncherPrefs.getPrefs(context)
                        .edit()
                        .apply { keys.forEach { key -> remove(key) } }
                        .apply()
                    Toast.makeText(context, "Reset $title", Toast.LENGTH_SHORT).show()
                    true
                }
            }
        )

    private inner class CustomSwitchPref(
        private val bindCallback: (holder: PreferenceViewHolder, pref: SwitchPreference) -> Unit
    ) : SwitchPreference(context) {

        override fun onBindViewHolder(holder: PreferenceViewHolder) {
            super.onBindViewHolder(holder)
            bindCallback.invoke(holder, this)
        }
    }

    private inner class CustomPref(
        private val bindCallback: (holder: PreferenceViewHolder, pref: Preference) -> Unit
    ) : Preference(context) {

        override fun onBindViewHolder(holder: PreferenceViewHolder) {
            super.onBindViewHolder(holder)
            bindCallback.invoke(holder, this)
        }
    }

    companion object {
        const val TAG = "DeviceConfigUIHelper"

+0 −240
Original line number Diff line number Diff line
/*
 * 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.
 */
package com.android.launcher3.uioverrides.flags;

import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_HIGHLIGHT_KEY;
import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
import static com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_COUNT;
import static com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_SEEN;
import static com.android.launcher3.util.OnboardingPrefs.HOTSEAT_DISCOVERY_TIP_COUNT;
import static com.android.launcher3.util.OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN;
import static com.android.launcher3.util.OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.Toast;

import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;

import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.secondarydisplay.SecondaryDisplayLauncher;
import com.android.systemui.shared.plugins.PluginPrefs;

/**
 * Dev-build only UI allowing developers to toggle flag settings and plugins.
 * See {@link FeatureFlags}.
 */
public class DeveloperOptionsUI {

    private final PreferenceFragmentCompat mFragment;
    private final PreferenceScreen mPreferenceScreen;

    public DeveloperOptionsUI(PreferenceFragmentCompat fragment, PreferenceCategory flags) {
        mFragment = fragment;
        mPreferenceScreen = fragment.getPreferenceScreen();
        flags.getParent().removePreference(flags);

        // Add search bar
        View listView = mFragment.getListView();
        ViewGroup parent = (ViewGroup) listView.getParent();
        View topBar = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.developer_options_top_bar, parent, false);
        parent.addView(topBar, parent.indexOfChild(listView));
        initSearch(topBar.findViewById(R.id.filter_box));

        DevOptionsUiHelper uiHelper = new DevOptionsUiHelper();
        uiHelper.inflateServerFlags(newCategory("Server flags"));
        if (PluginPrefs.hasPlugins(getContext())) {
            uiHelper.inflatePluginPrefs(newCategory("Plugins"));
        }

        maybeAddSandboxCategory();
        addOnboardingPrefsCatergory();
    }

    private void filterPreferences(String query, PreferenceGroup pg) {
        int count = pg.getPreferenceCount();
        int hidden = 0;
        for (int i = 0; i < count; i++) {
            Preference preference = pg.getPreference(i);
            if (preference instanceof PreferenceGroup) {
                filterPreferences(query, (PreferenceGroup) preference);
            } else {
                String title = preference.getTitle().toString().toLowerCase().replace("_", " ");
                if (query.isEmpty() || title.contains(query)) {
                    preference.setVisible(true);
                } else {
                    preference.setVisible(false);
                    hidden++;
                }
            }
        }
        pg.setVisible(hidden != count);
    }

    private void initSearch(EditText filterBox) {
        filterBox.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { }

            @Override
            public void afterTextChanged(Editable editable) {
                String query = editable.toString().toLowerCase().replace("_", " ");
                filterPreferences(query, mPreferenceScreen);
            }
        });

        if (mFragment.getArguments() != null) {
            String filter = mFragment.getArguments().getString(EXTRA_FRAGMENT_HIGHLIGHT_KEY);
            // Normally EXTRA_FRAGMENT_ARG_KEY is used to highlight the preference with the given
            // key. This is a slight variation where we instead filter by the human-readable titles.
            if (filter != null) {
                filterBox.setText(filter);
            }
        }
    }

    private PreferenceCategory newCategory(String title) {
        PreferenceCategory category = new PreferenceCategory(getContext());
        category.setOrder(Preference.DEFAULT_ORDER);
        category.setTitle(title);
        mPreferenceScreen.addPreference(category);
        return category;
    }

    private Context getContext() {
        return mFragment.requireContext();
    }

    private void maybeAddSandboxCategory() {
        Context context = getContext();
        if (context == null) {
            return;
        }
        Intent launchSandboxIntent =
                new Intent("com.android.quickstep.action.GESTURE_SANDBOX")
                        .setPackage(context.getPackageName())
                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if (launchSandboxIntent.resolveActivity(context.getPackageManager()) == null) {
            return;
        }
        PreferenceCategory sandboxCategory = newCategory("Gesture Navigation Sandbox");
        sandboxCategory.setSummary("Learn and practice navigation gestures");
        Preference launchTutorialStepMenuPreference = new Preference(context);
        launchTutorialStepMenuPreference.setKey("launchTutorialStepMenu");
        launchTutorialStepMenuPreference.setTitle("Launch Gesture Tutorial Steps menu");
        launchTutorialStepMenuPreference.setSummary("Select a gesture tutorial step.");
        launchTutorialStepMenuPreference.setIntent(
                new Intent(launchSandboxIntent).putExtra("use_tutorial_menu", true));

        sandboxCategory.addPreference(launchTutorialStepMenuPreference);
        Preference launchOnboardingTutorialPreference = new Preference(context);
        launchOnboardingTutorialPreference.setKey("launchOnboardingTutorial");
        launchOnboardingTutorialPreference.setTitle("Launch Onboarding Tutorial");
        launchOnboardingTutorialPreference.setSummary("Learn the basic navigation gestures.");
        launchTutorialStepMenuPreference.setIntent(new Intent(launchSandboxIntent)
                .putExtra("use_tutorial_menu", false)
                .putExtra("tutorial_steps",
                        new String[] {
                                "HOME_NAVIGATION",
                                "BACK_NAVIGATION",
                                "OVERVIEW_NAVIGATION"}));

        sandboxCategory.addPreference(launchOnboardingTutorialPreference);
        Preference launchBackTutorialPreference = new Preference(context);
        launchBackTutorialPreference.setKey("launchBackTutorial");
        launchBackTutorialPreference.setTitle("Launch Back Tutorial");
        launchBackTutorialPreference.setSummary("Learn how to use the Back gesture");
        launchBackTutorialPreference.setIntent(new Intent(launchSandboxIntent)
                    .putExtra("use_tutorial_menu", false)
                    .putExtra("tutorial_steps", new String[] {"BACK_NAVIGATION"}));

        sandboxCategory.addPreference(launchBackTutorialPreference);
        Preference launchHomeTutorialPreference = new Preference(context);
        launchHomeTutorialPreference.setKey("launchHomeTutorial");
        launchHomeTutorialPreference.setTitle("Launch Home Tutorial");
        launchHomeTutorialPreference.setSummary("Learn how to use the Home gesture");
        launchHomeTutorialPreference.setIntent(new Intent(launchSandboxIntent)
                    .putExtra("use_tutorial_menu", false)
                    .putExtra("tutorial_steps", new String[] {"HOME_NAVIGATION"}));

        sandboxCategory.addPreference(launchHomeTutorialPreference);
        Preference launchOverviewTutorialPreference = new Preference(context);
        launchOverviewTutorialPreference.setKey("launchOverviewTutorial");
        launchOverviewTutorialPreference.setTitle("Launch Overview Tutorial");
        launchOverviewTutorialPreference.setSummary("Learn how to use the Overview gesture");
        launchOverviewTutorialPreference.setIntent(new Intent(launchSandboxIntent)
                    .putExtra("use_tutorial_menu", false)
                    .putExtra("tutorial_steps", new String[] {"OVERVIEW_NAVIGATION"}));

        sandboxCategory.addPreference(launchOverviewTutorialPreference);
        Preference launchSecondaryDisplayPreference = new Preference(context);
        launchSecondaryDisplayPreference.setKey("launchSecondaryDisplay");
        launchSecondaryDisplayPreference.setTitle("Launch Secondary Display");
        launchSecondaryDisplayPreference.setSummary("Launch secondary display activity");
        launchSecondaryDisplayPreference.setIntent(
                new Intent(context, SecondaryDisplayLauncher.class));

    }

    private void addOnboardingPrefsCatergory() {
        PreferenceCategory onboardingCategory = newCategory("Onboarding Flows");
        onboardingCategory.setSummary("Reset these if you want to see the education again.");

        onboardingCategory.addPreference(createOnboardPref("All Apps Bounce",
                HOME_BOUNCE_SEEN.getSharedPrefKey(), HOME_BOUNCE_COUNT.getSharedPrefKey()));
        onboardingCategory.addPreference(createOnboardPref("Hybrid Hotseat Education",
                HOTSEAT_DISCOVERY_TIP_COUNT.getSharedPrefKey(),
                HOTSEAT_LONGPRESS_TIP_SEEN.getSharedPrefKey()));
        onboardingCategory.addPreference(createOnboardPref("Taskbar Education",
                TASKBAR_EDU_TOOLTIP_STEP.getSharedPrefKey()));
        onboardingCategory.addPreference(createOnboardPref("All Apps Visited Count",
                ALL_APPS_VISITED_COUNT.getSharedPrefKey()));
    }

    private Preference createOnboardPref(String title, String... keys) {
        Preference onboardingPref = new Preference(getContext());
        onboardingPref.setTitle(title);
        onboardingPref.setSummary("Tap to reset");
        onboardingPref.setOnPreferenceClickListener(preference -> {
            SharedPreferences.Editor sharedPrefsEdit = LauncherPrefs.getPrefs(getContext())
                    .edit();
            for (String key : keys) {
                sharedPrefsEdit.remove(key);
            }
            sharedPrefsEdit.apply();
            Toast.makeText(getContext(), "Reset " + title, Toast.LENGTH_SHORT).show();
            return true;
        });
        return onboardingPref;
    }
}
+0 −9

File changed.

Preview size limit exceeded, changes collapsed.

+0 −29

File deleted.

Preview size limit exceeded, changes collapsed.

+0 −27
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.launcher3.uioverrides.flags;

import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;

/**
 * Place holder class for developer options.
 */
public class DeveloperOptionsUI {

    public DeveloperOptionsUI(PreferenceFragmentCompat fragment, PreferenceCategory flags) { }
}