1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.settings; 17 18 import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED; 19 import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey; 20 21 import android.annotation.TargetApi; 22 import android.content.BroadcastReceiver; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.pm.PackageInfo; 28 import android.content.pm.PackageManager; 29 import android.content.pm.ResolveInfo; 30 import android.net.Uri; 31 import android.os.Build; 32 import android.os.Bundle; 33 import android.provider.Settings; 34 import android.util.ArrayMap; 35 import android.util.ArraySet; 36 import android.view.Menu; 37 import android.view.MenuInflater; 38 import android.view.MenuItem; 39 import android.view.View; 40 41 import com.android.launcher3.R; 42 import com.android.launcher3.config.FeatureFlags; 43 import com.android.launcher3.config.FlagTogglerPrefUi; 44 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; 45 46 import java.util.List; 47 import java.util.Set; 48 49 import androidx.preference.Preference; 50 import androidx.preference.PreferenceCategory; 51 import androidx.preference.PreferenceDataStore; 52 import androidx.preference.PreferenceFragment; 53 import androidx.preference.PreferenceScreen; 54 import androidx.preference.PreferenceViewHolder; 55 import androidx.preference.SwitchPreference; 56 57 /** 58 * Dev-build only UI allowing developers to toggle flag settings and plugins. 59 * See {@link FeatureFlags}. 60 */ 61 @TargetApi(Build.VERSION_CODES.O) 62 public class DeveloperOptionsFragment extends PreferenceFragment { 63 64 private static final String ACTION_PLUGIN_SETTINGS = "com.android.systemui.action.PLUGIN_SETTINGS"; 65 private static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN"; 66 67 private final BroadcastReceiver mPluginReceiver = new BroadcastReceiver() { 68 @Override 69 public void onReceive(Context context, Intent intent) { 70 loadPluginPrefs(); 71 } 72 }; 73 74 private PreferenceScreen mPreferenceScreen; 75 76 private PreferenceCategory mPluginsCategory; 77 private FlagTogglerPrefUi mFlagTogglerPrefUi; 78 79 @Override onCreatePreferences(Bundle savedInstanceState, String rootKey)80 public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 81 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 82 filter.addAction(Intent.ACTION_PACKAGE_CHANGED); 83 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 84 filter.addDataScheme("package"); 85 getContext().registerReceiver(mPluginReceiver, filter); 86 getContext().registerReceiver(mPluginReceiver, 87 new IntentFilter(Intent.ACTION_USER_UNLOCKED)); 88 89 mPreferenceScreen = getPreferenceManager().createPreferenceScreen(getContext()); 90 setPreferenceScreen(mPreferenceScreen); 91 92 initFlags(); 93 loadPluginPrefs(); 94 } 95 96 @Override onDestroy()97 public void onDestroy() { 98 super.onDestroy(); 99 getContext().unregisterReceiver(mPluginReceiver); 100 } 101 newCategory(String title)102 private PreferenceCategory newCategory(String title) { 103 PreferenceCategory category = new PreferenceCategory(getContext()); 104 category.setOrder(Preference.DEFAULT_ORDER); 105 category.setTitle(title); 106 mPreferenceScreen.addPreference(category); 107 return category; 108 } 109 initFlags()110 private void initFlags() { 111 if (!FeatureFlags.showFlagTogglerUi(getContext())) { 112 return; 113 } 114 115 mFlagTogglerPrefUi = new FlagTogglerPrefUi(this); 116 mFlagTogglerPrefUi.applyTo(newCategory("Feature flags")); 117 } 118 119 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)120 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 121 if (mFlagTogglerPrefUi != null) { 122 mFlagTogglerPrefUi.onCreateOptionsMenu(menu); 123 } 124 } 125 126 @Override onOptionsItemSelected(MenuItem item)127 public boolean onOptionsItemSelected(MenuItem item) { 128 if (mFlagTogglerPrefUi != null) { 129 mFlagTogglerPrefUi.onOptionsItemSelected(item); 130 } 131 return super.onOptionsItemSelected(item); 132 } 133 134 @Override onStop()135 public void onStop() { 136 if (mFlagTogglerPrefUi != null) { 137 mFlagTogglerPrefUi.onStop(); 138 } 139 super.onStop(); 140 } 141 loadPluginPrefs()142 private void loadPluginPrefs() { 143 if (mPluginsCategory != null) { 144 mPreferenceScreen.removePreference(mPluginsCategory); 145 } 146 if (!PluginManagerWrapper.hasPlugins(getActivity())) { 147 mPluginsCategory = null; 148 return; 149 } 150 mPluginsCategory = newCategory("Plugins"); 151 152 PluginManagerWrapper manager = PluginManagerWrapper.INSTANCE.get(getContext()); 153 Context prefContext = getContext(); 154 PackageManager pm = getContext().getPackageManager(); 155 156 Set<String> pluginActions = manager.getPluginActions(); 157 ArrayMap<String, ArraySet<String>> plugins = new ArrayMap<>(); 158 for (String action : pluginActions) { 159 String name = toName(action); 160 List<ResolveInfo> result = pm.queryIntentServices( 161 new Intent(action), PackageManager.MATCH_DISABLED_COMPONENTS); 162 for (ResolveInfo info : result) { 163 String packageName = info.serviceInfo.packageName; 164 if (!plugins.containsKey(packageName)) { 165 plugins.put(packageName, new ArraySet<>()); 166 } 167 plugins.get(packageName).add(name); 168 } 169 } 170 171 List<PackageInfo> apps = pm.getPackagesHoldingPermissions(new String[]{PLUGIN_PERMISSION}, 172 PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.GET_SERVICES); 173 PreferenceDataStore enabled = manager.getPluginEnabler(); 174 apps.forEach(app -> { 175 if (!plugins.containsKey(app.packageName)) return; 176 SwitchPreference pref = new PluginPreference(prefContext, app, enabled); 177 pref.setSummary("Plugins: " + toString(plugins.get(app.packageName))); 178 mPluginsCategory.addPreference(pref); 179 }); 180 } 181 toString(ArraySet<String> plugins)182 private String toString(ArraySet<String> plugins) { 183 StringBuilder b = new StringBuilder(); 184 for (String string : plugins) { 185 if (b.length() != 0) { 186 b.append(", "); 187 } 188 b.append(string); 189 } 190 return b.toString(); 191 } 192 toName(String action)193 private String toName(String action) { 194 String str = action.replace("com.android.systemui.action.PLUGIN_", ""); 195 StringBuilder b = new StringBuilder(); 196 for (String s : str.split("_")) { 197 if (b.length() != 0) { 198 b.append(' '); 199 } 200 b.append(s.substring(0, 1)); 201 b.append(s.substring(1).toLowerCase()); 202 } 203 return b.toString(); 204 } 205 206 private static class PluginPreference extends SwitchPreference { 207 private final boolean mHasSettings; 208 private final PackageInfo mInfo; 209 private final PreferenceDataStore mPluginEnabler; 210 PluginPreference(Context prefContext, PackageInfo info, PreferenceDataStore pluginEnabler)211 public PluginPreference(Context prefContext, PackageInfo info, 212 PreferenceDataStore pluginEnabler) { 213 super(prefContext); 214 PackageManager pm = prefContext.getPackageManager(); 215 mHasSettings = pm.resolveActivity(new Intent(ACTION_PLUGIN_SETTINGS) 216 .setPackage(info.packageName), 0) != null; 217 mInfo = info; 218 mPluginEnabler = pluginEnabler; 219 setTitle(info.applicationInfo.loadLabel(pm)); 220 setChecked(isPluginEnabled()); 221 setWidgetLayoutResource(R.layout.switch_preference_with_settings); 222 } 223 isEnabled(ComponentName cn)224 private boolean isEnabled(ComponentName cn) { 225 return mPluginEnabler.getBoolean(pluginEnabledKey(cn), true); 226 227 } 228 isPluginEnabled()229 private boolean isPluginEnabled() { 230 for (int i = 0; i < mInfo.services.length; i++) { 231 ComponentName componentName = new ComponentName(mInfo.packageName, 232 mInfo.services[i].name); 233 if (!isEnabled(componentName)) { 234 return false; 235 } 236 } 237 return true; 238 } 239 240 @Override persistBoolean(boolean isEnabled)241 protected boolean persistBoolean(boolean isEnabled) { 242 boolean shouldSendBroadcast = false; 243 for (int i = 0; i < mInfo.services.length; i++) { 244 ComponentName componentName = new ComponentName(mInfo.packageName, 245 mInfo.services[i].name); 246 247 if (isEnabled(componentName) != isEnabled) { 248 mPluginEnabler.putBoolean(pluginEnabledKey(componentName), isEnabled); 249 shouldSendBroadcast = true; 250 } 251 } 252 if (shouldSendBroadcast) { 253 final String pkg = mInfo.packageName; 254 final Intent intent = new Intent(PLUGIN_CHANGED, 255 pkg != null ? Uri.fromParts("package", pkg, null) : null); 256 getContext().sendBroadcast(intent); 257 } 258 setChecked(isEnabled); 259 return true; 260 } 261 262 @Override onBindViewHolder(PreferenceViewHolder holder)263 public void onBindViewHolder(PreferenceViewHolder holder) { 264 super.onBindViewHolder(holder); 265 holder.findViewById(R.id.settings).setVisibility(mHasSettings ? View.VISIBLE 266 : View.GONE); 267 holder.findViewById(R.id.divider).setVisibility(mHasSettings ? View.VISIBLE 268 : View.GONE); 269 holder.findViewById(R.id.settings).setOnClickListener(v -> { 270 ResolveInfo result = v.getContext().getPackageManager().resolveActivity( 271 new Intent(ACTION_PLUGIN_SETTINGS).setPackage( 272 mInfo.packageName), 0); 273 if (result != null) { 274 v.getContext().startActivity(new Intent().setComponent( 275 new ComponentName(result.activityInfo.packageName, 276 result.activityInfo.name))); 277 } 278 }); 279 holder.itemView.setOnLongClickListener(v -> { 280 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 281 intent.setData(Uri.fromParts("package", mInfo.packageName, null)); 282 getContext().startActivity(intent); 283 return true; 284 }); 285 } 286 } 287 } 288