/* * 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 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.PackageInfo; 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.util.ArrayMap; import android.util.ArraySet; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; 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.List; import java.util.Set; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceDataStore; import androidx.preference.PreferenceFragment; import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceViewHolder; import androidx.preference.SwitchPreference; /** * Dev-build only UI allowing developers to toggle flag settings and plugins. * See {@link FeatureFlags}. */ @TargetApi(Build.VERSION_CODES.O) public class DeveloperOptionsFragment extends PreferenceFragment { 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(); } @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 pluginActions = manager.getPluginActions(); ArrayMap> plugins = new ArrayMap<>(); for (String action : pluginActions) { String name = toName(action); List result = pm.queryIntentServices( new Intent(action), PackageManager.MATCH_DISABLED_COMPONENTS); for (ResolveInfo info : result) { String packageName = info.serviceInfo.packageName; if (!plugins.containsKey(packageName)) { plugins.put(packageName, new ArraySet<>()); } plugins.get(packageName).add(name); } } List apps = pm.getPackagesHoldingPermissions(new String[]{PLUGIN_PERMISSION}, PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.GET_SERVICES); PreferenceDataStore enabled = manager.getPluginEnabler(); apps.forEach(app -> { if (!plugins.containsKey(app.packageName)) return; SwitchPreference pref = new PluginPreference(prefContext, app, enabled); pref.setSummary("Plugins: " + toString(plugins.get(app.packageName))); mPluginsCategory.addPreference(pref); }); } private String toString(ArraySet plugins) { StringBuilder b = new StringBuilder(); for (String string : plugins) { if (b.length() != 0) { b.append(", "); } b.append(string); } return b.toString(); } private String toName(String action) { String str = action.replace("com.android.systemui.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 boolean mHasSettings; private final PackageInfo mInfo; private final PreferenceDataStore mPluginEnabler; public PluginPreference(Context prefContext, PackageInfo info, PreferenceDataStore pluginEnabler) { super(prefContext); PackageManager pm = prefContext.getPackageManager(); mHasSettings = pm.resolveActivity(new Intent(ACTION_PLUGIN_SETTINGS) .setPackage(info.packageName), 0) != null; mInfo = info; mPluginEnabler = pluginEnabler; setTitle(info.applicationInfo.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 (int i = 0; i < mInfo.services.length; i++) { ComponentName componentName = new ComponentName(mInfo.packageName, mInfo.services[i].name); if (!isEnabled(componentName)) { return false; } } return true; } @Override protected boolean persistBoolean(boolean isEnabled) { boolean shouldSendBroadcast = false; for (int i = 0; i < mInfo.services.length; i++) { ComponentName componentName = new ComponentName(mInfo.packageName, mInfo.services[i].name); if (isEnabled(componentName) != isEnabled) { mPluginEnabler.putBoolean(pluginEnabledKey(componentName), isEnabled); shouldSendBroadcast = true; } } if (shouldSendBroadcast) { final String pkg = mInfo.packageName; 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); holder.findViewById(R.id.settings).setVisibility(mHasSettings ? View.VISIBLE : View.GONE); holder.findViewById(R.id.divider).setVisibility(mHasSettings ? View.VISIBLE : View.GONE); holder.findViewById(R.id.settings).setOnClickListener(v -> { ResolveInfo result = v.getContext().getPackageManager().resolveActivity( new Intent(ACTION_PLUGIN_SETTINGS).setPackage( mInfo.packageName), 0); if (result != null) { v.getContext().startActivity(new Intent().setComponent( new ComponentName(result.activityInfo.packageName, result.activityInfo.name))); } }); holder.itemView.setOnLongClickListener(v -> { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.fromParts("package", mInfo.packageName, null)); getContext().startActivity(intent); return true; }); } } }