Files
Lawnchair/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
T
Brian Isganitis 890d8d5a91 Give each preference screen its own activity and toolbar title
This change is in preparation for each preference screen having its own collapsible toolbar with its specific title.

Test: Subsettings open with activity transition and title changes
Bug: 187732263
Change-Id: Iac44d688539195ddb6c2aca0a96d737ce7727071
2021-05-27 16:08:22 -04:00

449 lines
18 KiB
Java

/*
* 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.settings;
import static android.content.pm.PackageManager.GET_RESOLVED_FILTER;
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED;
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey;
import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.ArrayMap;
import android.util.Pair;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceDataStore;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder;
import androidx.preference.SwitchPreference;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.config.FlagTogglerPrefUi;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Dev-build only UI allowing developers to toggle flag settings and plugins.
* See {@link FeatureFlags}.
*/
@TargetApi(Build.VERSION_CODES.O)
public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
private static final String ACTION_PLUGIN_SETTINGS = "com.android.systemui.action.PLUGIN_SETTINGS";
private static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN";
private final BroadcastReceiver mPluginReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
loadPluginPrefs();
}
};
private PreferenceScreen mPreferenceScreen;
private PreferenceCategory mPluginsCategory;
private FlagTogglerPrefUi mFlagTogglerPrefUi;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
getContext().registerReceiver(mPluginReceiver, filter);
getContext().registerReceiver(mPluginReceiver,
new IntentFilter(Intent.ACTION_USER_UNLOCKED));
mPreferenceScreen = getPreferenceManager().createPreferenceScreen(getContext());
setPreferenceScreen(mPreferenceScreen);
initFlags();
loadPluginPrefs();
maybeAddSandboxCategory();
if (getActivity() != null) {
getActivity().setTitle("Developer Options");
}
}
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);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
EditText filterBox = view.findViewById(R.id.filter_box);
filterBox.setVisibility(VISIBLE);
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);
}
});
}
@Override
public void onDestroy() {
super.onDestroy();
getContext().unregisterReceiver(mPluginReceiver);
}
private PreferenceCategory newCategory(String title) {
PreferenceCategory category = new PreferenceCategory(getContext());
category.setOrder(Preference.DEFAULT_ORDER);
category.setTitle(title);
mPreferenceScreen.addPreference(category);
return category;
}
private void initFlags() {
if (!FeatureFlags.showFlagTogglerUi(getContext())) {
return;
}
mFlagTogglerPrefUi = new FlagTogglerPrefUi(this);
mFlagTogglerPrefUi.applyTo(newCategory("Feature flags"));
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (mFlagTogglerPrefUi != null) {
mFlagTogglerPrefUi.onCreateOptionsMenu(menu);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (mFlagTogglerPrefUi != null) {
mFlagTogglerPrefUi.onOptionsItemSelected(item);
}
return super.onOptionsItemSelected(item);
}
@Override
public void onStop() {
if (mFlagTogglerPrefUi != null) {
mFlagTogglerPrefUi.onStop();
}
super.onStop();
}
private void loadPluginPrefs() {
if (mPluginsCategory != null) {
mPreferenceScreen.removePreference(mPluginsCategory);
}
if (!PluginManagerWrapper.hasPlugins(getActivity())) {
mPluginsCategory = null;
return;
}
mPluginsCategory = newCategory("Plugins");
PluginManagerWrapper manager = PluginManagerWrapper.INSTANCE.get(getContext());
Context prefContext = getContext();
PackageManager pm = getContext().getPackageManager();
Set<String> pluginActions = manager.getPluginActions();
ArrayMap<Pair<String, String>, ArrayList<Pair<String, ResolveInfo>>> plugins =
new ArrayMap<>();
Set<String> pluginPermissionApps = pm.getPackagesHoldingPermissions(
new String[]{PLUGIN_PERMISSION}, MATCH_DISABLED_COMPONENTS)
.stream()
.map(pi -> pi.packageName)
.collect(Collectors.toSet());
for (String action : pluginActions) {
String name = toName(action);
List<ResolveInfo> result = pm.queryIntentServices(
new Intent(action), MATCH_DISABLED_COMPONENTS | GET_RESOLVED_FILTER);
for (ResolveInfo info : result) {
String packageName = info.serviceInfo.packageName;
if (!pluginPermissionApps.contains(packageName)) {
continue;
}
Pair<String, String> key = Pair.create(packageName, info.serviceInfo.processName);
if (!plugins.containsKey(key)) {
plugins.put(key, new ArrayList<>());
}
plugins.get(key).add(Pair.create(name, info));
}
}
PreferenceDataStore enabler = manager.getPluginEnabler();
plugins.forEach((key, si) -> {
String packageName = key.first;
List<ComponentName> componentNames = si.stream()
.map(p -> new ComponentName(packageName, p.second.serviceInfo.name))
.collect(Collectors.toList());
if (!componentNames.isEmpty()) {
SwitchPreference pref = new PluginPreference(
prefContext, si.get(0).second, enabler, componentNames);
pref.setSummary("Plugins: "
+ si.stream().map(p -> p.first).collect(Collectors.joining(", ")));
mPluginsCategory.addPreference(pref);
}
});
}
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 launchOnboardingTutorialPreference = new Preference(context);
launchOnboardingTutorialPreference.setKey("launchOnboardingTutorial");
launchOnboardingTutorialPreference.setTitle("Launch Onboarding Tutorial");
launchOnboardingTutorialPreference.setSummary("Learn the basic navigation gestures.");
launchOnboardingTutorialPreference.setOnPreferenceClickListener(preference -> {
startActivity(launchSandboxIntent.putExtra(
"tutorial_steps",
new String[] {
"HOME_NAVIGATION",
"LEFT_EDGE_BACK_NAVIGATION",
"RIGHT_EDGE_BACK_NAVIGATION",
"OVERVIEW_NAVIGATION"}));
return true;
});
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.setOnPreferenceClickListener(preference -> {
startActivity(launchSandboxIntent.putExtra(
"tutorial_steps",
new String[] {"LEFT_EDGE_BACK_NAVIGATION", "RIGHT_EDGE_BACK_NAVIGATION"}));
return true;
});
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.setOnPreferenceClickListener(preference -> {
startActivity(launchSandboxIntent.putExtra(
"tutorial_steps",
new String[] {"HOME_NAVIGATION"}));
return true;
});
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.setOnPreferenceClickListener(preference -> {
startActivity(launchSandboxIntent.putExtra(
"tutorial_steps",
new String[] {"OVERVIEW_NAVIGATION"}));
return true;
});
sandboxCategory.addPreference(launchOverviewTutorialPreference);
Preference launchAssistantTutorialPreference = new Preference(context);
launchAssistantTutorialPreference.setKey("launchAssistantTutorial");
launchAssistantTutorialPreference.setTitle("Launch Assistant Tutorial");
launchAssistantTutorialPreference.setSummary("Learn how to use the Assistant gesture");
launchAssistantTutorialPreference.setOnPreferenceClickListener(preference -> {
startActivity(launchSandboxIntent.putExtra(
"tutorial_steps",
new String[] {"ASSISTANT"}));
return true;
});
sandboxCategory.addPreference(launchAssistantTutorialPreference);
Preference launchSandboxModeTutorialPreference = new Preference(context);
launchSandboxModeTutorialPreference.setKey("launchSandboxMode");
launchSandboxModeTutorialPreference.setTitle("Launch Sandbox Mode");
launchSandboxModeTutorialPreference.setSummary("Practice navigation gestures");
launchSandboxModeTutorialPreference.setOnPreferenceClickListener(preference -> {
startActivity(launchSandboxIntent.putExtra(
"tutorial_steps",
new String[] {"SANDBOX_MODE"}));
return true;
});
sandboxCategory.addPreference(launchSandboxModeTutorialPreference);
}
private String toName(String action) {
String str = action.replace("com.android.systemui.action.PLUGIN_", "")
.replace("com.android.launcher3.action.PLUGIN_", "");
StringBuilder b = new StringBuilder();
for (String s : str.split("_")) {
if (b.length() != 0) {
b.append(' ');
}
b.append(s.substring(0, 1));
b.append(s.substring(1).toLowerCase());
}
return b.toString();
}
private static class PluginPreference extends SwitchPreference {
private final String mPackageName;
private final ResolveInfo mSettingsInfo;
private final PreferenceDataStore mPluginEnabler;
private final List<ComponentName> mComponentNames;
PluginPreference(Context prefContext, ResolveInfo pluginInfo,
PreferenceDataStore pluginEnabler, List<ComponentName> componentNames) {
super(prefContext);
PackageManager pm = prefContext.getPackageManager();
mPackageName = pluginInfo.serviceInfo.applicationInfo.packageName;
Intent settingsIntent = new Intent(ACTION_PLUGIN_SETTINGS).setPackage(mPackageName);
// If any Settings activity in app has category filters, set plugin action as category.
List<ResolveInfo> settingsInfos =
pm.queryIntentActivities(settingsIntent, GET_RESOLVED_FILTER);
if (pluginInfo.filter != null) {
for (ResolveInfo settingsInfo : settingsInfos) {
if (settingsInfo.filter != null && settingsInfo.filter.countCategories() > 0) {
settingsIntent.addCategory(pluginInfo.filter.getAction(0));
break;
}
}
}
mSettingsInfo = pm.resolveActivity(settingsIntent, 0);
mPluginEnabler = pluginEnabler;
mComponentNames = componentNames;
setTitle(pluginInfo.loadLabel(pm));
setChecked(isPluginEnabled());
setWidgetLayoutResource(R.layout.switch_preference_with_settings);
}
private boolean isEnabled(ComponentName cn) {
return mPluginEnabler.getBoolean(pluginEnabledKey(cn), true);
}
private boolean isPluginEnabled() {
for (ComponentName componentName : mComponentNames) {
if (!isEnabled(componentName)) {
return false;
}
}
return true;
}
@Override
protected boolean persistBoolean(boolean isEnabled) {
boolean shouldSendBroadcast = false;
for (ComponentName componentName : mComponentNames) {
if (isEnabled(componentName) != isEnabled) {
mPluginEnabler.putBoolean(pluginEnabledKey(componentName), isEnabled);
shouldSendBroadcast = true;
}
}
if (shouldSendBroadcast) {
final String pkg = mPackageName;
final Intent intent = new Intent(PLUGIN_CHANGED,
pkg != null ? Uri.fromParts("package", pkg, null) : null);
getContext().sendBroadcast(intent);
}
setChecked(isEnabled);
return true;
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
boolean hasSettings = mSettingsInfo != null;
holder.findViewById(R.id.settings).setVisibility(hasSettings ? VISIBLE : GONE);
holder.findViewById(R.id.divider).setVisibility(hasSettings ? VISIBLE : GONE);
holder.findViewById(R.id.settings).setOnClickListener(v -> {
if (hasSettings) {
v.getContext().startActivity(new Intent().setComponent(
new ComponentName(mSettingsInfo.activityInfo.packageName,
mSettingsInfo.activityInfo.name)));
}
});
holder.itemView.setOnLongClickListener(v -> {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", mPackageName, null));
getContext().startActivity(intent);
return true;
});
}
}
}