1 /* 2 * Copyright (C) 2019 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 17 package com.android.launcher3.widget.custom; 18 19 import static com.android.launcher3.Flags.enableSmartspaceAsAWidget; 20 import static com.android.launcher3.model.data.LauncherAppWidgetInfo.CUSTOM_WIDGET_ID; 21 import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.CLS_CUSTOM_WIDGET_PREFIX; 22 23 import android.appwidget.AppWidgetManager; 24 import android.appwidget.AppWidgetProviderInfo; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.os.Parcel; 28 import android.os.Process; 29 import android.util.Log; 30 31 import androidx.annotation.NonNull; 32 import androidx.annotation.Nullable; 33 34 import com.android.launcher3.R; 35 import com.android.launcher3.util.MainThreadInitializedObject; 36 import com.android.launcher3.util.PackageUserKey; 37 import com.android.launcher3.util.PluginManagerWrapper; 38 import com.android.launcher3.util.SafeCloseable; 39 import com.android.launcher3.widget.LauncherAppWidgetHostView; 40 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; 41 import com.android.systemui.plugins.CustomWidgetPlugin; 42 import com.android.systemui.plugins.PluginListener; 43 44 import java.lang.reflect.InvocationTargetException; 45 import java.util.ArrayList; 46 import java.util.HashMap; 47 import java.util.List; 48 import java.util.function.Consumer; 49 import java.util.stream.Stream; 50 51 /** 52 * CustomWidgetManager handles custom widgets implemented as a plugin. 53 */ 54 public class CustomWidgetManager implements PluginListener<CustomWidgetPlugin>, SafeCloseable { 55 56 public static final MainThreadInitializedObject<CustomWidgetManager> INSTANCE = 57 new MainThreadInitializedObject<>(CustomWidgetManager::new); 58 59 private static final String TAG = "CustomWidgetManager"; 60 private static final String PLUGIN_PKG = "android"; 61 private final Context mContext; 62 private final HashMap<ComponentName, CustomWidgetPlugin> mPlugins; 63 private final List<CustomAppWidgetProviderInfo> mCustomWidgets; 64 private Consumer<PackageUserKey> mWidgetRefreshCallback; 65 CustomWidgetManager(Context context)66 private CustomWidgetManager(Context context) { 67 mContext = context; 68 mPlugins = new HashMap<>(); 69 mCustomWidgets = new ArrayList<>(); 70 PluginManagerWrapper.INSTANCE.get(context) 71 .addPluginListener(this, CustomWidgetPlugin.class, true); 72 73 if (enableSmartspaceAsAWidget()) { 74 for (String s: context.getResources() 75 .getStringArray(R.array.custom_widget_providers)) { 76 try { 77 Class<?> cls = Class.forName(s); 78 CustomWidgetPlugin plugin = (CustomWidgetPlugin) 79 cls.getDeclaredConstructor(Context.class).newInstance(context); 80 onPluginConnected(plugin, context); 81 } catch (ClassNotFoundException | InstantiationException | IllegalAccessException 82 | ClassCastException | NoSuchMethodException 83 | InvocationTargetException e) { 84 Log.e(TAG, "Exception found when trying to add custom widgets: " + e); 85 } 86 } 87 } 88 } 89 90 @Override close()91 public void close() { 92 PluginManagerWrapper.INSTANCE.get(mContext).removePluginListener(this); 93 } 94 95 @Override onPluginConnected(CustomWidgetPlugin plugin, Context context)96 public void onPluginConnected(CustomWidgetPlugin plugin, Context context) { 97 List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(context) 98 .getInstalledProvidersForProfile(Process.myUserHandle()); 99 if (providers.isEmpty()) return; 100 Parcel parcel = Parcel.obtain(); 101 providers.get(0).writeToParcel(parcel, 0); 102 parcel.setDataPosition(0); 103 CustomAppWidgetProviderInfo info = newInfo(plugin, parcel); 104 parcel.recycle(); 105 mPlugins.put(info.provider, plugin); 106 mCustomWidgets.add(info); 107 } 108 109 @Override onPluginDisconnected(CustomWidgetPlugin plugin)110 public void onPluginDisconnected(CustomWidgetPlugin plugin) { 111 ComponentName cn = getWidgetProviderComponent(plugin); 112 mPlugins.remove(cn); 113 mCustomWidgets.removeIf(w -> w.getComponent().equals(cn)); 114 } 115 116 /** 117 * Inject a callback function to refresh the widgets. 118 */ setWidgetRefreshCallback(Consumer<PackageUserKey> cb)119 public void setWidgetRefreshCallback(Consumer<PackageUserKey> cb) { 120 mWidgetRefreshCallback = cb; 121 } 122 123 /** 124 * Callback method to inform a plugin it's corresponding widget has been created. 125 */ onViewCreated(LauncherAppWidgetHostView view)126 public void onViewCreated(LauncherAppWidgetHostView view) { 127 CustomAppWidgetProviderInfo info = (CustomAppWidgetProviderInfo) view.getAppWidgetInfo(); 128 CustomWidgetPlugin plugin = mPlugins.get(info.provider); 129 if (plugin == null) return; 130 plugin.onViewCreated(view); 131 } 132 133 /** 134 * Returns the stream of custom widgets. 135 */ 136 @NonNull stream()137 public Stream<CustomAppWidgetProviderInfo> stream() { 138 return mCustomWidgets.stream(); 139 } 140 141 /** 142 * Returns the widget provider in respect to given widget id. 143 */ 144 @Nullable getWidgetProvider(ComponentName cn)145 public LauncherAppWidgetProviderInfo getWidgetProvider(ComponentName cn) { 146 return mCustomWidgets.stream() 147 .filter(w -> w.getComponent().equals(cn)).findAny().orElse(null); 148 } 149 newInfo(CustomWidgetPlugin plugin, Parcel parcel)150 private CustomAppWidgetProviderInfo newInfo(CustomWidgetPlugin plugin, Parcel parcel) { 151 CustomAppWidgetProviderInfo info = new CustomAppWidgetProviderInfo(parcel, false); 152 info.provider = getWidgetProviderComponent(plugin); 153 plugin.updateWidgetInfo(info, mContext); 154 return info; 155 } 156 157 /** 158 * Returns an id to set as the appWidgetId for a custom widget. 159 */ allocateCustomAppWidgetId(ComponentName componentName)160 public int allocateCustomAppWidgetId(ComponentName componentName) { 161 return CUSTOM_WIDGET_ID - mCustomWidgets.indexOf(getWidgetProvider(componentName)); 162 } 163 getWidgetProviderComponent(CustomWidgetPlugin plugin)164 private ComponentName getWidgetProviderComponent(CustomWidgetPlugin plugin) { 165 return new ComponentName( 166 PLUGIN_PKG, CLS_CUSTOM_WIDGET_PREFIX + plugin.getClass().getName()); 167 } 168 } 169