From 7b97eebbe5bba3c46756ed71064f16f3b532b0f5 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Fri, 8 Nov 2019 13:43:58 -0800 Subject: [PATCH] Adding utility class to load customization resources for easier prototype Also adding support for grouping multiple plugins in same APK using process name To customize resources update the defination of dymanic_resources in config.xml: @color/delete_target_hover_tint @integer/config_folderDelay @dimen/all_apps_action_spacing @fraction/container_margin Change-Id: I79c08845464510af96b16d375b424ad914657f39 --- res/values-land/dimens.xml | 3 - res/values/config.xml | 3 + res/xml/dynamic_resources.xml | 9 ++ .../settings/DeveloperOptionsFragment.java | 112 ++++++++++-------- .../launcher3/util/DynamicResource.java | 89 ++++++++++++++ .../systemui/plugins/ResourceProvider.java | 47 ++++++++ 6 files changed, 209 insertions(+), 54 deletions(-) create mode 100644 res/xml/dynamic_resources.xml create mode 100644 src/com/android/launcher3/util/DynamicResource.java create mode 100644 src_plugins/com/android/systemui/plugins/ResourceProvider.java diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml index bc658e4830..662b86ebbe 100644 --- a/res/values-land/dimens.xml +++ b/res/values-land/dimens.xml @@ -15,9 +15,6 @@ --> - - 12% - 58dp 48dp diff --git a/res/values/config.xml b/res/values/config.xml index 2a1f6f7864..0dfed97ce3 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -114,4 +114,7 @@ + + + diff --git a/res/xml/dynamic_resources.xml b/res/xml/dynamic_resources.xml new file mode 100644 index 0000000000..f5d262868c --- /dev/null +++ b/res/xml/dynamic_resources.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java index a9242f94e0..3668313bdc 100644 --- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java +++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.settings; +import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; + import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED; import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey; @@ -24,28 +26,21 @@ 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.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; 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.util.Pair; 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; @@ -54,6 +49,16 @@ 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}. @@ -154,44 +159,53 @@ public class DeveloperOptionsFragment extends PreferenceFragment { PackageManager pm = getContext().getPackageManager(); Set pluginActions = manager.getPluginActions(); - ArrayMap> plugins = new ArrayMap<>(); + + ArrayMap, ArrayList>> plugins = + new ArrayMap<>(); + + Set 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 result = pm.queryIntentServices( - new Intent(action), PackageManager.MATCH_DISABLED_COMPONENTS); + new Intent(action), MATCH_DISABLED_COMPONENTS); for (ResolveInfo info : result) { String packageName = info.serviceInfo.packageName; - if (!plugins.containsKey(packageName)) { - plugins.put(packageName, new ArraySet<>()); + if (!pluginPermissionApps.contains(packageName)) { + continue; } - plugins.get(packageName).add(name); + + Pair key = Pair.create(packageName, info.serviceInfo.processName); + if (!plugins.containsKey(key)) { + plugins.put(key, new ArrayList<>()); + } + plugins.get(key).add(Pair.create(name, info.serviceInfo)); } } - 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); + PreferenceDataStore enabler = manager.getPluginEnabler(); + plugins.forEach((key, si) -> { + String packageName = key.first; + List componentNames = si.stream() + .map(p -> new ComponentName(packageName, p.second.name)) + .collect(Collectors.toList()); + if (!componentNames.isEmpty()) { + SwitchPreference pref = new PluginPreference( + prefContext, si.get(0).second.applicationInfo, enabler, componentNames); + pref.setSummary("Plugins: " + + si.stream().map(p -> p.first).collect(Collectors.joining(", "))); + 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_", ""); + 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) { @@ -205,18 +219,20 @@ public class DeveloperOptionsFragment extends PreferenceFragment { private static class PluginPreference extends SwitchPreference { private final boolean mHasSettings; - private final PackageInfo mInfo; private final PreferenceDataStore mPluginEnabler; + private final String mPackageName; + private final List mComponentNames; - public PluginPreference(Context prefContext, PackageInfo info, - PreferenceDataStore pluginEnabler) { + PluginPreference(Context prefContext, ApplicationInfo info, + PreferenceDataStore pluginEnabler, List componentNames) { super(prefContext); PackageManager pm = prefContext.getPackageManager(); mHasSettings = pm.resolveActivity(new Intent(ACTION_PLUGIN_SETTINGS) .setPackage(info.packageName), 0) != null; - mInfo = info; + mPackageName = info.packageName; + mComponentNames = componentNames; mPluginEnabler = pluginEnabler; - setTitle(info.applicationInfo.loadLabel(pm)); + setTitle(info.loadLabel(pm)); setChecked(isPluginEnabled()); setWidgetLayoutResource(R.layout.switch_preference_with_settings); } @@ -227,9 +243,7 @@ public class DeveloperOptionsFragment extends PreferenceFragment { } private boolean isPluginEnabled() { - for (int i = 0; i < mInfo.services.length; i++) { - ComponentName componentName = new ComponentName(mInfo.packageName, - mInfo.services[i].name); + for (ComponentName componentName : mComponentNames) { if (!isEnabled(componentName)) { return false; } @@ -240,17 +254,14 @@ public class DeveloperOptionsFragment extends PreferenceFragment { @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); - + for (ComponentName componentName : mComponentNames) { if (isEnabled(componentName) != isEnabled) { mPluginEnabler.putBoolean(pluginEnabledKey(componentName), isEnabled); shouldSendBroadcast = true; } } if (shouldSendBroadcast) { - final String pkg = mInfo.packageName; + final String pkg = mPackageName; final Intent intent = new Intent(PLUGIN_CHANGED, pkg != null ? Uri.fromParts("package", pkg, null) : null); getContext().sendBroadcast(intent); @@ -268,8 +279,7 @@ public class DeveloperOptionsFragment extends PreferenceFragment { : View.GONE); holder.findViewById(R.id.settings).setOnClickListener(v -> { ResolveInfo result = v.getContext().getPackageManager().resolveActivity( - new Intent(ACTION_PLUGIN_SETTINGS).setPackage( - mInfo.packageName), 0); + new Intent(ACTION_PLUGIN_SETTINGS).setPackage(mPackageName), 0); if (result != null) { v.getContext().startActivity(new Intent().setComponent( new ComponentName(result.activityInfo.packageName, @@ -278,7 +288,7 @@ public class DeveloperOptionsFragment extends PreferenceFragment { }); holder.itemView.setOnLongClickListener(v -> { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); - intent.setData(Uri.fromParts("package", mInfo.packageName, null)); + intent.setData(Uri.fromParts("package", mPackageName, null)); getContext().startActivity(intent); return true; }); diff --git a/src/com/android/launcher3/util/DynamicResource.java b/src/com/android/launcher3/util/DynamicResource.java new file mode 100644 index 0000000000..8a75767f42 --- /dev/null +++ b/src/com/android/launcher3/util/DynamicResource.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2019 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.util; + +import android.content.Context; + +import androidx.annotation.ColorRes; +import androidx.annotation.DimenRes; +import androidx.annotation.FractionRes; +import androidx.annotation.IntegerRes; + +import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; +import com.android.systemui.plugins.PluginListener; +import com.android.systemui.plugins.ResourceProvider; + +/** + * Utility class to support customizing resource values using plugins + * + * To load resources, call + * DynamicResource.provider(context).getInt(resId) or any other supported methods + * + * To allow customization for a particular resource, add them to dynamic_resources.xml + */ +public class DynamicResource implements ResourceProvider, PluginListener { + + private static final MainThreadInitializedObject INSTANCE = + new MainThreadInitializedObject<>(DynamicResource::new); + + private final Context mContext; + private ResourceProvider mPlugin; + + private DynamicResource(Context context) { + mContext = context; + PluginManagerWrapper.INSTANCE.get(context).addPluginListener(this, + ResourceProvider.class, false /* allowedMultiple */); + } + + @Override + public int getInt(@IntegerRes int resId) { + return mContext.getResources().getInteger(resId); + } + + @Override + public float getFraction(@FractionRes int resId) { + return mContext.getResources().getFraction(resId, 1, 1); + } + + @Override + public float getDimension(@DimenRes int resId) { + return mContext.getResources().getDimension(resId); + } + + @Override + public int getColor(@ColorRes int resId) { + return mContext.getResources().getColor(resId, null); + } + + @Override + public void onPluginConnected(ResourceProvider plugin, Context context) { + mPlugin = plugin; + } + + @Override + public void onPluginDisconnected(ResourceProvider plugin) { + mPlugin = null; + } + + /** + * Returns the currently active or default provider + */ + public static ResourceProvider provider(Context context) { + DynamicResource dr = DynamicResource.INSTANCE.get(context); + ResourceProvider plugin = dr.mPlugin; + return plugin == null ? dr : plugin; + } +} diff --git a/src_plugins/com/android/systemui/plugins/ResourceProvider.java b/src_plugins/com/android/systemui/plugins/ResourceProvider.java new file mode 100644 index 0000000000..eaed9e7f1c --- /dev/null +++ b/src_plugins/com/android/systemui/plugins/ResourceProvider.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2019 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.systemui.plugins; + +import com.android.systemui.plugins.annotations.ProvidesInterface; + +/** + * Plugin to support customizing resource + */ +@ProvidesInterface(action = ResourceProvider.ACTION, version = ResourceProvider.VERSION) +public interface ResourceProvider extends Plugin { + String ACTION = "com.android.launcher3.action.PLUGIN_DYNAMIC_RESOURCE"; + int VERSION = 1; + + /** + * @see android.content.res.Resources#getInteger(int) + */ + int getInt(int resId); + + /** + * @see android.content.res.Resources#getFraction(int, int, int) + */ + float getFraction(int resId); + + /** + * @see android.content.res.Resources#getDimension(int) + */ + float getDimension(int resId); + + /** + * @see android.content.res.Resources#getColor(int) + */ + int getColor(int resId); +}